PyYAML反序列化漏洞
PyYAML反序列化漏洞
关于yaml的基本知识可以到菜鸟教程学习
yaml语言我老是在docker-compsoe.yml见到它,像这样
下面就开始简单认识一下这个玩意儿,以及其中的反序列化漏洞利用
yaml的基本语法
大小写敏感
使用空格代替tab键缩进表示层级,对齐即可表示同级
'#'注释内容
在同一个yml文件中用
------
隔开多份配置
!!
表示强制类型转换
yaml的数据类型
YAML 对象
键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
对象键值对使用冒号结构表示 key: value,冒号后面要加一个空格(这类用":"分隔的数据转化为python格式就是字典):
YAML 数组
一组按次序排列的值,又称为序列(sequence) / 列表(list)
以 - 开头的行表示构成一个数组("-"后携带的数据转化为python格式就是列表):
YAML 纯量
单个的、不可再分的值
字符串、布尔值、整数、浮点数、Null、时间、日期
强制类型转换
yaml本身支持强制类型转化
用特有的yaml标签来指定转化的类型
像强制转化为str类型就是!!str
如下
结果
原本整数类型的321被转换为str类型,字符串类型的123被转换为int类型
pyyaml下支持所有yaml标签转化为python对应类型:
最后有五个功能强大的yaml标签,支持转化为指定的python模块,类,方法以及对象实例
Pyyaml<=5.1
在Pyyaml提供以下两类方法来实现python和yaml两种语言格式的互相转化
python-> yaml
load(data)#加载单个 YAML 配置
load(data, Loader=yaml.Loader)#指定加载器有BaseLoader、SafeLoader
load_all(data)#加载多个 YAML 配置
load_all(data, Loader=yaml.Loader)#指定加载器
yaml.load()
方法的作用是将yaml类型数据转化为python对象包括自定义的对象实例、字典、列表等类型数据
Loader就是用来指定加载器
BaseConstructor
:最最基础的构造器,不支持强制类型转换
SafeConstructor
:集成 BaseConstructor,强制类型转换和 YAML 规范保持一致,没有魔改
Constructor
:在 YAML 规范上新增了很多强制类型转换(5.1以下默认此加载器,很危险)
接收的data参数可以是yaml格式的字串、Unicode字符串、二进制文件对象或者打开的文本文件对象
yaml -> python
yaml.dump(data)
dump接收的参数就是python对象包括对象实例、字典、列表等类型数据
dump后python的对象实例转化最终是变成一串yaml格式的字符,所以这种情况我们愿称之为序列化,反之load就是在反序列化
五个complex标签的认识及利用
!!python/object/apply
通过调试,进入yaml模块源码yaml/constructor.py中,找到!!python/object/apply
标签的处理函数,construct_python_object_apply
,如下:
然后会调用make_python_instance
方法
又进入了find_python_name
方法,通过__import__
将模块导入进来
经过测试,针对!!python/object/apply
标签的payload如下
!!python/object/new
constructor.py中也能找到处理函数
从代码可以看出!!python/object/new
标签最终也是调用construct_python_object_apply
方法
尽管newobj的值是Ture,但是在测试之后发现并不影响利用
原理跟上面一样,最终进入了find_python_name
方法,通过__import__
将模块导入进来
针对 !!python/object/new
标签的payload如下:
!!python/object
constructor.py中也能找到!!python/object
标签的处理函数:
可以看到也是调用了make_python_instance
方法
但是有一个致命问题就是没有像前面的调用一样,把命令作为参数传进去
在这里参数为空,命令就无法执行
!!python/module
该标签在constructor.py中处理函数
这里调用了find_python_module
方法,跟find_python_name
方法很像,返回的结果是模块名而已
这里没有任何可以对命令参数处理的地方,跟上一个其实差不多
!!python/name
该标签在constructor.py中处理函数
这里也是跟!!python/module
一样陷入窘境
针对上面
这三个不能直接执行命令的标签,条件允许其实有其他办法
原理
利用现有文件上传或者写文件的功能,传入一个写入命令执行代码的文件
将文件名写入标签中,当该标签被反序列化时,就可以顺利导入该文件作为模块,执行当中的命令
利用方式
文件名yaml_test.py
如果在另一文件simple.py中,依次运行以下load代码
都能成功弹出计算器
当然!!python/object/new
和 !!python/object/apply
也可以用这种方式实现利用
以上要求是在同一目录下
如果不在同一目录下怎么办
好比如这种情况
那payload稍作修改,在文件名前加入目录名可
当然文件名写成__init__.py
将会更简单
payload只需目录即可
而且apply和new两个标签也可以构造利用了
漏洞的修复
大于5.1的版本,打了补丁
通过调试发现
find_python_name方法(还有find_python_mdule方法也一样)增加了一个默认unsafe为false的值
就无法直接__import__
,最终会报错
接下来执行这一段
PyYAML >5.1
在PyYAML>=5.1版本中,提供了以下方法用于加载yaml语言:
load(data) [works under certain conditions]
load(data, Loader=yaml.Loader) #loader可选择BaseLoader、SafeLoader、FullLoader、UnsafeLoader
load_all(data) [works under certain condition]
load_all(data, Loader=yaml.Loader) #loader可选择BaseLoader、SafeLoader、FullLoader、UnsafeLoader
full_load(data)
full_load_all(data)
unsafe_load(data)
unsafe_load_all(data)
在5.1之后的yaml中load函数被限制使用了,会被警告提醒加上一个参数 Loader
针对不同的需要,选择不同的加载器,有以下几种加载器
BaseConstructor:仅加载最基本的YAML
SafeConstructor:安全加载Yaml语言的子集,建议用于加载不受信任的输入(safe_load)
FullConstructor:加载的模块必须位于
sys.modules
中(说明程序已经 import 过了才让加载)。这个是默认的加载器。UnsafeConstructor(也称为Loader向后兼容性):原始的Loader代码,可以通过不受信任的数据输入轻松利用(unsafe_load)
Constructor:等同于UnsafeConstructor
如果说指定的加载器是UnsafeConstructor
或者Constructor
,那么利用方式就照旧
Fullloader加载模式的对漏洞利用的限制
-
如果不执行只是为了单纯导入模块,那么需要
sys.modules
字典中有我们的模块,否则报错报错内容:yaml.constructor.ConstructorError: while constructing a Python object
module 'subprocess' is not imported例如
-
如果要执行,那么
sys.modules
字典中要有利用模块,并且加载进来的module.name
必须是一个类而不能是方法,否则就会报错报错内容:yaml.constructor.ConstructorError: while constructing a Python instance
expected a class, but found <class 'builtin_function_or_method'>
认识builtins模块
builtins
是python的内建模块,所谓内建模块就是你在使用时不需要import
,在python启
动后,在没有执行程序员编写的任何代码前,python会加载内建模块中的函数到内存中。
而且我发现在find_python_name
处理不带.
,也就是module为空的情况下,自动默认module为builtins
,并且该模块是在sys.modules
中的
用下面程序可以看看该模块下有哪些成员

有足足153个,稍改一下程序排除掉方法,筛选出类成员
payload
我们可以用python的内置函数eval
(或者exec
)来执行代码,用map
来触发函数执行,用tuple
将map对象转化为元组输出来(当然用list
、frozenset
、bytes
都可以),用python写出来如下
变为yaml
除此之外网上还有很多大佬有其他的payload
局限
我经过测试pyyaml到5.4之后,上面的payload基本用不了
参考文章
SecMap - 反序列化(PyYAML) - Tr0y's Blog
__EOF__

本文链接:https://www.cnblogs.com/damoxilai/p/16707055.html
关于博主:网安小萌新一名,希望从今天开始慢慢提高,一步步走向技术的高峰!
版权声明:达摩西来
声援博主:达摩西来
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!