###5.3 代码执行漏洞
简述
代码执行漏洞是指应用程序本身过滤不严,用户可以通过请求将代码注人到应用中执行。说得好理解一点,类似于SQL注人漏洞,可以把SQL语句注入到SQL服务执行,而 PHP 代码执行漏洞则是可以把代码注人应用中最终到 WebServer 去执行。这样的漏洞如果没有特殊的过滤,相当于直接有一个 Web 后门存在,该漏洞主要由 eval()、assert()、 preg_replace()、call_user_fune()、 call_user_func_array()、 array_map() 等函数的参数过滤不严格导致,另外还有 PHP 的动态函数($a($b))也是目前出现比较多的。
实例列举
eval() 和 assert() 函数导致的代码执行漏洞大多是因为载人缓存或者模板以及对变量的处理不严格导致,比如直接把一个外部可控的参数拼接到模板里面,然后调用这两个函数去当成 PHP 代码执行。
preg_replace() 函数的代码执行需要存在/e 参数,这个函数原本是用来处理字符串的,因此漏洞出现最多的是在对字符串的处理,比如 URL、HTML 标签以及文章内容等过滤功能。
call_user_func() 和 call_user_func_array() 函数的功能是调用函数,多用在框架里面动态调用函数,所以一般比较小的程序出现这种方式的代码执行会比较少。array_map() 函数的作用是调用函数并且除第一个参数外其他参数为数组,通常会写死第一个参数,即调用的两数,类似这三个两数功能的两数还有很多。除了上面这些函数导致的代码执行漏洞,还有一类非常常见的是动态函数的代码执行,比如下面这样的写法:
$_GET($_POST[“xx”])
基于这种写法变形出来的各种异形,经常被用来当作 web后门使用,可以看到这里的 PHP 函数是从$_GET 变量当做字符串传人进来的,这是 PHP 的一个特性。
PHP 代码执行有多种利用方式,但目前见得最多的还是由于函数的使用不当导致的,这类函数还不少,有 eval()、assert()、 preg_replace()、 call user_func()、 call_user_func _array() 以及 array_map() 等,下面我们来详细看看各自产生漏洞的原理和利用方式吧。
eval 和 assert 函数
这两个函数原本的作用就是用来动态执行代码,所以它们的参数直接就是 PHP 代码,我们来看看是怎么使用的,测试代码如下:
<?php
$a='aaa';
$b= 'bbb';
eval("$a=$b;";
var_dump($a);
输出结果:
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(),结果如下图所示。
调用函数过滤不严
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() 作为参数传入,如图所示。
同类的函数还有如下这些:
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()、xm1_setexternal_entity_ref_handler()、 xml_set_notation_declhandler()、xml_set_processing_instruction_handler()、xml_setstart namespace_decl_handler()、xml_set_unparsed_entity_decl_handler()、 stream_flter_register()、set_error_handler()、register_shutdown_funetion()、register_tick_function()
动态函数执行
由于 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())
要挖掘这种形式的代码执行漏洞,需要找可控的动态函数名。
漏洞防范
采用参数白名单过滤,在可预测满足正常业务的参数情况下,这是非常实用的方式,这里的白名单并不是说完全固定为参数,因为在 eval()、 assert() 和 preg_replace()函数的参数中大部分是不可预测一字不差的,我们可以结合正则表达式来进行白名单限制。
用代码举例更加清晰易懂,代码如下:
<?php
preg_replace('/(\w+)\|(.*)/ie','$\\1="\\2;"',$_GETI'a']);
?>
这段代码是有问题的,只要提交/1.php?a=b/${@phpinfo()}即可执行 phpinfo() 函数,这时候如果我们知道\2 的值范围为纯数字,只要正则改成(\w+)(\d+) 即可解决执行代码的问题,这只是一种修复方案,最好的方法是:在 $\1=“\2”这里不要用双引号。
更新时间:2023-04-12 16:17