PHP代码审计小技巧(上)

admin 2021年12月31日03:56:47PHP代码审计小技巧(上)已关闭评论125 views字数 8881阅读29分36秒阅读模式

下面我们会用两篇文章分析从怎么绕过GPC等过滤、字符串常见的安全问题、PHP输入输出流、FUZZ挖掘漏洞以及正则表达式不严谨容易出现的问题等几个方面来介绍一些小技巧。

1.绕过GPC等转义

GPC会自动把我们提交上去的单引号等敏感字符转义掉,这样我们的攻击代码就没法执行了,GPC是PHP天生自带的功能,所以是我们最大的天敌。不过不要担心,GPC并不是把所有变量都进行了过滤,反而人们容易忽视而又用得多的$_SERVER变量没有被GPC过滤,包括编码转换的过程中,部分情况下我们也是可以干掉GPC的转义符号,是不是有点小激动?下面我们来仔细了解下。

1.1**不受GPC保护的$_SERVER变量

GPC上面我们已经介绍过,是用来过滤request中提交的数据,将特殊字符进行转义来防止攻击,在PHP5之后用$_SERVER取到的header字段不受GPC影响,所以当GPC开启的时候,它里面的特殊字符如单引号也不会被转义掉,另外一点是普通程序员很少会考虑这些字段被修改。而在header注入里面最常见的是user-agent、referer以及client-ip/x-forward-for,因为大多的Web应用都会记录访问者的IP以及referer等信息。同样的$_FILES变量也一样不受GPC保护。

测试代码如下:

```php
<?php

echo 'GPC'.get_magic_quotes_gpc();
echo '

client-ip = '.$_SERVER['HTTP_CLIENT_IP'];
echo '

$_GET[a] = '.$_GET['a'];
```

测试前将对应php版本的配置文件php.ini中的magic_quotes_gpc = On即可看到效果

PHP代码审计小技巧(上)

测试截图见图1-1

PHP代码审计小技巧(上)

图1-1

1.2编码转换问题

在SQL注入漏洞中的宽字节注入就是一种非常典型的编码转换问题导致绕过GPC的方式。给一个查询页面ID参数请求/1.php?id=-1%df' and 1=1%23时,这时MySQL运行的SQL语句为:

sql
select * from user where id = '1運' and 1=1#'

这是由于单引号被自动转成',前面的%df和转义字符反斜杠(%5c)组合成了%df%5c,也就是“運”字,这时候单引号依然还在,于是成功闭合了前面的单引号。

这个例子讲的是PHP与MySQL交互过程中发生编码转换导致的问题,而其实只要发生编码转换就有可能出现这种问题,也就是说在PHP自带的编码转换函数上面也会存在这个问题,比如mb_convert_encoding()函数。

我们来证实一下,代码如下:

php
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<?php
$sql="where id='"urldecode("-1%df%5c' -- ")."'";
print_f(mb_convert_encoding($sql, "UTF-8", "GBK"));
?>

这里需要注意,把网页和文件编码都设置成UTF-8,不然浏览器会自动转码,这段代码是把UTF-8编码转换成GBK,运行这段代码,输出如下:

sql
where id='-1運' -- '

可以看到也成功闭合了前面的单引号。

这种方式造成的SQL注入也有不少先例,比如ecshop就出过多次这个问题,我们来看看出现这个问题的核心代码,代码位置出现在inlucdes.cls_iconv.php文件的chinese类中的Convert()函数:

```php
<?php

function Convert($source_lang, $target_lang, $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;
        }
    }
    else {
        $string = '';
        $text = $SourceText;
        while($text) {
            if (ord(substr($text, 0, 1)) > 127) {
                if ($this->config['source_lang'] != 'UTF-8') {
                    $char = $this->_convert_iconv_mbstring(substr($text, 0, 2), 'UTF-8', $this->congfig['source_lang']);
                }
                else {

                }
            }
        }
    }
}

}
```

这个函数的作用是将UTF-8的编码转换成GBK,本函数调用到$this->_convert_iconv_mbstring()函数,我们跟进去看看,代码如下:

```php
<?php

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){

    }
}

}
```

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

2.神奇的字符串

中国文字博大精深,而在计算机里面就是因为这些语言的"博大"即大而杂,导致机器在语言编码转换的时候,经常会出现各种各样的异常,这些神奇的字符串可能组合成一堆乱码来,也有可能直接把程序搞崩溃掉,不过总有那么一些字符,可以帮助我们在利用漏洞的时候变得更简单一些,下面我们就来看看是哪些函数这么调皮。

2.1字符串里函数报错信息泄露

页面的报错信息通常能泄露文件的绝对路径、代码、变量以及函数等信息,页面报错有很多情况,比如参数少了或者多了、参数类型不对、数组下标越界、页面超时,等,不过并不是所有情况下页面都会出现错误信息,要显示错误信息需要打开在PHP配置文件php.ini中设置display_errors=on或者在代码中加入error_reporting()函数,error_reporting()函数有几个选项来配置显示错误的等级,列表如下:

报告级别

PHP代码审计小技巧(上)

其中最常用的是E_ALL、E_WARNING、E_NOTICE,E_ALL代表提示所有问题,E_WARNING代表显示错误信息,E_NOTICE则是显示基础提示信息。

大多数错误提示都会显示文件路径,在渗透测试中,经常遇到webshell的场景要用到文件的绝对路径,所以利用页面报错来获取Web路径也比较实在了,用户提交上去的数据后端大多是以字符串方式处理,所以利用字符串处理函数报错成了必不可少的方法,对于利用参数来报错,给函数传入不同类型的变量是最实用的方式。

大多数程序会使用trim()函数对用户名等值去掉两边的空格,这时候如果我们传入的用户名是一个数组,则程序会报错,测试代码如下:

php
<?php
echo trim($_GET['a']);

当我们请求/char.php?a[]=try,程序报错如下,如图1-2所示。

PHP代码审计小技巧(上)

图2-1

类似的函数还有很多很多,比如

addcslashes()、addslashes()、bin2hex()、chop()、chunk_split()、convert_ayr_string()、convert_uudecode()、convert_uuencode、count_chars()、crc32()、crypt()、echo()、explode()、fprintf()、get_html_translation_table()、hebrev()、hebrevc()、html_entity_decode()、htmlentites()、htmlspecialchars_decode()、htmlspecialchars()、implode()、join()、levenshtein()、localconv()、ltrim()、md5_file()、md5()、metaphone()、money_format()、nl_langinfo()、n12br()、number_format()、ord()、parse_str()、print()、quoted_printable_decode()、quotemeta()、rtrim()、setlocale()、sha1_file()、sha1()、similar_text()、soundex()、sprintf()、sscanf()、str_ireplace()、str_pad()、str_repeat()、str_replace()、str_rot13()、str_shuffle()、str_split()、str_word_count()、strcasecmp()、strchr()、strcmp()、strcoll()、strcspn()、strip_tags()、stripcslashes()、stripos()、stripslashes()、stristr()、strlen()、strnatcasecmp()、strnatcmp()、strncasecmp()、strpbrk()、strpos()、strrchr()、strrev()、strripos()、strspn()、strstr()、strtok()、strtolower()、strtoupper()、strtr()、substr_compare()、substr_count()、substr_replace()、substr()、trim()、ucfirst()、ucwords()等函数。

2.2字符串截断

如果你做过渗透测试,那字符串截断应该是我们比较熟悉的一个利用方式,特别是在零几年,在利用文件上传漏洞的时候,经常会用到抓包,然后修改POST文件上传数据包里面的文件,在文件名里面加一个%00,用来绕过文件扩展名的检查,又能把脚本写入到服务器中,下面我们就来了解其中的原理吧。

2.2.1%00空字符串截断

字符串截断被利用最多的是在文件操作上面,通常用来利用文件包含漏洞和文件上传漏洞,%00即NULL是被GPC和addslashes()函数过滤掉,所以想要用%00截断需要GPC关闭以及不被addslashes()函数过滤,另外在PHP 5.3之后的版本全面修复了文件名%00截断的问题,这个版本以后也是不能用这种方式截断,为什么PHP在文件操作的时候用%00会截断字符?PHP是基于C语言开发的,%00在URL解码后为,在C语言中是字符串结束符,遇到的时候以为到了字符串结尾,不再读取后面的字符串,自然而然的就理解成了截断。

做一个简单的测试,测试代码(1.php)

php
<?php
include( $_GET['a'].'.php');

在同目录下面新建文件2.txt,内容为输出phpinfo信息代码,当我们请求

url
/1.php?f=2.txt%00

实际上包含了2.txt这个文件,正常执行phpinfo代码。

2.2.2iconv函数字符编码转换截断

iconv()函数用来做字符编码转换,比如从UTF-8转换到GBK,字符集的编码转换总会存在一定的差异性,导致部分编码不能被成功转换,也就是出现常说的乱码。

在使用iconv()函数转码的时候,当遇到不能处理的字符串则后续字符串会不被处理。

我们来做一个简单的测试,测试代码如下:

php
<?php
$a='1'.chr(130).'2';
echo $a;
echo '<br />';
echo iconv("UTF-8", "gbk",$a);
?>

我们执行这段代码的行结果如图2-2所示。

PHP代码审计小技巧(上)

图2-2

可以看到第一次输出$a变量,1和2都被正常输出,当使用iconv()函数转换编码后,从chr(130)字符开始之后的字符串都没有输出,已经被成功截断。经过笔者fuzz测试,当我们文件名中有chr(128)到chr(255)之间都可以截断字符。

这种截断有很多利用常见,下面我们来看一个真实的案例,乌云平台漏洞【建站之星模糊测试实战之任意文件上传漏洞】,漏洞编号 WooYun-2014-4829s,漏洞作者为felixk3y,漏洞发生在/module/mod_tool.php文件第89行起,img_create()函数,代码如下:

php
public function img_create() {
$file_info =& ParamHolder:get('img_name', array(), PS_FILES);
if ($file_info['error'] > 0) {
Notice:set('mod_marquee/msg', __('Invalid post file data!'));
Content:redirect(Html:uriquery('mod_tool', 'upload_img'));
}
if(!preg_match('/.('.PIC_ALLOW_EXT.')$/i', $file_info["name"])){
Notice:set('mod_marquee/msg', __('File type error!'));
Content:redirect(Html:uriquery('mod_marquee', 'upload_img'));
}
if(file_exists(ROOT.'/upload/image/'.$file_info["name"])) {
$file_info["name"] = Toolkit:randomStr(8).strrchr($file_info["name"],".");
}
if (!$this->_savelinkimg($file_info)) {
Notice:set('mod_marquee/msg', __('Link image upload failed!'));
Content:redirect(Html:uriquery('mod_marquee', 'upload_img'));
}

这是一个文件上传的代码,其中此漏洞的关键代码在

php
if (!$this->_savelinkimg($file_info)) {
Notice:set('mod_marquee/msg', __('Link image upload failed!'));
Content:redirect(Html:uriquery('mod_marquee', 'upload_img'));
}

在这里调用_savelinkimg()函数保存文件,跟进该函数,函数代码如下:

php
private function _savelinkimg($struct_file) {
$struct_file['name'] = iconv("UTF-8", "gb2312", $struct_file['name']);
echo $struct_file['name'];
move_uploaded_file($struct_file['tmp_name'], ROOT.'/upload/image/'.$struct_ file['name']);
move_uploaded_file($struct_file['tmp_name'], ROOT.'/upload/image/'.$struct_ file['name']);
return ParamParser:fire_virus(ROOT.'/upload/image/'.$struct_file['name']);
}

代码中:

php
$struct_file['name'] = iconv("UTF-8", "gb2312", $struct_file['name']);

对文件名进行转码,之后:

php
move_uploaded_file($struct_file['tmp_name'], ROOT.'/upload/image/'.$struct_file['name']);

写入文件,这里就出现了我们上面说到的编码转换,最终导致可以上传任意文件。

3.php://输入输出流

提到流,大家会想到水流或者数据流,PHP提供了php://的协议允许访问PHP的输入输出流、标准输入输出和错误描述符,内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。主要提供如下访问方式来使用这些封装器:

txt
php://stdin
php://stdout
php://stderr
php://input
php://output
php://output
php://fd
php://memory
php://temp
php://filter
php://phar
php:zip

使用最多的是php://inputphp://output以及php://filter,其中php://input是可以访问请求的原始数据的只读流。即可以直接读取到POST上没有经过解析的原始数据,但是php://input不能在获取“multipart/form-data”方式提交的数据。我们做一个测试,测试代码如下:

php
<?php
echo file_get_contents("php://input");
?>

当我们用POST提交a=Hello World时,a=Hello World被直接打印出来,如图3-1所示

PHP代码审计小技巧(上)

图3-1

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

php://filter是一个文件操作的协议,可以对磁盘中的文件进行读写操作,效果类似于readfile()、file()和file_get_contents(),它有多个参数可以进行相应的操作,说明如表3-1所示

表3-1

PHP代码审计小技巧(上)

我们来测试使用php://filter写文件,测试代码如下:

php
<?php
file_put_contents("php://filter/write=string.rot13/resource=example.txt","Hello World");

当我们执行代码的时候,会像脚本同目录下写入“example.txt”文件,内容为rot13编码过的“Hello World”,而php://filter还可以用来读文件,如果有远程文件保护漏洞,类似如下的代码:

php
<?php
include($_GET['f']);

正常情况下如果我们直接传入一个文件名,则是会被include函数包含并执行,如果我们想读取Web目录下的PHP文件,则可以通过请求:

php
/2.php?f=php://filter/convert.base64-encode/resource=2.php

来将文件进行Base64编码后输出,输入结果如图3-2所示。

PHP代码审计小技巧(上)

图3-2

4.PHP代码解析标签

PHP有几种解析标签的写法来标识PHP代码,比如最标准的<?php?>,当PHP解析器找到这个标签的时候,就会执行这个标签里面的代码,实际上除了这种写法外还有一些标签

分别如下:

1)脚本标签:

php
<script language="php"></script>

这种方式写法有点像JavaScript,不过也是可以正常解析PHP代码。

2)短标签:<??>,使用短标签前需要在php.ini中设置**short_open_tag=on****,默认是on状态。

3)asp标签:<%%>,在PHP 3.0.4版后可用,需要在php.ini中设置asp_tags=on,默认是off。

因为有的程序在后台配置模板的时候,禁止提交<?php ?>这样的标签来执行PHP代码,但是大部分程序会存在过滤不全的问题,所以这些各式各样的写法常常用于留后门以及绕过Web程序或者waf的防护写入webshell。

我们来测试脚本标签方式,测试代码如下:

php
<script language="php">
phpinfo()
</script>

执行结果如图4-1所示。

PHP代码审计小技巧(上)

图4-1

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

相关推荐: 使用免费工具进行逆向和利用:第11部分

使用免费工具进行逆向和利用:第11部分 原作者:Ricardo Narvaja 翻译作者:梦幻的彼岸 更新日期:2021年11月29日 逐步ROP 通常有工具可以为简单的情况构建ROP。 在困难的情况下,这些工具通常不能解决问题,或者只能部分解决问题,让一个人…

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日03:56:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   PHP代码审计小技巧(上)http://cn-sec.com/archives/692013.html