代码审计系列:审计思路学习笔记

Posted 思源湖的鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代码审计系列:审计思路学习笔记相关的知识,希望对你有一定的参考价值。

前言

学习记录代码审计的一些思路

一、基础代码审计

1、代码审计思路

首先要知道,程序的根本是什么

  • 函数
  • 变量

我们代码审计就怼这两个东西,要让其变成可利用的漏洞,关键在于

  • 可控变量
  • 变量到达有利用价值的函数(危险函数)

一般来说,漏洞的利用效果取决于最终函数的功能

一般拿到一个cms后的操作:

  • 如果是之前没有审计过的,就先通读一遍代码,再通过写思维导图(就是写目录注释),看目录和对应功能的文件给一一列举一下出来(就是写代码注释),摸清楚大体的框架
  • 如果是之前就审计过的,直接上工具走一遍危险函数

一些工具:

接下来就是审计了,有两条线:

  • 按照具体功能审计
  • 按照漏洞类型审计

(1)按照具体功能审计

就是对应我们通读代码的时候标志好的

比如说,upload对应的是上传功能,那我们首选就应该开工找上传

比如有函数名 move_uploaded_file() ,接着看调用这个函数的代码是否存在为限制上传格式或者可以绕过

  • 未过滤或本地过滤:服务器端未过滤,直接上传php格式的文件即可利用

  • 黑名单扩展名过滤:
    限制不够全面:IIS默认支持解析.asp,.cdx, .asa,.cer等。
    扩展名可绕过:不被允许的文件格式.php,但是我们可以上传文件名为1.php(注意后面有一个空格)

  • 文件头 content-type验证绕过:
    getimagesize()函数:验证文件头只要为GIF89a,就会返回真
    限制$_FILES["file"]["type"]的值 就是人为限制content-type为可控变量

  • 防范:
    使用in_array()或 利用三等于===对比扩展名
    保存上传文件是重命名,规则采用时间戳拼接随机数:md5(time() + rand(1,1000))

(2)按照漏洞类型审计

这个很好理解,命令执行,代码执行,sql注入,文件操作(包含上传读取下载等),变量覆盖,xss

  • PHP 执行系统命令可以使用的函数:systemexecpassthrushell_execpopenproc_openpcntl_exec
  • PHP 可能出现代码执行的函数:evalpreg_replace+/eassertcall_user_func
    call_user_func_arraycreate_function
  • SQL 注入一般会查找 SQL 语句关键字:insertdeleteupdateselect
  • PHP 可能出现文件包含的函数:includeinclude_oncerequirerequire_once
    show_sourcehighlight_filereadfilefile_get_contentsfopennt>file
  • PHP 变量覆盖会出现在下面几种情况:
    遍历初始化变量:foreach($_GET as $key => $value);$$key = $value;
    函数覆盖变量:parse_strmb_parse_strimport_request_variables
    Register_globals=ON 时,GET 方式提交变量会直接覆盖
  • PHP 文件读取:copyrmdirunlinkdeletefwritechmodfgetcfgetcsvfgetsfgetssfilefile_get_contentsfreadreadfileftruncatefile_put_contentsfputcsvfputs
  • PHP 文件上传:move_uploaded_file() 接着看调用这个函数的代码是否存在为限制上传格式或者可以绕过

通常是上手一个好用的审计工具,然后写正则,由高危函数回溯到Model层再到Control层,最终定位漏洞的触发位置

  • $$变量覆盖
(?:extract|parse_str)\\s{0,5}\\(.{0,50}\\$

\\$\\$

\\${{0,1}\\$\\w{1,20}((\\[["']|\\[)\\${0,1}[\\w\\[\\]"']{0,30}){0,1}\\s{0,4}=\\s{0,4}.{0,20}\\$\\w{1,20}((\\[["']|\\[)\\${0,1}[\\w\\[\\]"']{0,30}){0,1}
  • array_map代码执行
\\barray_map\\s{0,4}\\(\\s{0,4}.{0,20}\\$\\w{1,20}((\\[["']|\\[)\\${0,1}[\\w\\[\\]"']{0,30}){0,1}\\s{0,4}.{0,20},

(?:\\beval\\(|assert\\(|call_user_func\\(|call_user_func_array\\(|create_function\\(|preg_replace\\(\\s{0,5}.*/[is]{0,2}e[is]{0,2}["']\\s{0,5},(.*\\$.*,|.*,.*\\$))
  • 命令执行
(?:\\bexec\\(|passthru\\(|\\bsystem\\(|\\bpopen\\(|proc_open\\(|proc_close\\(|shell_exec\\(|pcntl_exec\\(|escapeshellcmd\\().{0,50}\\$

2、快速审计

(1)补丁对比

常见对比工具:

  • 系统命令:fc、diff等

  • 专业工具:Beyond Compare、UltraCompare等

常见的安全补丁方式:

  • 变量初始化:$str=‘’;$arr=array();

  • 变量过滤: intval/int()addslashes()、正则等

对比的版本选择:

  • 相临近的版本

常用的办法:

1.对常见的中间件的公布漏洞网站的页面进行监控,能够快速知道和了解这个漏洞,比如说

这种网站的页面,用爬虫监控起来,如果更新,比如说security-8.html是目前最新的,当出现security-9.html存活的时候/状态码200的时候,就触发一个事件(给你发短信之类的),然后就能速度响应了。

也通用于CMS,比如说wordpress,就是换个网站而已。
https://core.trac.wordpress.org/query?status=closed&milestone=4.7.3&group=component&col=id&col=summary&col=component&col=status&col=owner&col=type&col=priority&col=keywords&order=priority
这个status 和milestone.

2.下载版本:可以一直保存所有版本,也是和上面一样,监控就行。比如说wordpress,监控https://wordpress.org/download/就行了,留个硬盘,专门保存所有历史版本。

  • 中间件:Apache&Apache tomcat 、nginx 、redis 、Weblogic 、Jboss 、JOnAS 、WebSphere 等等

  • CMS:
    国外:wordpress 、joomla 、 drupal 等等
    国内:dedecms 帝国cms 思途cms discuz CMS ECShop phpcms 等等

3.补丁对比

首先我们要确定一点,就是这个版本更新是属于安全更新,而不是功能更新
检索这些敏感的关键词,定位到函数或者xx.php,再用审计工具跟进这个函数(静态分析)
如果单论跟进的话,就用seay的工具

(2)逻辑漏洞业务型漏洞

逻辑漏洞或者说业务漏洞大概有以下这些:

  • 身份认证安全:暴力破解、cookie&session、加密测试
  • 业务一致性安全:手机号篡改、邮箱篡改、订单ID篡改、用户ID篡改、商务编号篡改
  • 业务数据篡改:金融数据篡改、商品数据篡改、本地js参数篡改、最大数限制突破
  • 密码找回漏洞
  • 验证码安全
  • 业务授权安全
  • 业务流程乱序
  • 业务接口调用:恶意注册、内容编辑、短信轰炸
  • 时效绕过

可简单参考:浅谈逻辑漏洞

几个例子:

1、某些函数的错误,如:empty()isset()strpos()rename()

if($operateId == 1){
    $date = date("Ymd");
    $dest = $CONFIG->basePath."data/files/".$date."/";
    $COMMON->createDir($dest);
    //if (!is_dir($dest))   mkdir($dest, 0777);
    $nameExt = strtolower($COMMON->getFileExtName($_FILES['Filedata']['name']));
    $allowedType = array('jpg', 'gif', 'bmp', 'png', 'jpeg');
    if(!in_array($nameExt, $allowedType)){
        $msg = 0;
    } //这里的安全检查把msg赋值为0,直接就可以进入下面的if分支了
    if(empty($msg)){
        $filename = getmicrotime().'.'.$nameExt;
        $file_url = urlencode($CONFIG->baseUrl.'data/files/'.$date."/".$filename);
        $filename = $dest.$filename;
        if(empty($_FILES['Filedata']['error'])){
            move_uploaded_file($_FILES['Filedata']['tmp_name'],$filename);
        }
        if (file_exists($filename)){
            //$msg = 1;
            $msg = $file_url;
            @chmod($filename, 0444);
        }else{
            $msg = 0;
        }
    }
    $outMsg = "fileUrl=".$msg;
    $_SESSION["eoutmsg"] = $outMsg;
    exit;
}

2、条件竞争

  • 程序猿逻辑:利用copy函数,将realfile生成shell.php-→删除掉shell.php
  • 黑客逻辑:copy成temp.php–>不断访问temp.php->temp.php生成shell.php->删除temp.php
if($_POST['realfile']){
    copy($_POST['realfile'],$_POST['path']);
}
$file = mb_convert_encoding($_POST[file],"GBK","UTF-8");
header("Pragma:");
header("Cache-Control:");
header("Content-type:application/octet-stream");
header("Content-Length:".filesize($_POST[path]));
header("Content-Disposition:attachment;filename=\\"$file\\"");
readfile($_POST[path]);
if($_POST['realfile']){
    unlink($_POST["path"]);
}

(3)Fuzzing

Fuzzing技术是一种基于缺陷注入的自动软件测试技术,它利用黑盒分析技术方法,使用大量半有效的数据作为应用程序的输入,以程序是否出现异常为标志,来发现应用程序中可能存在的安全漏洞。半有效数据是指被测目标程序的必要标识部分和大部分数据是有效的,有意构造的数据部分是无效的,应用程序在处理该数据时就有可能发生错误,可能导致应用程序的崩溃或者触发相应的安全漏洞。

根据分析目标的特点,Fuzzing可以分为三类:

  • 动态Web页面Fuzzing,针对ASP、PHP、Java、Perl等编写的网页程序,也包括使用这类技术构建的B/S架构应用程序,典型应用软件为HTTP Fuzz;

  • 文件格式Fuzzing,针对各种文档格式,典型应用软件为PDF Fuzz;

  • 协议Fuzzing,针对网络协议,典型应用软件为针对微软RPC(远程过程调用)的Fuzz。

Fuzzer软件输入的构造方法与黑盒测试软件的构造相似,边界值、字符串、文件头、文件尾的附加字符串等均可以作为基本的构造条件。Fuzzer软件可以用于检测多种安全漏洞,包括缓冲区溢出漏洞、整型溢出漏洞、格式化字符串和特殊字符漏洞、竞争条件和死锁漏洞、SQL注入、跨站脚本、RPC漏洞攻击、文件系统攻击、信息泄露等

一些工具:

  • Browser Fuzzer 3 (bf3)
  • MantraPortable
  • Webshag
  • Wfuzz
  • WVS
  • LAN Guard
  • SQLmap

二、高级代码审计

1、变量本身的key

说到变量的提交很多人只是看到了GET/POST/COOKIE等提交的变量的值,但是忘记了有的程序把变量本身的key也当变量提取给函数处理。

<?php
//key.php?aaaa'aaa=1&bb'b=2 
//print_R($_GET); 
 foreach ($_GET AS $key => $value)
{
	print $key."\\n";
}
?>

上面的代码就提取了变量本身的key显示出来,单纯对于上面的代码,如果我们提交URL:

key.php?<script>alert(1);</script>=1&bbb=2

那么就导致一个xss的漏洞,扩展一下如果这个key提交给include()等函数或者sql查询呢?

2、变量覆盖

很多的漏洞查找者都知道extract()这个函数在指定参数为EXTR_OVERWRITE或者没有指定函数可以导致变量覆盖,但是还有很多其他情况导致变量覆盖的如:

(1)遍历初始化变量

请看如下代码:

<?php
//var.php?a=fuck
$a='hi';
foreach($_GET as $key => $value) {
	$$key = $value;
}
print $a;
?>

很多的WEB应用都使用上面的方式(注意循环不一定是foreach),如Discuz!4.1的WAP部分的代码:

$chs = '';
if($_POST && $charset != 'utf-8') {
	$chs = new Chinese('UTF-8', $charset);
	foreach($_POST as $key => $value) {
		$$key = $chs->Convert($value);
	}
	unset($chs);

漏洞审计策略

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:通读代码

(2)parse_str()变量覆盖漏洞

//var.php?var=new
$var = 'init';                     
parse_str($_SERVER['QUERY_STRING']); 
print $var;

该函数一样可以覆盖数组变量,上面的代码是通过$_SERVER['QUERY_STRING']来提取变量的,对于指定了变量名的我们可以通过注射“=”来实现覆盖其他的变量:

//var.php?var=1&a[1]=var1%3d222
$var1 = 'init';
parse_str($a[$_GET['var']]);
print $var1;

上面的代码通过提交$var来实现对$var1的覆盖。

漏洞审计策略(parse_str)

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:查找字符parse_str

漏洞审计策略(mb_parse_str)

  • PHP版本要求:php4<4.4.7 php5<5.2.2
  • 系统要求:无
  • 审计策略:查找字符mb_parse_str

(3)import_request_variables()变量覆盖漏洞

//var.php?_SERVER[REMOTE_ADDR]=10.1.1.1
echo 'GLOBALS '.(int)ini_get("register_globals")."n";
import_request_variables('GPC');
if ($_SERVER['REMOTE_ADDR'] != '10.1.1.1') die('Go away!');
echo 'Hello admin!';

漏洞审计策略(import_request_variables)

  • PHP版本要求:php4<4.4.1 php5<5.2.2
  • 系统要求:无
  • 审计策略:查找字符import_request_variables

(4)PHP5 Globals

从严格意义上来说这个不可以算是PHP的漏洞,只能算是一个特性,测试代码:

<?
// register_globals =ON
//foo.php?GLOBALS[foobar]=HELLO
php echo $foobar; 
?>

但是很多的程序没有考虑到这点,请看如下代码:

//为了安全取消全局变量
//var.php?GLOBALS[a]=aaaa&b=111
if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k});
print $a;
print $_GET[b];

如果熟悉WEB2.0的攻击的同学,很容易想到上面的代码我们可以利用这个特性进行crsf攻击。

漏洞审计策略

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:通读代码

3、magic_quotes_gpc与代码安全

当打开时,所有的 '(单引号),"(双引号),\\(反斜线)和 NULL 字符都会被自动加上一个反斜线进行转义。还有很多函数有类似的作用 如:addslashes()mysql_escape_string()mysql_real_escape_string()等,另外还有parse_str()后的变量也受magic_quotes_gpc的影响。目前大多数的主机都打开了这个选项,并且很多程序员也注意使用上面那些函数去过滤变量,这看上去很安全。很多漏洞查找者或者工具遇到些函数过滤后的变量直接就放弃,但是就在他们放弃的同时也放过很多致命的安全漏洞。

(1)哪些地方没有魔术引号的保护

1、$_SERVER变量

PHP5的$_SERVER变量缺少magic_quotes_gpc的保护,导致近年来X-Forwarded-For的漏洞猛暴,所以很多程序员考虑过滤X-Forwarded-For,但是其他的变量呢?

漏洞审计策略($_SERVER变量)

  • PHP版本要求:5
  • 系统要求:无
  • 审计策略:查找字符_SERVER

2、 getenv()得到的变量(使用类似$_SERVER变量)

漏洞审计策略(getenv())

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:查找字符getenv

3、$HTTP_RAW_POST_DATA与PHP输入、输出流

主要应用于soap/xmlrpc/webpublish功能里,请看如下代码:

if ( !isset( $HTTP_RAW_POST_DATA ) ) {
	$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
}
if ( isset($HTTP_RAW_POST_DATA) )
	$HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);

漏洞审计策略(数据流)

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:查找字符HTTP_RAW_POST_DATA或者php://input

4、数据库操作容易忘记'的地方如:in()/limit/order by/group by

如Discuz!<5.0的pm.php:

if(is_array($msgtobuddys)) {
	$msgto = array_merge($msgtobuddys, array($msgtoid));
		......
foreach($msgto as $uid) {
	$uids .= $comma.$uid;
	$comma = ',';
}
......
$query = $db->query("SELECT m.username, mf.ignorepm FROM {$tablepre}members m
	LEFT JOIN {$tablepre}memberfields mf USING(uid)
	WHERE m.uid IN ($uids)");

漏洞审计策略

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:查找数据库操作字符(select,update,insert等等)

(2)变量的编码与解码

一个WEB程序很多功能的实现都需要变量的编码解码,而且就在这一转一解的传递过程中就悄悄的绕过你的过滤的安全防线。

这个类型的主要函数有:

1、stripslashes() 这个其实就是一个decode-addslashes()

2、其他字符串转换函数:
在这里插入图片描述

3、字符集函数(GKB,UTF7/8…)如iconv()/mb_convert_encoding()等

目前很多漏洞挖掘者开始注意这一类型的漏洞了,如典型的urldecode:

$sql = "SELECT * FROM article WHERE articleid='".urldecode($_GET[id])."'";

magic_quotes_gpc=on时,我们提交?id=%2527,得到sql语句为:

SELECT * FROM article WHERE articleid='''

漏洞审计策略

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:查找对应的编码函数

(3)二次攻击

1、数据库出来的变量没有进行过滤

2、数据库的转义符号

4、代码注射

(1)PHP中可能导致代码注射的函数

很多人都知道evalpreg_replace+/e可以执行代码,但是不知道php还有很多的函数可以执行代码如:

  • assert()
  • call_user_func()
  • call_user_func_array()
  • create_function()
  • 变量函数

几个关于create_function()代码执行漏洞的代码:

<?php
//how to exp this code
$sort_by=$_GET['sort_by'];
$sorter='strnatcasecmp';
$databases=array('test','test');
$sort_function = '  return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);
	      ';
usort($databases, create_function('$a, $b', $sort_function));

漏洞审计策略

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:查找对应函数(assert,call_user_func,call_user_func_array,create_function等)

(2)变量函数与双引号

对于单引号和双引号的区别,很多程序员深有体会,示例代码:

echo "$a\\n";
echo '$a\\n';

我们再看如下代码:

//how to exp this code
if($globals['bbc_email']){

$text = preg_replace(
		array("/\\[email=(.*?)\\](.*?)\\[\\/email\\]/ies",
				"/\\[email\\](.*?)\\[\\/email\\]/ies"),
		array('check_email("$1", "$2")',
				'check_email("$1", "$1")'), $text);

另外很多的应用程序都把变量用""存放在缓存文件或者config或者data文件里,这样很容易被人注射变量函数。

漏洞审计策略

  • PHP版本要求:无
  • 系统要求:无
  • 审计策略:通读代码

5、PHP自身函数漏洞及缺陷

(1)PHP函数的溢出漏洞

比较有名的要算是unserialize(),代码如下:

unserialize(stripslashes($HTTP_COOKIE_VARS[$cookiename . '_data']);

在以往的PHP版本里,很多函数都曾经出现过溢出漏洞,所以我们在审计应用程序漏洞的时候不要忘记了测试目标使用的PHP版本信息。

漏洞审计策略

  • PHP版本要求:对应fix的版本
  • 系统要求:五
  • 审计策略:查找对应函数名

(2)PHP函数的其他漏洞

Stefan Esser大牛发现的漏洞:unset()–Zend_Hash_Del_Key_Or_Index Vulnerability

比如phpwind早期的serarch.php里的代码:

unset($uids);

以上是关于代码审计系列:审计思路学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

代码审计那些代码审计的思路

一起来学PHP代码审计入门

小皮1-click漏洞的代码审计学习笔记

Bluecmsv1.6-代码审计

代码审计思路之PHP代码审计

一次小破站JS代码审计出XSS漏洞思路学习