5.2.1 挖掘经验
eval() 和 assert() 函数导致的代码执行漏洞大多是因为载入缓存或者模板以及对变量的处理不严格导致,比如直接把一个外部可控的参数拼接到模板里面,然后调用这两个函数去当成 PHP 代码执行。
preg_replace() 函数的代码执行需要存在/e 参数,这个函数原本是用来处理字符串的,因此漏洞出现最多的是在对字符串的处理,比如 URL、HTML 标签以及文章内容等过滤功能。
call_user_func() 和 call_user_func_array() 函数的功能是调用函数,多用在框架里面动态调用函数,所以一般比较小的程序出现这种方式的代码执行会比较少。array_map() 函数的作用是调用函数并且除第一个参数外其他参数为数组,通常会写死第一个参数,即调用的函数,类似这三个函数功能的函数还有很多。
除了上面这些函数导致的代码执行漏洞,还有一类非常常见的是动态函数的代码执行,比如下面这样的写法:
$_GET ( $_POST["xx"] )
基于这种写法变形出来的各种异形,经常被用来当作 Web 后门使用,可以看到这里的 PHP 函数是从$_GET 变量当做字符串传入进来的,这是 PHP 的一个特性。
5.2.1.1 代码执行函数
PHP 代码执行有多种利用方式,但目前见得最多的还是由于函数的使用不当导致的,这类函数还不少,有 eval()、assert()、preg_replace()、call_user_func()、call_user_func_array() 以及 array_map() 等,下面我们来详细看看各自产生漏洞的原理和利用方式吧。
1.eval 和 assert 函数
这两个函数原本的作用就是用来动态执行代码,所以它们的参数直接就是 PHP 代码,我们来看看是怎么使用的,测试代码如下:
<?php $a='aaa' ; $b='bbb' ; eval ( '$a=$b ; ' ); var_dump ( $a );
测试截图如图 5-7 所示。
图 5-7
2.preg_replace 函数
preg_replace 函数的作用是对字符串进行正则处理,我们在上面的挖掘经验已经介绍了,它经常会出现漏洞的位置,下面我们来看看它在什么情况下才会出现代码执行漏洞。
它的参数和返回如下:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [ , int $limit = -1 [ , int &$count ]] )
这段代码的含义是搜索$subject 中匹配$pattern 的部分,以$replacement 进行替换,而当$pattern 处即第一个参数存在 e 修饰符时,$replacement 的值会被当成 PHP 代码来执行,我们来看一个简单的例子(1.php)。
<?php preg_replace ( "/\[ ( .* ) \]/e" , '\\1' , $_GET['str'] );? >
正则的意思是从$_GET['str']变量里搜索中括号[]中间的内容作为第一组结果,preg_replace() 函数第二个参数为'\\1'代表这里用第一组结果填充,这里是可以直接执行代码的,所以当我们请求/1.php?str=[phpinfo()]时,则执行代码 phpinfo(),结果如图 5-8 所示。
图 5-8
3.调用函数过滤不严
call_user_func() 和 array_map() 等数十个函数有调用其他函数的功能,其中的一个参数作为要调用的函数名,那如果这个传入的函数名可控,那就可以调用意外的函数来执行我们想知道的代码,也就是存在代码执行漏洞。
我们用 call_user_func() 函数来举例,函数的作用是调用函数并且第二个参数作为要调用的函数的参数,官方说明如下:
mixed call_user_func ( callable $callback [ , mixed $parameter [ , mixed $... ]] )
该函数第一个参数为回调函数,后面的参数为回调函数的参数,测试代码如下:
<?php $b="phpinfo () " ; call_user_func ( $_GET['a'] , $b );? >
当请求 1.php?a=assert 的时候,则调用了 assert 函数,并且将 phpinfo() 作为参数传入,如图 5-9 所示。
图 5-9
同类的函数还有如下这些:
call_user_func ()、 call_user_func_array ()、 array_map () usort ()、 uasort ()、 uksort ()、 array_filter () array_reduce ()、 array_diff_uassoc ()、 array_diff_ukey () array_udiff ()、 array_udiff_assoc ()、 array_udiff_uassoc () array_intersect_assoc ()、 array_intersect_uassoc () array_uintersect ()、 array_uintersect_assoc () array_uintersect_uassoc ()、 array_walk ()、 array_walk_recursive () xml_set_character_data_handler ()、 xml_set_default_handler () xml_set_element_handler ()、 xml_set_end_namespace_decl_handler () xml_set_external_entity_ref_handler ()、 xml_set_notation_decl_handler () xml_set_processing_instruction_handler () xml_set_start_namespace_decl_handler () xml_set_unparsed_entity_decl_handler ()、 stream_filter_register () set_error_handler ()、 register_shutdown_function ()、 register_tick_function ()
5.2.1.2 动态函数执行
由于 PHP 的特性原因,PHP 的函数可以直接由字符串拼接,这导致了 PHP 在安全上的控制又加大了难度,比如增加了漏洞数量和提高了 PHP 后门的查杀难度。要找漏洞就要先理解为什么程序代码要这么写,不少知名程序中也用到了动态函数的写法,这种写法跟使用 call_user_func 的初衷是一样的,大多用在框架里,用来更简单更方便地调用函数,但是一旦过滤不严格就会造成代码执行漏洞。
PHP 动态函数写法为“变量(参数)”,我们来看一个动态函数后门的写法:
<?php $_GET['a'] ( $_GET['b'] );? >
代码的意思是接收 GET 请求的 a 参数,作为函数,b 参数作为函数的参数。当请求 a 参数值为 assert,b 参数值为 phpinfo() 的时候打印出 phpinfo 信息,请求如下:
http : //127.0.0.1/test/1.php?a=assert&b=phpinfo ()
执行结果如图 5-10 所示。
图 5-10
要挖掘这种形式的代码执行漏洞,需要找可控的动态函数名。
5.2.1.3 Thinkphp 代码执行漏洞分析
要分析代码执行的案例,在 Java 界来说就是 Struts2 的代码执行了,不过在 PHP 领域,国内影响比较大的代码执行漏洞非 thinkphp 框架 URL 解析的代码执行漏洞莫属,这个漏洞的影响力,做渗透测试的安全人员应该比较清楚,在国内还是会经常遇到这个漏洞的。
下面我们来分析这个漏洞的原理,thinkphp 框架的 GET 参数以 index.php/a/b/c 的形式传递,程序在获取参数之前需要先解析 URL,漏洞就发生在解析 URL 的地方,官方补丁对比地址如下:
https://code.google.com/p/thinkphp/source/diff?spec=svn2904&r=2838&format=side&path=/trunk/ThinkPHP/Lib/Core/Dispatcher.class.php。
漏洞出现在/ThinkPHP/Lib/Core/Dispatcher.class.php 文件的 dispatch() 函数,为了节省篇幅,这里只贴出关键代码:
$depr = C ( 'URL_PATHINFO_DEPR' ); if (! empty ( $_SERVER['PATH_INFO'] )) { tag ( 'path_info' ); if ( C ( 'URL_HTML_SUFFIX' )) { $_SERVER['PATH_INFO'] = preg_replace ( '/\.'.trim ( C ( 'URL_HTML_SUFFIX' ), '.' ) .'$/i' , '' , $_SERVER['PATH_INFO'] ); } if (! self :: routerCheck ()) { // 检测路由规则 如果没有则按默认规则调度 URL $paths = explode ( $depr , trim ( $_SERVER['PATH_INFO'] , '/' )); /***** 省略 ****/ $var[C ( 'VAR_ACTION' ) ] = array_shift ( $paths ); // 解析剩余的 URL 参数 $res = preg_replace ( '@ ( \w+ ) '.$depr.' ( [^'.$depr.'\/]+ ) @e' , '$var [\'\\1\']="\\2" ; ' , implode ( $depr , $paths )); $_GET = array_merge ( $var , $_GET ); }
可以看到这里使用 preg_replace() 函数,我们在前面已经介绍了关于这个函数的代码执行漏洞,这个函数里面的变量为$depr 和$paths,代码中的这句话:
$depr = C ( 'URL_PATHINFO_DEPR' );
是取得配置中的参数分隔符,下面这句话:
$paths = explode ( $depr , trim ( $_SERVER['PATH_INFO'] , '/' ));
则是从$_SERVER['PATH_INFO']中以$depr 为分隔符分割后的数组,而后面又用 implode() 函数还原成字符串才带入 preg_replace() 函数,关键在于:
'$var[\'\\1\']="\\2" ; '
代码的意思是,把正则匹配出来的参数 1 初始化到$var 变量中,并且赋值为参数 2 的值,问题是这段代码在赋值的时候使用的是双引号("),在 PHP 中,如果字符串使用双引号括起来,中间的变量是会正常解析的,如:
<?php $a=1 ; echo "$a" ;? >
会输出 1,而不是$a,利用这个特性,再结合 PHP 可变变量即可执行任意代码,最终 EXP 为:
/index.php/module/action/param1/${@phpinfo () }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论