最简单有效的修复是用 json.loads 替代 pickle.loads,仅限基础数据类型;禁用 joblib 处理外部输入;必须反序列化时采用白名单机制并全局排查隐藏入口。

直接用 json 替代 pickle.loads 是最简单有效的修复方式
只要数据结构只含 dict、list、str、int、float、bool 和 None,json 就能完全覆盖 pickle 的安全用途。它不执行任何代码,不调用任何方法,也不会重建类实例。
常见错误现象:把用户上传的 JSON 数据先用 json.loads 解析,再塞进 pickle.loads 里“二次处理”——这毫无必要,反而引入风险。
- 替换示例:
pickle.loads(request.data)→json.loads(request.data) - 如果原始数据是二进制(如 POST body),确保传入的是 UTF-8 字节串:
json.loads(request.data.decode('utf-8')) - 遇到
JSONDecodeError时,说明数据格式非法,应直接拒绝,而不是 fallback 到pickle
用 joblib 前必须确认场景是否真的需要它
joblib 不是 pickle 的“安全升级版”,而是为科学计算优化的序列化工具,底层仍依赖 pickle(尤其在 protocol 4+ 下)。它默认不做签名、不校验来源,对不可信输入同样危险。
典型误用场景:把 Web API 的请求体、表单字段、URL 参数交给 joblib.load 或 joblib.loads ——这和直接用 pickle 几乎等价。
立即学习“Python免费学习笔记(深入)”;
-
joblib合理用途:本地保存训练好的sklearn模型、缓存 numpy 数组、跨进程传递预处理结果 - 若必须网络传输 joblib 数据,务必加签名:
hmac.new(KEY, data, hashlib.sha256).digest(),且服务端验证通过后再解包 - 不要用
joblib处理任何来自 HTTP、Redis、Kafka 等外部通道的原始字节流
当业务真绕不开对象反序列化时,别碰 __reduce__,改用白名单机制
比如 Celery 任务参数、Django 缓存中的类实例、内部 RPC 的 payload——这些场景下,你控制数据生成端,但无法彻底禁用 pickle。此时关键不是换库,而是约束反序列化行为。
核心思路:禁止任意模块导入和函数调用,只允许加载预定义类和基础类型。
- 继承
pickle.Unpickler,重写find_class方法,硬编码可导入的模块与类名 - 示例中只放行
datetime.datetime和numpy.ndarray,其余一律抛AttributeError - 禁用
EXT1/EXT2/EXT4等 opcode(对应find_class调用),需用自定义 unpickler 配合restricted=True参数(Python 3.11+) - 永远不要在
find_class里拼接字符串构造模块名,避免绕过
最容易被忽略的点:日志、调试接口、错误回显也可能触发反序列化
很多团队修掉了主接口的 pickle.loads,却漏掉埋在日志中间件里的 pickle.load(open('/tmp/debug.pkl')),或开发环境开放的 /debug/unpickle?data=xxx 接口。
这类路径往往没走 WAF,也没做鉴权,攻击者只需找到一个未文档化的 endpoint 就能直连内网服务。
建议全局 grep:grep -r "pickle\.loads\|joblib\.loads\|pickle\.load" --include="*.py" .,特别检查 tests/、utils/、debug/ 目录下的临时脚本。


















