广告
您当前的位置: 首页 >  技术 >  Python

Python 中的序列化机制与 Pickle 安全漏洞 (RCE) 防御实战

作者:XiaoZhang 时间:2026-06-24 阅读数:0人阅读

在软件开发中,序列化(Serialization)反序列化(Deserialization) 是极为高频的基础操作。无论是将内存中的对象保存到磁盘、在分布式系统(如 Celery 任务队列)间传递数据,还是通过网络传输状态信息,我们都需要将复杂的内存对象图转换成可存储或可传输的字节流。

Python 标准库提供了多种序列化工具:底层的 marshal、通用的 pickle 以及跨语言的 json

然而,便捷性的背后隐藏着致命的系统安全隐患。pickle 模块在提供全对象类型反序列化能力的同时,也为黑客敞开了一道 远程代码执行(RCE)漏洞 的大门。

本文将为你深度剖析 Python 序列化工具的底层运转细节,重现 Pickle 漏洞的攻击过程,并分享如何在生产中保障序列化数据的安全。


一、 序列化三剑客:marshal、pickle 与 json

在 Python 体系中,不同的序列化模块服务于截然不同的应用场景:

1. 内置编译基石:marshal

  • 用途marshal 是 Python 解释器内部用来序列化 .pyc 字节码文件的工具。
  • 特点:极快,但只支持基本的 Python 类型(如数字、字符串、元组、代码对象)。
  • 高危警告marshal 的格式在不同 Python 大版本之间不保证向前或向后兼容。官方明令禁止使用 marshal 进行用户数据的持久化存储。

2. 原生对象图复制器:pickle

  • 用途:Python 特有的对象序列化协议。
  • 特点
    • 能够序列化几乎所有的 Python 对象(包括自定义类实例、函数、嵌套循环引用的复杂对象图)。
    • 支持 0 到 5 六个版本的协议(Protocol 5 引入了带外缓冲区(Out-of-band Buffers),为海量数据如 NumPy 数组传输提供了“零拷贝”内存优化)。

3. 跨语言文本交互:json

  • 用途:标准的、文本格式的跨语言数据交换格式。
  • 特点:纯文本、声明式、不包含任何可执行逻辑,安全性极高。但默认只支持基本的字典、列表、数字和字符串,不支持自定义类对象。

二、 致命黑魔法:Pickle 安全漏洞与 RCE 重现

为什么 pickle 的反序列化操作(pickle.loads())被安全界视为极其高危的操作?

1. 底层反序列化机理

与单纯的数据转换(如 JSON)不同,pickle 反序列化的过程实际上是在虚拟机中运行一段特殊的字节码指令(Opcode)。为了恢复自定义类的实例,pickle 必须有权动态寻找、导入并调用该类的构造函数。

2. 利用 __reduce__ 劫持反序列化

Python 允许我们在类中通过定义特殊方法 __reduce__(),来声明当该对象被 pickle 序列化时应该如何拆解和重组。 * __reduce__ 方法必须返回一个元组:第一个元素是一个可调用对象(如函数),第二个元素是传给该可调用的参数元组。 * 漏洞成因:如果在反序列化时不加过滤地执行 __reduce__ 返回的可调用对象,黑客就可以将这个可调用对象替换为系统的核心执行函数(如 os.systemsubprocess.Popen),从而在目标服务器上运行任意命令。

3. RCE 攻击 Payload 复现:

import pickle
import os

class Exploit:
    def __reduce__(self):
        # 声明反序列化时,直接调用 os.system 执行外部命令
        cmd = "echo 'WARNING: SYSTEM HACKED!'"
        return (os.system, (cmd,))

# 1. 黑客序列化恶意对象,生成 Payload 字节流
exploit_payload = pickle.dumps(Exploit())

# 2. 模拟服务器端在接收到该数据后,执行了反序列化操作
print("--- 开始反序列化 ---")
# 此时,服务器会直接执行 os.system("echo ..."),打印 hacked 信息!
pickle.loads(exploit_payload)

如果在真实的高权限 Web 服务器中,黑客将 cmd 替换为反弹 Shell 命令,就能在一瞬间彻底夺取服务器控制权。


三、 序列化方案的纵深安全防御

在实际工程设计中,我们应当通过多层次的防御手段彻底掐断反序列化带来的安全隐患:

1. 黄金法则:绝不使用 Pickle 接收不受信任的数据

如果数据来源于外部用户输入、不受信任的网络链接、或者未加密的公共 API,绝对不要使用 pickle.loads() 进行解析

2. 替代方案:自定义 JSON 编解码器

对于网络交互,推荐使用 JSON 格式。如果需要传输自定义对象,可以通过重写 json.JSONEncoder 实现安全的数据提取与重组:

import json

class User:
    def __init__(self, user_id, name):
        self.user_id = user_id
        self.name = name

# 自定义安全编码器
class CustomUserEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, User):
            # 只提取基本数据,不包含任何执行逻辑
            return {"__type__": "User", "user_id": obj.user_id, "name": obj.name}
        return super().default(obj)

# 转换测试
user = User(101, "Alice")
json_str = json.dumps(user, cls=CustomUserEncoder)
print("Safe JSON:", json_str)

3. 如果必须使用 Pickle:使用数字签名进行完整性校验

如果您在设计分布式计算(如 Celery 分布式任务队列),必须使用 pickle 传输复杂的计算对象,您必须通过加密签名(HMAC) 来验证数据的来源,防止数据在传输过程中被黑客篡改。

import hmac
import hashlib
import pickle

SECRET_KEY = b"your_extremely_secure_key"

def safe_dumps(obj):
    # 1. 序列化对象
    data = pickle.dumps(obj)
    # 2. 对数据生成 HMAC-SHA256 签名,用于身份验证
    signature = hmac.new(SECRET_KEY, data, hashlib.sha256).digest()
    # 3. 将数据与签名打包在一起发送
    return {"data": data, "signature": signature}

def safe_loads(packet):
    data = packet["data"]
    received_sig = packet["signature"]

    # 重新计算签名
    expected_sig = hmac.new(SECRET_KEY, data, hashlib.sha256).digest()

    # 采用安全的时间恒定比较防范时序攻击
    if not hmac.compare_digest(expected_sig, received_sig):
        raise SecurityError("警告:数据完整性校验失败,疑似遭到串改!")

    return pickle.loads(data)

总结

序列化是将动态内存对象映射为静态网络/存储字节的重要桥梁。在 CPython 的设计中,marshal 局限于底层的 .pyc 编译,pickle 充当了全功能的对象图拷贝者,而 json 提供了跨平台的无害文本交换。然而,pickle 底层基于 Opcode 指令流的求值逻辑,使得黑客可以通过 __reduce__ 轻松达成 RCE 劫持。在大型分布式架构中,坚决不反序列化陌生数据、优先选用安全的 JSON 反射、并对不得不使用的 Pickle 通道施以 HMAC 加密验证,是守卫数据和服务器安全的钢铁防线。

本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。

评论交流 (0)

正在加载评论...
头像

XiaoZhang

当你还撑不起你的梦想时,就要去奋斗。如果缘分安排我们相遇,请不要让她擦肩和过。我们一起奋斗!

微信