代码执行漏洞
当前各类网络业务应用系统的功能越来越多,给用户带来了各种业务便利和新的功能体验。在系统设计和开发过程中,软件工程师关注更多的是功能的可用性、易用性等方面,并没有注意程序代码所用的函数、实现的功能、执行的流程等方面是否安全。本节将从程序的函数参数、执行流程方面分析代码执行漏洞的原理。
代码执行漏洞是指用户通过客户端提交可执行命令代码,由于服务端没有针对函数的参数做有效过滤,导致系统执行非法命令代码。
代码执行函数
所谓代码执行,就是通过参数、变量等指定可执行的代码,参数和变量中存储的将不再是数字、字符串等数值,而是可以执行的脚本代码或二进制代码。代码执行函数则是指可以实现代码执行的函数。在PHP语言和框架中,有很多代码执行函数,其参数可以接收代码值并触发执行该代码。一旦将有风险的代码通过函数参数传递给这些函数,就很容易执行恶意操作,触发安全漏洞。例如eval()、assert()、preg_replace()、call_user_func()、call_user_func_array()、create_function()、array_map()等函数,最常见的如动态函数$a($b)等,代码执行漏洞就是因为上述这些函数的参数是用户可控的,而服务器端没有针对这些可控的参数进行有效的过滤,导致可控的参数被赋值恶意代码,进入命令执行函数被执行。
代码执行漏洞的另一种常见利用方式是通过文件包含函数,例如include()、include_once()、require_once()、file_get_contents()、file_put_contents()、fwrite()等,将恶意代码嵌入程序当前的执行空间,从而触发代码执行漏洞。
eval() 函数
eval()函数把参数的字符串值当作PHP代码来执行,当字符串是合法的PHP代码且以分号作为行结尾时,代码在服务端的运行效果与通过该函数触发执行效果相同。
示例
漏洞代码如下:
1 |
|
这段代码的作用是将get请求到的字符串转换成小写字母并输出
正常请求如下:

但是我们可以通过构造闭合括号的双引号进行代码注入攻击

将payload带入源代码(传参时会自动进行转义):
1 | eval("\$ret=strtolower(\"A\");phpinfo();(\"A\");"); |
因为eval()函数会以分号为分隔执行代码,所以执行的PHP代码如下:
1 | $ret=strtolower("A"); |
触发嵌入的phpinfo()代码便能成功执行
注:eval 是一个语言构造器而不是一个函数,不能被可变函数调用
assert() 函数
PHP语言的assert()函数判断一个表达式是否成立,返回true或false。当其参数为多个字符组成的字符串时,该函数首先将字符串当作PHP代码执行,并将代码执行的返回结果作为表达式判断是否有效。
与eval()不同的是assert()只能执行一条php代码,所以不需要增加分号。
示例
漏洞代码如下:
1 |
|

注:自PHP_8 起
assert()不再支持执行代码了
preg_replace() 函数
preg_replace()函数有三个参数:
第一个参数是要搜索的模式,可以是字符串或一个字符串数组;
第二个参数是用于替换的字符串或字符串数组;
- 第三个参数pattern存在的/e模式修饰符且PHP配置的
magic_quotes_gpc=Off时,函数会将第二个参数值当作PHP代码进行解析执行。该函数的三个参数都是用户可控的,因此,函数具备成为代码执行漏洞的条件。
示例
漏洞代码如下:
1 |
|
这段代码的作用是将匹配get传递sub参数中的数字部分替换为get传递的rp参数的值
正常请求如下:

但是我们可以把替换的值改为执行的PHP代码,成功触发漏洞

注:自PHP5.5.0版本起
/e修饰符被弃用,无法使用该函数执行代码
create_function() 函数
create_function()函数主要用来创建匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给create_function()执行任意命令。
示例
漏洞代码如下:
1 |
|
执行函数为:
1 | function func1($a,$b) { |
这段代码的作用是将两个get请求到的字符串拼接并输出
正常请求如下:

我们可以通过闭合函数前面与后面的大括号,来达到代码注入
payload:
1 | ?a=abc&b=ddd;}phpinfo();{123 |

注入后执行的函数:
1 | function func1($a,$b) { |
array_map() 函数
array_map()函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。它的第一个参数是回调函数,第二个参数是要处理的数组。回调函数接受的参数数目应该和传递给array_map()函数的数组数目一致。
示例
漏洞代码如下:
1 |
|
payload:
1 | ?func=phpinfo&cmd=1 |
array_filter() 函数
array_filter()函数用回调函数过滤数组中的值。该函数把输入数组中的每个键值传给回调函数。如果回调函数返回 true,则把输入数组中的当前键值返回结果数组中。数组键名保持不变。
示例
漏洞代码如下:
1 |
|
payload:
1 | ?func=phpinfo&cmd=1 |
call_user_func() / call_user_func_array() 函数
call_user_func() —— 把第一个参数作为回调函数调用,其余参数是回调函数的参数。
call_user_func_array() —— 调用回调函数,并把一个数组参数作为回调函数的参数。
示例
漏洞代码如下:
call_user_func():
1 |
|
call_user_func_array():
1 |
|
payload:
1 | ?cmd=phpinfo |
usort() / uasort() / uksort() 函数
usort()通过用户自定义的比较函数对数组进行排序。
uasort()使用用户自定义的比较函数对数组中的值进行排序并保持索引关联 。
示例
漏洞代码如下:
usort():
1 |
|
uasort():
1 |
|
uksort():
1 |
|
payload:
1 | ?str1=1&str2=phpinfo() |
其他代码执行函数
- array_reduce()
- array_diff_uassoc()
- array_diff_ukey()
- array_udiff()
- array_udiff_assoc()
- array_udiff_uassoc()
- array_intersect()
- 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_end_namespace_decl_handler()
- xml_set_external_entity_ref_handler()
- xml_set_notation_decl_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()
动态函数 $a($b)
由于PHP的特性原因,PHP的函数支持直接由拼接的方式调用,这直接导致了PHP在安全上的控制有加大了难度。不少知名程序中也用到了动态函数的写法,这种写法跟使用call_user_func()的初衷一样,用来更加方便地调用函数,但是一旦过滤不严格就会造成代码执行漏洞。
示例
漏洞代码如下:
1 |
|
payload:
1 | ?a=assert&b=phpinfo() |

文件包含函数导致代码执行
当PHP配置中的allow_url_include=On,allow_url_fopen=On,且PHP版本高于5.2时,会存在文件包含函数导致的代码执行漏洞。
示例
1 |
|
payload:
1 | ?test=data:text/plain,<?php phpinfo(); ?> |

参考资料
- 曹玉杰,王乐,李家辉,孔韬循 编著《Web代码安全漏洞深度剖析》
- 代码执行漏洞 - LeeeBoom - 博客园 (cnblogs.com)
- PHP 代码注入漏洞_Hi~ 土拨鼠-CSDN博客_php代码注入漏洞
- PHP代码执行函数总结 - Bypass - 博客园 (cnblogs.com)、assert()、preg_replace()、create )
- PHP create_function()代码注入【图文】_605939578_51CTO博客