如何审计 Python 服务中的不安全反序列化调用点?
在 Python 服务中替换不可信 pickle 反序列化:从缓存与队列注入到 JSON 载荷迁移
在 Python 服务中,缓存、消息队列和数据库常被默认当作“内部可信边界”,于是读取端直接调用 pickle.loads 还原数据。问题在于,反序列化的风险取决于数据能否被不可信方影响,而不取决于存储介质是否部署在内网。只要攻击者能触及某条写入路径、队列消息或缓存内容,读取端在还原时就可能执行其中嵌入的调用。审计的起点,是把“谁能写入、谁会读取、数据是否经过可信校验”这三个问题问清楚。
pickle 并不只是把字节还原成字典和列表。它会按载荷指示查找并调用对象,因此处理不可信 pickle 可能导致任意代码执行。一个典型的脆弱调用点往往藏在封装里,例如从缓存取值后直接还原。关键不在这行是否使用了缓存,而在返回的数据能否被外部构造;换用 pickle.Unpickler 或自建包装函数,也不会自动消除这一点。
定位调用点并追踪数据流
审计应先在仓库中搜索反序列化入口,常见关键词包括 pickle.loads、pickle.load、pickle.Unpickler,以及 dill.loads、cloudpickle.loads,同时覆盖项目自建封装和依赖库中的路径。仅定位调用点并不足够,还需沿数据流核实载荷来自哪个生产者、哪些身份有写入权限、哪些消费者会读取,并特别检查任务重试、死信队列、缓存回填和历史数据回放,因为它们可能继续处理旧载荷。静态扫描可帮助发现可疑点,但不能单独证明安全或漏洞成立。验证时只在隔离环境中使用无害测试载荷,不要把真实生产数据复制过来后直接反序列化,任何无法确认安全的旧载荷都不应通过“试着加载一下”来检查。
用结构化契约替代执行能力
长期修复不是更换序列化语法,而是让数据只表达经过校验的业务字段。迁移到 JSON 或结构化消息时,应同时约束格式版本、必填字段、类型、允许值和大小,拒绝未知操作与超限载荷。需要警惕的是“先试 JSON、失败就回退 pickle”的兜底写法:攻击者可以利用解析失败把数据引向危险分支。给 pickle 载荷加签也不能将其变为安全格式,签名只能在正确验证后帮助确认来源,无法改变 pickle 具备执行能力这一事实。真正收敛风险的方向,是缩小写入权限、移除不可信 pickle 读取,并用输入校验和回归测试持续验证新边界。

参与讨论
内网可信这个假设坑过太多人
pickle.loads 真的别乱用,血泪教训
队列消息那块我一直没想到,回头得排查下
死信队列和缓存回填这俩确实容易被忽略
那种先试JSON失败回退pickle的写法太常见了
加签不等于安全,这点想提醒一下团队
想问下自建封装怎么快速搜出来,关键词不够全
重试和历史回放会处理旧载荷,细思极恐
准备明天就在仓库里grep一遍几个关键词
结构化契约这个方向我赞同,光换语法没用
dill和cloudpickle也在列,很多人只盯着pickle
隔离环境测试载荷这条说得对,别拿生产数据冒险
缩小写入权限才是根本,读取端再防也累
版本、必填、类型、大小都要卡,这个清单实用