pickle.load() 不能读不受信数据,因为它反序列化时会执行任意代码,通过__reduce__等方法调用os.system等危险函数,仅限完全可控的内部系统使用。

为什么 pickle.load() 不能直接读不受信的数据
因为 pickle 反序列化时会执行任意代码——不是“可能”,是设计如此。它靠重建对象、调用 __setstate__、__reduce__ 等方法还原状态,而这些方法可以是任意可调用对象,包括 os.system 或 importlib.import_module。
常见错误现象:AttributeError: Can't get attribute 'xxx' on <module></module> 看似只是找不到类,但背后可能是攻击者替换了模块路径或注入了恶意 __reduce__ 返回值。
- 使用场景:只在完全可控的内部系统中用(比如同一进程写、同一进程读;或严格校验来源的 RPC 内部通信)
- 绝不能用于 HTTP 请求体、用户上传文件、数据库字段等任何含外部输入的场景
- 即使数据来自“自己人”,也要确认传输链路未被中间人篡改(
pickle不带签名或校验)
替代方案选哪个:JSON / msgpack / dill / cloudpickle
JSON 最安全,但只支持基础类型(dict、list、str、int、float、bool、None),无法保存函数、类实例、闭包。
msgpack 比 JSON 更紧凑、更快,但同样不支持自定义对象,且默认不校验浮点精度,需手动配 strict_float=True 避免 NaN 或 inf 引发解析失败。
立即学习“Python免费学习笔记(深入)”;
dill 和 cloudpickle 能序列化更多东西(比如 lambda、模块级函数),但——它们和 pickle 一样危险,反序列化时照样执行代码。
- 如果你只需要传数据,用
json.dumps()+json.loads(),加一层object_hook做类型恢复(比如把{"_type": "datetime", "value": "2024-01-01"}转成datetime) - 如果必须传函数/类,考虑用明确的注册表机制(如白名单函数名 + 参数),而不是直接反序列化字节流
-
cloudpickle在 Spark/Dask 中常用,但 worker 加载任务前仍需确保 job 提交者可信
想保留 pickle 又降低风险?这三点硬限制必须加
不是“建议”,是底线。少一条都可能绕过防护。
- 用
pickle.Unpickler子类重写find_class(),只允许从预设模块+类名白名单中加载(例如只允许numpy.ndarray、collections.Counter) - 读取前先用
hmac校验数据完整性(密钥必须保密,且不能硬编码在客户端) - 在沙箱进程里反序列化(比如用
subprocess.run()启一个最小权限子进程,传入数据并限时退出)——别指望threading或seccomp在主线程里拦住os.system
调试时误用 pickle 的典型翻车点
开发阶段图省事,把整个 self 对象或 globals() 一股脑 pickle.dump() 下去,上线后才发现依赖了临时定义的函数或 IPython 魔法变量。
错误现象:ModuleNotFoundError: No module named 'IPython' 或 AttributeError: module '__main__' has no attribute 'temp_func'。
-
__main__模块里的类/函数无法跨进程反序列化(不同脚本启动时__main__指向不同) - 闭包捕获的局部变量、装饰器生成的 wrapper、
functools.partial实例,都可能隐式依赖运行时上下文 - 用
pickletools.dis()查看 dump 出的字节码,能快速识别是否引用了可疑模块(比如含os、subprocess、importlib)
真正难防的不是黑客,是你自己下周写的那个没加类型注解、又顺手 pickle.dump() 的调试脚本。








