代码审计小技巧

学会一些代码审计的小技巧,可以让我们事半功倍,也能帮我们挖到更多有价值的漏洞,面临着找工作的压力,我依然坚持着学习,我相信,学的多了肯定有公司选择录取我的。

1.钻GPC等转义的空子

GPC会自动把我们提交上去的单引号等敏感字符给转义掉,这样我们的攻击代码就没办法执行啦,但是,GPC并不是把所有的变量都进行了过滤,比如$_SERVER变量没有被过滤。

不受GPC保护的$_SERVER变量
GPC是用来过滤request中提交的数据,将特殊字符进行转义来防止攻击。在PHP5之后用$_SERVER取到的header字段不受GPC影响,所以它里面的特殊字符 如单引号不会被转义掉,而在header注入里面最常见的是user-agent、referer以及client-ip/x-forward-for,因为大多web应用都会记录访问者的IP以及referer等信息。测试代码如下:

在浏览器输入http://localhost/a.php?a=1',结果如下:

编码转换问题
在之前写的宽字节注入就是非常典型的编码转换问题导致绕过GPC的方式,内容在《代码审计基础篇》中可以找到。这例子讲的是PHP与MySQL交互过程中发生编码转换导致的问题,PHP自带的编码转换函数也会存在这个问题,比如mb_convert_encoding()函数,测试代码如下:

浏览器输入http://localhost/b.php,结果如下:

PS:把网页和文件编码都设置成UTF-8,不然浏览器会自动转码。
可以看到结果成功闭合了前面的单引号,这种方式造成不少SQL注入的例子。

ECShop编码转换问题分析
我们先看看这个问题的核心代码,在ECShop\source\ecshop\includes\cls_iconv.php文件中,代码如下:
function Convert($source_lang, $target_lang, $source_string = ‘’)
{
/ 如果字符串为空或者字符串不需要转换,直接返回 /
if ($source_string == ‘’ || preg_match(“/[\x80-\xFF]+/“, $source_string) == 0)
{
return $source_string;
}

if ($source_lang)
{
    $this->config['source_lang'] = $this->_lang($source_lang);
}

if ($target_lang)
{
    $this->config['target_lang'] = $this->_lang($target_lang);
}

/* 如果编码相同,直接返回 */
if ($this->config['source_lang'] == $this->config['target_lang'])
{
    return $source_string;
}

$this->SourceText = $source_string;

if (($this->iconv_enabled || $this->mbstring_enabled) && !($this->config['source_lang'] == 'GBK' && $this->config['target_lang'] == 'BIG-5'))
{
    if ($this->config['target_lang'] != 'UNICODE')
    {
        $string = $this->_convert_iconv_mbstring($this->SourceText, $this->config['target_lang'], $this->config['source_lang']);

        /* 如果正确转换 */
        if ($string)
        {
            return $string;
        }
    }

这个函数的作用是将UTF-8的编码转换成GBK,本函数调用到$this->_convert_iconv_mbstring()函数,我们往下看,代码如下:
function _convert_iconv_mbstring($string, $target_lang, $source_lang)
{
if ($this->iconv_enabled)
{
$return_string = @iconv($source_lang, $target_lang, $string);
if ($return_string !== false)
{
return $return_string;
}
}

    if ($this->mbstring_enabled)
    {
        if ($source_lang == 'GBK')
        {
            $source_lang = 'CP936';
        }
        if ($target_lang == 'GBK')
        {
            $target_lang = 'CP936';
        }

        $return_string = @mb_convert_encoding($string, $target_lang, $source_lang);
        if ($return_string !== false)
        {
            return $return_string;
        }
        else
        {
            return false;
        }
    }
}

可以看到最终调用iconv()函数或者mb_convert_encoding()函数来进行转码,如果调用这个函数之后没有再次过滤,则会存在注入问题。

2.神奇的字符串

中国文字博大精深导致机器在语言编码转换的时候,出现各种异常,这些神奇的字符串可能组合成一堆乱码,可能直接把程序搞崩溃掉,也可能是我们利用漏洞的时候变得简单。

字符处理函数报错信息泄露
页面的报错信息通常能泄露文件绝对路径、代码、变量以及函数等信息。不是所有情况下页面都会出现错误信息,要提示错误信息需要打开在PHP配置文件php.ini中设置display_errors=on或者在代码中加入error_reporting()函数。
错误信息显示出文件路径,在渗透测试中,利用页面报错这个方法来获取web路径比较实在。
大多数程序会使用trim()函数对用户名等值去掉两边的空格,这时候我们传入的用户名参数是一个数组,则程序就会报错,测试代码如下:

在浏览器中输入http://localhost/c.php?a[]=test,结果如下:

字符串截断
以前在做渗透时,字符串截断是经常利用的一个方式,下面我们来了解下它的原理。

(I)%00空字符截断
字符串截断被利用的最多的是在文件操作上面,通常用来利用文件包含漏洞和文件上传漏洞,%00即NULL是会被GPC和addslashes()函数过滤掉。PHP基于C语言开发,%00在URL解码后为\0,\0是C语言中字符串结束符,遇到\0不能再读取后面的字符串。测试代码如下:

在浏览器中输入http://localhost/d.php?f=2.txt,结果如下

(II)iconv函数字符编码转换截断
iconv()函数用来做字符编码转换,比如从UTF-8转换到GBK,字符集的编码转换总会存在一些差异,导致部分代码不能被成功转换。在使用iconv()函数转码的时候,当遇到不能处理的字符串则后续字符串会不被处理。测试代码如下:

在浏览器输入,结果如下:

可以看到第一次输出$a变量,1和2都被正常输出,当使用iconv()函数转换编码后,发现被成功截断。
(PS:经测试在chr(128)到chr(255)之间都会被截断)

将代码chr(130)改为chr(256),结果如下:

3.php://输入输出流

PHP提供了php://的协议允许访问PHP的输入输出流、标准输入输出和错误描述符,内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。主要提供如下访问方式来使用这些封装器:
php://stdin
php://stdout
php://stderr
php://input
php://output
php://fd
php://memory
php://temp
php://filter
使用最多的是php://input、php://output和php://filter,其中php://input是可以访问请求的原始数据的只读流。
测试代码如下:

在浏览器输入http://127.0.0.1/g.php,POST提交a=111,结果如下图:

php://output是一个只写的数据流,php://input是读取POST提交上来的数据,而php://output则是 将数据输出。

php://filter是一个文件操作的协议,可以对磁盘中的文件进行读写操作,效果类似于readfile()、file()和file_get_contents()。我们来测试使用php://filter写文件,测试代码如下:

在浏览器输入http://127.0.0.1/h.php,结果如下:

我们执行代码的时候,会在脚本同目录写下”example.txt”,结果如下:

php://filter还可以读文件,如果有远程包含漏洞,测试代码如下:

正常情况下如果我们传入一个文件名,则是会被include函数包含并执行,如果我们想读取web目录下的PHP文件,可以在浏览器输入http://127.0.0.1/i.php?f=php://filter/convert.base64-encode/resource=i.php,目的是将文件进行Base64编码后输出,结果如下:

4.PHP代码解析标签

PHP有几种解析标签的写法来标识PHP代码,当解析器找到这个标签的时候,就会执行这个标签里面的代码,标签种类如下:
(1)标准标签:<?php?>;
(2)脚本标签:;
(3)短标签:<?…?>,使用短标签前需要在php.ini中设置short_open_tag=on;
(4)asp标签:<%…%>,需要在php.ini中设置asp_tags=on。
有的程序在后台配置模板会禁止提交<?php?>这样的标签,但是大部分程序会存在过滤不全的问题,所以这些各式各样的写法常常用于留后门以及绕过Web程序或者waf的防护写入webshell。
测试代码如下:

在浏览器输入,结果如下:

可以看到PHP代码可以正常解析。

5.fuzz漏洞发现

fuzz指的是对特定目标的模糊测试,它不同于漏洞扫描器进行批量漏洞扫描。fuzz的工作原理流程如下

目前互联网有不少fuzz工具来专门做各种各样的fuzz测试,比如无线、Web、浏览器、协议等。
我在之前介绍过iconv()函数字符编码转换截断时提过一个字符串枚举来尝试寻找导致iconv()函数异常而截断数据,代码如下:

在浏览器输入http://localhost/k.php,结果如下:

由结果可知当遇到不能正常转码的时候出现字符串截断,并且iconv()函数报出一个notice提示。

6.不严谨的正则表达式

很多程序在判断文件上传扩展名、URL解析、入库参数等值的时候,都会使用正则表达式,正则表达式能够帮助我们少写很多逻辑处理的代码,但是如果规则写的不严谨,就会导致安全问题产生。

(I)没有使用^和$限定匹配开始位置
举例说明,通过HTTP_CLIENT_IP来获取用户ip,其中这个值是可以被修改的,所以一般都会在服务端再过滤一下,看看是否再过滤一下,看看是否被修改过,而过滤不严格的正则表达式很多写成”\d+.\d+.\d+.\d+”的形式,测试代码如下:
<?php
$ip=$_SERVER[‘HTTP_CLIENT_IP’];
if(preg_match(‘/\d+.\d+.\d+.\d+/‘,$ip))
{
echo $ip;
}
?>
当请求头里面添加”client-ip:127.0.0.1aa”时输出127.0.0.1aa,同样通过检测,严谨一点的正则应该写成”^\d+.\d+.\d+.\d+$”。

(II)特殊字符未转义
在正则表达式里,所有能被正则表达式引擎解析的字符都算是特殊字符,而在匹配这些字符的原字符时需要使用反斜杠()来进行转义,如果不进行转义,像英文句号(.)可用来表示任何字符,存在安全隐患。
代码如下:
<?php
$filename=urldecode(‘xxx.php%00jpg’);
if(preg_match(‘/.(jpg|gif|png|bmp)$/i’,$filename))
{
file_put_contents($filename,’aa’);
}
else
{
echo ‘不允许的文件扩展名’;
}
?>
这段代码可以看出,程序员原本是想检查文件的扩展名,如果不是图片规则不允许上传,但是在检查扩展名的时候,正则表达式里面扩展名前面的点(.)没有进行转义,导致变成了全匹配符。如果这时候提交的文件名是’xxx.php%00jpg’,则会绕过检查并写入一个PHP文件。

7.Windows FindFirstFile利用

目前大多数程序都会对上传的文件名加入时间戳等字符再进行MD5,然后下载文件的时候通过保存在数据库里的文件ID读出文件路径,在Windows系统下利用Windows的特性就可以访问到文件,这是因为Windows在搜索文件的时候使用到了FindFirstFile这一个winapi函数,该函数到一个文件夹去搜索指定文件。
利用方法很简单,我们只要将文件名不可知部分之后的字符用”<”或者”>”代替即可。
(PS:一个”<”或者”>”只能代表一个字符)
做个简单的测试,测试代码如下:
<?php
include($_GET[‘file’]);
?>
在脚本同目录下建一个12345.txt文件,文件内容是phpinfo()函数:

在浏览器输入http://localhost/a.php?file=12<<,结果如下:

由上图可以看出成功包含了12345.txt文件。

PHP并没有在语言层面禁止使用>、<这些特殊字符,在函数层面上讲很多函数都有这个特性,函数列表如下:include() include_once() require() require_once() fopen() copy() readfile() file_get_contents() parse_ini_file() file_put_contents() mkdir() tempnam() tuoch() move_uploaded_file() opendir() readdir() rewinddir() closedir()

8.PHP可变变量

PHP可变变量指的一个变量的变量名可以动态地设置和使用,是PHP语言的一个特性,这个特性让我们在操作变量的时候更加方便灵活,但是也带来一些安全问题,我们在挖掘到代码执行漏洞的时候就经常需要用到可变变量来执行代码。
现在先用一段代码理解什么是可变变量,代码如下:
<?php
$a=’seay’;
$$a=’123’;
echo $seay;
?>
在浏览器输入http://localhost/a.php,结果如下:

输出变量$seay的值为123,这个123是$$a赋值的,这时候$$a就相当于$seay。

部分PHP应用在写配置文件或者使用preg_replace()函数第二个参数赋值变量时,会用到双引号(“)来代表string型给变量赋值,在PHP语言中,单引号和双引号是有区别的,单引号代表纯字符串,而双引号则是会解析中间的变量,所以在使用双引号时会存在代码执行漏洞。
测试代码如下:
<?php
$a=”${@phpinfo()}”;
?>
在浏览器输入http://localhost/a.php,结果如下:

PHPinfo()函数成功执行,这里有个该注意的地方,代码$a=”${@phpinfo()}”中的”@”符合必须存在。
除了”@”符号,还有其他写法也可以:
(1)花括号内第一个字符空格: $a=”${ phpinfo()}”;
(2)花括号内第一个字符为TAB:$a=”${ phpinfo()}”;
(3)括号内第一个字符为注释符:$a=”${/**/ phpinfo()}”;
(4)括号内第一个字符为回车换行符:
$a=”${
phpinfo()}”;
(5)括号内第一个字符为+:$a=”${+phpinfo()}”;
(6)括号内第一个字符为-:$a=”${-phpinfo()}”;
(7)括号内第一个字符为!:$a=”${!phpinfo()}”;

猜你喜欢

转载自blog.csdn.net/qq_36197704/article/details/81623882