PHP代码审计之WEB安全系列基础文章(四)之代码执行篇
【如需转载,请详细表明来源,请勿设置原创】
嗨,大家好,我是闪石星曜CyberSecurity创始人Power7089。
欢迎大家扫描下方二维码关注 “闪石星曜CyberSecurity” 公众号,这里专注分享渗透测试,Java代码审计,PHP代码审计等内容,都是非常干的干货哦。
今天为大家带来PHP代码审计基础系列文章第四篇之代码执行篇。
这是【炼石计划@PHP代码审计】知识星球第二阶段的原创基础系列文章,拿出部分课程分享给零基础的朋友学习。本系列原创基础文章涵盖了PHP代码审计中常见的十余种WEB漏洞,是匹夫老师精心的创作,欢迎关注我的公众号跟着一起学习。
【炼石计划@PHP代码审计】是一个系统化从入门到提升学习PHP代码审计的成长型知识星球。这里不仅注重夯实基础,更加专注实战进阶。强烈推荐加入我们,一起来实战提升PHP代码审计。
PHP代码审计之代码执行
1.代码执行原理
为了代码的灵活性与简洁性,会适当调用PHP中的一些代码执行函数来完成一些系统的功能,而代码执行的函数相当于可以直接调用PHP中任意代码来执行,更重要的是代码执行可以通过调用命令执行的函数来执行系统命令,来达到控制后台甚至我们的服务器,这就是我们所说的RCE(远程代码/命令执行漏洞)的由来。
1.1 代码执行示例代码
eval.php
以最常见的一句话木马为例,我们通过代码执行函数eval()
来进行演示,假设我们eval()
中的参数可控,我们就可以控制我们传入的参数来造成RCE。
1 |
|
由于这里的value
参数可控,我们传入PHP中的phpinfo()
函数即可输出PHP配置信息。值得注意的是eval()中传入的函数必须要用分号来结尾,否则会报出致命性错误,导致执行中断。
也可以传入我们PHP中命令执行的函数来执行系统命令,这就是我们上篇所说的代码执行也可以当作命令执行的原理。
在**eval()**函数中可以传入多个函数来执行,每个函数也必需要用分号进行分割。
2.代码执行相关函数
eval()
将传入的字符串当作PHP代码来执行,代码示例如上图。
assert()
其与eval()
类似,传入的内容会被当做代码来执行,不同的是eval中传入的值可以不用分号来结尾。
示例代码:
1 |
|
同样也可以调用命令执行函数来执行系统命令
assert()函数也可进行拆分调用,这是与eval()
函数很大的不同点,有时可以用来绕过一些WAF的防御。
示例代码:
1 |
|
preg_replace()
该函数有三个参数,用于执行一个正则表达式的搜索和替换
搜索 subject
中匹配 pattern
的部分, 如果匹配成功以 replacement
进行替换
语法:
1 |
|
1 |
|
示例代码:
1 |
|
由于使用/e
传入的phpinfo()
会被当做代码来执行
create_function()
PHP中的匿名函数,相当于定义了一个没有名字的函数,该函数直接用变量
进行调用。
第一个参数参数是函数传递的参数,第二个参数相当于函数中的函数体。
语法:
1 |
|
代码示例:
第二个参数会在内部执行eval()
,在这里也就是执行后面的return
语句
1 |
|
上面的代码可以等价于以下代码只不过匿名函数是用变量来调用的,而非匿名变量是通过函数名来调用的。
1 |
|
显然他们的计算结果是相同的
代码示例:
当create_function()
中传入的参数可控时就会造成代码执行
1 |
|
由于$a
在被调用时才会触发执行匿名函数,所以我们通过传入的参数闭合该匿名函数的}
,就造成了代码执行,实际情况中要根据具体闭合条件进行闭合,从而使我们想要执行的代码独立出来。
// 注释当前行代码(单行注释)
/* 注释之后所有代码(多行注释)
array_map()
array_map() 函数返回用户自定义函数作用后的数组。回调函数接受的参数数目应该和传递给 array_map() 函数的数组数目一致。
语法:
1 |
|
代码示例:
array_map()
将调用sum_num()
函数,去执行该函数,这也就是上面所说的函数回调。且后面传入的参数要与回调的函数参数数目保持一致。
1 |
|
代码示例:
当传入的参数可控时就会造成代码执行
1 |
|
当我们传入assert,array_map()
函数就会回调assert()
函数去执行我们后面传入的参数phpinfo()
,相当于assert(phpinfo())
call_user_func()
第一个参数作为回调函数调用, 其余参数是回调函数的参数:
语法:
1 |
|
代码示例:
第一个参数welcome
作为回调函数进行调用,后面则需要传入我们回调函数所需的参数,如下面的$city
。
1 |
|
示例代码:
当我们传入的参数可控时就会造成RCE
1 |
|
call_user_func_array()
把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入,与call_user_func()
函数不同的是,该函数传入的参数是以数组的形式传入的。
语法:
1 |
|
示例代码:
这里需要注意的是参数必须以数组形式传入,如下面的$a
。
1 |
|
示例代码:
当传入的参数可控时就会造成RCE
1 |
|
这里的arg2
传入的参数就是以数组形式就行传递的
如果直接不以数组形式就行传递就会导致致命错误
array_filter()
把输入数组中的每个键值传给回调函数,与call_user_func_array()
不同的是,该函数的第一个参数为回调函数的参数,而第二个参数则是传入的回调函数。
代码示例:
当传入的参数可控时就会造成RCE
1 |
|
ob_start()
用于打开输出控制缓冲,如果参数可控也可造成RCE。
1 |
|
usort()
使用用户自定义的比较函数对数组中的元素进行排序,该函数第二个参数是用户自定义的回调函数。
语法:
1 |
|
代码示例:
1 |
|
回调用户自定义my_sort()
函数对$a
数组中的值进行排序
代码示例:
如果usort()
函数参数可控,也可能可造成RCE。PHP版本>=5.6可实现
1 |
|
array_walk()
使用用户自定义函数对数组中的每个元素做回调处理,该函数的第二个参数也为回调的自定义函数。
语法:
1 |
|
代码示例:
1 |
|
回调上面的myfunction()
函数,对$a
赋值的键值对进行处理。
代码示例:
如果array_walk()
函数参数可控,也可能可造成RCE
1 |
|
由于传入的参数为数组,所以形参需在后面加[]
动态函数
PHP函数直接由字符串拼接,且拼接内容可控就会导致RCE
示例代码:
1 |
|
这种动态函数代码执行的方式相信大家很好理解,传入的第一个参数则为函数名,而第二个参数则传入参数值。如下面的system
为函数,calc.exe
为函数值,从而拼接后执行system(calc.exe);
弹出计算器。
3.代码执行代码审计总结
1 |
|