8.2 神奇的字符串

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

8.2.1 字符处理函数报错信息泄露

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


E_WARNING

E_PARSE

E_NOTICE

E_CORE_ERROR

E_CORE_WARNING

E_COMPILE_ERROR

E_COMPILE_WARNING

E_USER_ERROR

E_USER_WARNING

E_USER_NOTICE

E_STRICT

E_RECOVERABLE_ERROR

E_ALL

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

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

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


< php

echo trim
$_GET['a'] );

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

图 8-2

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

addcslashes()、addslashes()、bin2hex()、chop()、chr()、chunk_split()、convert_cyr_string()、convert_uudecode()、convert_uuencode()、count_chars()、crc32()、crypt()、echo()、explode()、fprintf()、get_html_translation_table()、hebrev()、hebrevc()、html_entity_decode()、htmlentities()、htmlspecialchars_decode()、htmlspecialchars()、implode()、join()、levenshtein()、localeconv()、ltrim()、md5_file()、md5()、metaphone()、money_format()、nl_langinfo()、nl2br()、number_format()、ord()、parse_str()、print()、printf()、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()、strncmp()、strpbrk()、strpos()、strrchr()、strrev()、strripos()、strrpos()、strspn()、strstr()、strtok()、strtolower()、strtoupper()、strtr()、substr_compare()、substr_count()、substr_replace()、substr()、trim()、ucfirst()、ucwords()、vfprintf()、vprintf()、vsprintf()、wordwrap()、strtolower()、strtoupper()、ucfirst()、ucwords()、ucfirst()、ucwords(),等等函数。

8.2.2 字符串截断

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

8.2.2.1 %00空字符截断

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

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


< php

include
$_GET['f'].'.php' );

在同目录下面新建文件2.txt,内容为输出phpinfo信息代码,当我们请求/1.php?f=2.txt%00时,实际上包含了2.txt这个文件,正常执行phpinfo代码。

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

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

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


< php

$a='1'.chr
130 .'2'

echo $a


echo '<br />'


echo iconv
"UTF-8" "gbk" $a );

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

图 8-3

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

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


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' ));

        }

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


if (! $this->_savelinkimg $file_info )) {

    Notice
:: set 'mod_marquee/msg' __ 'Link image upload failed ' ));

    Content
:: redirect Html :: uriquery 'mod_marquee' 'upload_img' ));

}

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


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'] );

    return ParamParser
:: fire_virus ROOT.'/upload/image/'.$struct_file['name'] );

}

代码中:


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

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


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

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