pickle反序列化学习
前置知识
详细前置知识可以看官方文档https://docs.python.org/zh-cn/3/library/pickle.html
Pickle–Python对象反序列化
Pickle
1 | pickle是python里一个可以对一个 Python 对象结构的二进制序列化和反序列化的模块。 |
pickle序列化和反序列化
对象 <–> 二进制字节流
1 | import pickle |
pickle.dumps将对象序列化为二进制字节流
pickle.loads将二进制字节流化为对象
Pickle反序列化漏洞
成因:就是在二进制字节流上做手脚 就像php反序列化我们传入序列化后的字符串来达到我们想要的目标
1 | import pickle |
特殊函数__reduce__
return 一个元组 第一个是可调用对象 第二个是一个参数元组
在pickle.loads的时候就会执行os.system(“whoami”) 那就成功执行任意python代码了
Pickle工作原理
独立的栈语言,由一串串opcode组成。语言解析依靠Pickle Virtual Machine(PVM)进行。
- 指令处理器:从流中读取
opcode
和参数,并对其进行解释处理。重复这个动作,直到遇到 . 这个结束符后停止。 最终留在栈顶的值将被作为反序列化对象返回。 - stack:由 Python 的
list
实现,被用来临时存储数据、参数以及对象。 - memo:由 Python 的
dict
实现,为 PVM 的整个生命周期提供存储。
常用opcode指令
指令 | 描述 | 具体写法 | 栈上的变化 |
---|---|---|---|
c | 获取一个全局对象或import一个模块 | c[module]\n[instance]\n | 获得的对象入栈 |
o | 寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象) | o | 这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈 |
i | 相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象) | i[module]\n[callable]\n | 这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈 |
N | 实例化一个None | N | 获得的对象入栈 |
S | 实例化一个字符串对象 | S’xxx’\n(也可以使用双引号、'等python字符串形式) | 获得的对象入栈 |
V | 实例化一个UNICODE字符串对象 | Vxxx\n | 获得的对象入栈 |
I | 实例化一个int对象 | Ixxx\n | 获得的对象入栈 |
F | 实例化一个float对象 | Fx.x\n | 获得的对象入栈 |
R | 选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数 | R | 函数和参数出栈,函数的返回值入栈 |
. | 程序结束,栈顶的一个元素作为pickle.loads()的返回值 | . | 无 |
( | 向栈中压入一个MARK标记 | ( | MARK标记入栈 |
t | 寻找栈中的上一个MARK,并组合之间的数据为元组 | t | MARK标记以及被组合的数据出栈,获得的对象入栈 |
) | 向栈中直接压入一个空元组 | ) | 空元组入栈 |
l | 寻找栈中的上一个MARK,并组合之间的数据为列表 | l | MARK标记以及被组合的数据出栈,获得的对象入栈 |
] | 向栈中直接压入一个空列表 | ] | 空列表入栈 |
d | 寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对) | d | MARK标记以及被组合的数据出栈,获得的对象入栈 |
} | 向栈中直接压入一个空字典 | } | 空字典入栈 |
p | 将栈顶对象储存至memo_n | pn\n | 无 |
g | 将memo_n的对象压栈 | gn\n | 对象被压栈 |
0 | 丢弃栈顶对象 | 0 | 栈顶对象被丢弃 |
b | 使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置 | b | 栈上第一个元素出栈 |
s | 将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为key)中 | s | 第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新 |
u | 寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中 | u | MARK标记以及被组合的数据出栈,字典被更新 |
a | 将栈的第一个元素append到第二个元素(列表)中 | a | 栈顶元素出栈,第二个元素(列表)被更新 |
e | 寻找栈中的上一个MARK,组合之间的数据并extends到该MARK之前的一个元素(必须为列表)中 | e | MARK标记以及被组合的数据出栈,列表被更新 |
1 | import pickle |
运行以上代码就会执行whoami命令
pickletools
用这个模块可以把opcode转换成我们易读的形式
1 | import pickletools |
得
1 | 0: c GLOBAL 'os system' |
Opcode漏洞利用
命令执行
opcode可以执行多个命令,可以通过手写的方式来达到。
opcode中,**.**是程序结束的标志,我们可以通过去掉.将两个字节流拼接在一起。
1 | import pickle |
注意!
1 | 部分Linux系统下和Windows下的opcode字节流并不兼容,比如Windows下执行系统命令函数为os.system(),在部分Linux下则为posix.system()。 |
R
i
o
这是三个执行函数的字节码
R
1 | opcode1=b'''cos |
i
1 | opcode2=b'''(S'whoami' |
o
1 | opcode3=b'''(cos |
变量覆盖
在session或token中,由于需要存储一些用户信息,所以我们常常能够看见pickle的身影。程序会将用户的各种信息序列化并存储在session或token中,以此来验证用户的身份。所以pickle可以进行session伪造,来变量覆盖。
实验:
1 | #test.py |
1 | #secret.py |
这样就可以成功覆盖原来的secret
pickle暂时先学到这里吧笔者觉得学再多的理论还是得应用,如果以后有什么新的东西就再来补坑🤭
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Sauy's corner!