https://www.cnblogs.com/chenhuabin/p/10502096.html
将内存中不容易储存的格式用固定的二进制方式表达出来并可逆的转化回去,调试可看
一般的数据类型可以使用 JSON 模块来序列化,但是如果是 python 特殊拥有的类型则需要用到 pickle
https://blog.51cto.com/u_16213419/7541752
__reduce__
是一个特殊方法,用于对对象进行序列化和反序列化。它允许我们自定义对象在序列化和反序列化过程中的行为。
https://tttang.com/archive/1782/
https://www.cnblogs.com/F12-blog/p/17982311
pickletools 模块可以将 opcode 指令转变成易读的形式:
总结 Pickle 的几个特点:
- 非图灵完备的栈语言,没有运算、循环、条件分支等结构
- 可以实现的操作
- 构造 Python 内置基础类型(
str
,int
,float
,list
,tuple
,dict
) dict
和list
成员的赋值(无法直接取值)- 对象成员的赋值(无法直接取值)
- callable 对象 的调用
- 通过
_Pickler.find_class
导入模块中的某对象,find_class
的第一个参数可以是模块或包,本质是getattr(__import__(module), name)
find_class 的限制GLOBLE:c INST:i STACK_GLOBAL:\x93
会调用find_class
- 版本保持向下兼容,通过 opcode 头解析版本
- 0 号 protocol 使用
\n
作操作数的分割
- 构造 Python 内置基础类型(
通过项目 https://github.com/eddieivan01/pker 来将 python 语句用 AST 抽象语法树 转化为虚拟机代码
什么是 Pickle?
很简单,就是一个 python 的序列化模块,方便对象的传输与存储。但是 pickle 的灵活度很高,可以通过对 opcode 的编写来实现代码执行的效果,由此引发一系列的安全问题
Pickle 使用
举个简单的例子
pickle.dumps (p)
将对象序列化,同理 pickle.loads (opcode)
就是反序列化的过程
注意
值得注意的是在不同平台环境下 pickle 生成的 opcode 是不同的,例如在 windows 和 linux 环境下相同的对象,dumps 下来的 opcode 就不一样
魔术方法__reduce__
object.__reduce__ 是 object 类的一个魔术方法,我们可以通过重写该方法,让该方法在反序列化时按我们的重写的方式执行,python 要求该方法返回一个字符串或元组,如果返回元组 (callable, (param1, param2, )) ,那么每当反序列化时,就会调用 callable (param1, param2, ),我们可以控制 callable 和它的参数来实现代码执行
Pickle 反序列化漏洞利用
很明显在反序列化的过程时执行了 os.system (‘whoami’),这是 pickle 反序列化漏洞的最简单的利用方式,要掌握更加高级的利用手法,我们还得继续深入学习 pickle
Pickle 的工作原理
opcode 的解析依靠 Pickle Virtual Machine (PVM)进行 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。
pickle 协议是向前兼容的,v0 版本的字符串可以直接交给 pickle.loads (),不用担心引发什么意外。下面我们以 v0 版本为例,介绍一下 opcode 指令
常用 opcode 指令介绍
opcode | 描述 | 具体写法 | 栈上的变化 | memo 上的变化 |
---|---|---|---|---|
c | 获取一个全局对象或 import 一个模块(注:会调用 import 语句,能够引入新的包)会加入 self. stack | 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 | 丢弃栈顶对象(self. stack) | 0 | 栈顶对象被丢弃 | 无 |
b | 使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置 | b | 栈上第一个元素出栈 | 无 |
s | 将栈的第一个和第二个对象作为 key-value 对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为 key)中 | s | 第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新 | 无 |
u | 寻找栈中的上一个 MARK,组合之间的数据(数据必须有偶数个,即呈 key-value 对)并全部添加或更新到该 MARK 之前的一个元素(必须为字典)中 | u | MARK 标记以及被组合的数据出栈,字典被更新 | 无 |
a | 将栈的第一个元素 append 到第二个元素 (列表) 中 | a | 栈顶元素出栈,第二个元素(列表)被更新 | 无 |
e | 寻找栈中的上一个 MARK,组合之间的数据并 extends 到该 MARK 之前的一个元素(必须为列表)中 | e | MARK 标记以及被组合的数据出栈,列表被更新 | 无 |
更多的 opcode 指令可以查看 pickle. py 获取
PVM 工作流程
手写 opcode
举个简单的 opcode 例子:
程序:
pickletools 模块可以将 opcode 指令转变成易读的形式:
多命令执行
在上面描述的修改 reduce 来达到命令执行的效果,一次只能执行一条命令,想要多命令执行就只能通过手写 opcode 来实现,只要不碰到 .
导致程序结束返回就能一直执行命令
R,i,o 介绍
在 opcode 里能执行函数的字节码就是 R,i,o
- R
- i : 相当于 c 和 o 的组合,先获取一个全局函数,然后寻找栈中的上一个 MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)
- o : 寻找栈中的上一个 MARK,以之间的第一个数据(必须为函数)为 callable,第二个到第 n 个数据为参数,执行该函数(或实例化一个对象)
实例化对象
实例化对象也是一种变相的函数执行,因为 python 不需要 new 一个对象(bushi
变量覆盖
也是一个 nb 的利用手段,通常 python 框架使用了 session 时都会有个 secret,我们可以通过覆盖掉这个 secret 来伪造 session
首先通过 c 来获取 main. secret 模块,然后将 MARK 标记压入栈,字符串 secret, F12 压入栈,d 将两个字符串组合成字典也就是{‘secret’: ‘F12’}的形式,由于在 pickle 中,反序列化的数据都是以 key-value 的形式存储的,所有 main. secret 也就是 {‘secret’: ‘F13’},b 执行 dict.update (),也就是{‘secret’: ‘F13’}. update ({‘secret’: ‘F12’}),最终 secret 变成了 F12
Pker 工具介绍
一个方便生成所需要 opcode 代码的工具:https://github.com/eddieivan01/pker 仿 python 语法生成 opcode,使用方法很简单 https://xz.aliyun.com/t/7012