不能直接用 pickle 反序列化不可信数据,因为 pickle.load() 会执行任意代码而非仅解析数据,可能触发恶意操作;应改用 json + 显式构造或 pydantic/marshmallow 等安全方案。

为什么不能直接用 pickle 反序列化不可信数据
因为 pickle 在 load() 时会执行任意代码,不是“解析数据”,而是“重建对象+运行构造逻辑”。收到一个恶意的 pickle 字节串,可能直接删文件、起反向 shell、读取环境变量。
常见错误现象:os.system("rm -rf /") 这类调用藏在自定义类的 __reduce__ 方法里,pickle.load() 一跑就触发;或者用 builtins.exec 注入执行。
使用场景:只要数据来源不完全可控(比如网络请求、用户上传、跨服务通信),就不能用 pickle 做反序列化。
推荐替代方案:优先选 json + 显式构造
json 安全,但只支持基础类型(dict、list、str、int、float、bool、None),不支持自定义类、函数、datetime 等。所以得自己写转换逻辑。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 把对象转成
dict再json.dumps(),比如加一个to_dict()方法 - 反序列化时,先
json.loads(),再手动用字典内容初始化对象(别用eval或__dict__.update()) - 对关键字段做类型检查和范围校验,比如
if not isinstance(data.get("id"), int): raise ValueError - 如果需要时间字段,统一用 ISO 格式字符串:
dt.isoformat(),反序列化时用datetime.fromisoformat()
示例:
用 php + mysql 驱动的在线商城系统,我们的目标为中国的中小企业及个人提供最简洁,最安全,最高效的在线商城解决方案,使用了自建的会员积分折扣功能,不同的会员组有不同的折扣,让您的商店吸引更多的后续客户。 系统自动加分处理功能,自动处理会员等级,免去人工处理的工作量,让您的商店运作起来更方便省事 采用了自建的直接模板技术,免去了模板解析时间,提高了代码利用效率 独立开发的购物车系统,使用最
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
<pre class='brush:python;toolbar:false;'>def to_dict(self) -> dict:
return {"name": self.name, "age": self.age}
@classmethod
def from_dict(cls, data: dict):
return cls(name=data["name"], age=int(data["age"])) # 显式类型转换需要结构化 + 类型安全?用 dataclasses + marshmallow 或 pydantic
当对象字段多、有嵌套、要校验、还要生成文档时,手写 to_dict 很容易漏字段或类型错位。这时候上结构化方案更稳。
性能与兼容性影响:
-
pydantic v2(BaseModel)默认用 C 加速,解析比纯json+ 手动构造略慢 10–20%,但开发效率和安全性提升明显 -
marshmallow更轻量,无额外依赖,适合只做校验不需运行时类型提示的场景 - 注意
pydantic的model_validate_json()能直接从 JSON 字符串构建实例,但必须确保输入是合法 JSON —— 它不处理 pickle 字节流
容易踩的坑:
- 用
model_dump_json()序列化后,又用json.loads()再传给model_validate():多此一举,直接用model_validate_json() - 字段名含下划线但前端传的是驼峰(如
user_namevsuserName),没配alias或alias_generator,导致字段丢失 - 把
datetime直接塞进BaseModel字段却不设default_factory或default,实例化时报TypeError: datetime.datetime(...) is not JSON serializable
临时兼容老 pickle 数据?别硬解,做迁移层
线上已有大量 pickle 文件或缓存,不能立刻全切走?不要尝试用 RestrictedUnpickler 或白名单绕过风险——它本质还是在执行未知逻辑,维护成本高、易被绕过。
正确做法是加一层迁移:
- 新服务启动时,检测到旧格式(比如文件头是
b'\x80\x04'),就用隔离环境(如子进程 +timeout+seccomp)跑一次pickle.load(),仅提取原始字段,转成 JSON 存新路径 - 同时记录日志:哪个 key、什么类型、是否含可疑模块(如
os、subprocess),便于审计 - 后续所有读操作只认新格式,旧路径只读、不写、限期下线
复杂点在于:有些老 pickle 依赖已删的模块或类名,这时得在隔离环境里动态 patch sys.modules,但只允许导入内置模块和白名单里的几个工具类——这步很容易漏权限控制,务必限制子进程能访问的文件路径和系统调用。









