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'] );
写入文件,这里就出现了我们上面说到的编码转换,最终导致可以上传任意文件。