想象你收到一个精美的礼品盒。普通礼盒打开后只有礼物;但有一种“魔术礼盒”,盒盖内侧藏着弹簧机关,你一掀盖子,机关自动弹射——开盒这个动作本身就触发了命令。这个魔术礼盒,就是 Python 生态中最常用的序列化工具 Pickle。AI 工程师每天都在用 torch.save、torch.load 保存和加载模型权重,但很少有人意识到:torch.save 的底层就是 Pickle。Pickle 序列化后的文件里,不仅存着模型参数,还混入了“如何重建对象”的指令流。攻击者可以伪造一个看似正常的模型文件,把恶意指令埋伏在权重数据里,等你 torch.load 一执行,代码就在你的服务器上安家了。 近年来,HuggingFace 等模型仓库多次曝出恶意 Pickle 模型事件:有研究者上传了“能读取系统文件”的模型权重,有模型在加载时会启动反弹 shell。模型文件的安全性,已经从“会不会跑错”升级为“会不会被黑”。这篇文章将带你把“魔术礼盒”拆开看个明白:Pickle 怎么构造、漏洞怎么触发、攻击者怎么投毒、检测工具怎么扫描,以及如何用 safetensors 这把“安全钥匙”换掉隐患。读完,你会对模型供应链安全建立起系统直觉。

岗位关联

对 AI 从业者而言,Pickle 反序列化漏洞是模型供应链安全的第一课——你每天 torch.load 的模型权重,可能就是攻击者递来的“毒礼盒”。

目录
  1. 可视化演示
  2. 一、序列化基础:礼盒是怎么做出来的
  3. 二、漏洞原理:盖子一开,机关就触发
  4. 三、攻击手法:谁在礼盒里放了蛇
  5. 四、安全检测:收件前的安检扫描
  6. 五、安全格式:换成透明收纳盒
  7. 六、运维实践:建立快递签收规矩
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示Pickle反序列化漏洞原理与防护:Pickle VM字节码执行(reduce→callable→RCE)、模型供应链投毒攻击路径(投毒→分发→下载→加载→执行)、四层防护体系(safetensors/静态扫描/weights_only/沙箱),三模式切换理解模型文件安全。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Pickle VM字节码执行漏洞 Pickle非简单编码而是栈式虚拟机 reduce返回(callable,args)触发执行 unpickle -> reduce -> callable(*args) -> RCE 反序列化时逐条执行字节码,reduce方法可触发任意代码执行。

一、序列化基础:礼盒是怎么做出来的

要理解魔术礼盒为什么危险,得先知道礼盒是怎么制造的。Pickle 序列化,就像一个纸艺大师把一只“立体小玩具”一步步拆平,叠成一张带折线印记的纸板;反序列化时,沿着折线把纸板重新折回立体玩具。问题是,这张“折纸说明书”上不仅画了怎么折,还夹带了几行“小字指令”——比如“压一下弹簧就弹起”。

Pickle 的序列化过程把 Python 对象变成字节流,反序列化过程从字节流恢复对象。这个过程中间由一系列 opcode(操作码)驱动,就像折纸说明上的每一步折叠指令。普通的 Python 数据类型(数字、字符串、列表)用简单的 opcode 就能还原;但一个自定义类的实例要怎么还原?Pickle 会把类名、属性一起打包,恢复时“按图重建”。

Pickle 之所以高效方便,是因为它能处理几乎任意 Python 对象。torch.save 保存模型时,本质上是把 OrderedDict 的模型参数、优化器状态、超参数等对象依次 Pickle,反序列化 torch.load 恢复对象。这一进一出看似人畜无害,但关键在“任意”两字——Pickle 允许文件内容里指定“调用某个函数”。这就是藏机关的入口。

所以魔术礼盒的本质,是“折纸图纸上混入了机关说明”。你以为是拆纸板,其实是照着图纸装了一个弹簧。接下来我们看看,这个弹簧具体是怎么弹出来的。

O→Picklebytes→UnpickleO′O \xrightarrow{\text{Pickle}} \text{bytes} \xrightarrow{\text{Unpickle}} O'
通俗说明:对象 O 经过 Pickle 编码变为字节流,再经 Unpickle 解码恢复为 O'。正常过程 O' ≈ O。但攻击者可以控制字节流中间的内容,让 O' 根本不是原始对象。
  1. 序列化方向将 Python 对象 O 转换为字节流,这是 torch.save 做的事
    O→PicklebytesO \xrightarrow{\text{Pickle}} \text{bytes}
  2. 反序列化方向将字节流还原为 Python 对象 O',这是 torch.load 做的事
    bytes→UnpickleO′\text{bytes} \xrightarrow{\text{Unpickle}} O'
  3. 对象等价正常情况下 O' 与 O 内容一致,但字节流可被篡改
    O′≈OO' \approx O
  4. 指令注入点字节流不仅是“数据”,还包含“指令”,这是风险根源
    bytes=Data∪Instructions\text{bytes} = \text{Data} \cup \text{Instructions}

二、漏洞原理:盖子一开,机关就触发

机关的触发机制,藏在 Python 的一个“魔法方法”里:__reduce__。这个方法就像礼盒夹层里的弹簧片——Pickle 设计它来告诉你“这个对象该怎么重组”。

__reduce__ 是 Python 对象协议中的一个特殊方法。当一个类定义了 __reduce__,Pickle 序列化时不会保守地打包对象的属性和类名,而是直接调用这个方法,获取你指定的“重建指令”。这个指令由两部分组成:(callable, args)——一个可调用对象和参数元组。反序列化时,Pickle 看到指令,就自动执行 callable(*args)。

这个“自动执行”是设计初衷:某些对象无法通过常规属性还原(比如打开的文件句柄),Python 允许你通过 __reduce__ 指定如何重建。但攻击者不需要什么正当理由——直接把 callable 设成 os.system,args 设成攻击命令即可。于是,受害者执行 torch.load("model.pth"),等同于执行了一段被解码出来的 os.system("任意命令")。更可怕的是,不只是 torch.load,pandas.read_pickle、joblib.load 等所有基于 pickle 的加载器都有同样的问题。只要用户加载了恶意文件,无需其他操作,RCE(远程代码执行)就发生了。

你看,弹簧的构造其实很简单:__reduce__ 就是弹簧,REDUCE 就是触发装置,f(a) 就是弹射出来的小丑。魔术礼盒完全合法——它按设计工作。危险不在程序有 bug,而在“能执行代码”这一特性本身被恶意利用了。

Unpickle(D)→REDUCE(f,a)→f(a)\text{Unpickle}(D) \to \text{REDUCE}(f, a) \to f(a)
通俗说明:反序列化恶意字节流 D 时,遇到 REDUCE 指令会取出函数 f(如 os.system)和参数 a(如命令字符串),并立即执行 f(a)。
  1. 恶意输入D 是攻击者构造的 pickle 字节流
    D=Pickle(EvilObject)D = \text{Pickle}(\text{EvilObject})
  2. 执行触发Unpickle 逐条解释 opcode,遇到 REDUCE 指令
    Unpickle(D)⇒opcode=REDUCE\text{Unpickle}(D) \Rightarrow \text{opcode}=\text{REDUCE}
  3. 取出函数与参数从栈中弹出 callable 和 args
    f←stack.pop(),a←stack.pop()f \leftarrow \text{stack.pop()}, \quad a \leftarrow \text{stack.pop()}
  4. 代码执行立即调用函数完成“对象重建”,实则执行任意代码
    f(a)=os.system(cmd)f(a) = \text{os.system}(\text{cmd})

三、攻击手法:谁在礼盒里放了蛇

明白了机关原理,现在看坏人怎么往礼盒里放蛇。放蛇不需要高深的黑客技术,一个懂点 Python 的普通人都能做到。

构造恶意 pickle 的核心代码极其简单:定义一个类,在 __reduce__ 里返回 (os.system, ("echo pwned > /tmp/rce",)),然后 pickle.dumps 一把生成字节流。这段字节流就埋在模型文件的某个位置。攻击者通常把它放在“模型权重”或“配置对象”里,让 torch.load 加载成功后看起来一切正常——命令可能已经执行完了。

攻击的规模化套路是“供应链投毒”:攻击者把恶意模型上传到 HuggingFace、GitHub 等公开仓库,起名“最新大模型权重”或“预训练加速版”。一旦有开发者下载加载,后门即被触发。真实的案例中,有人上传了能读取 ~/.ssh 私钥的模型;有人用 pickle 反弹 shell 夺取服务器控制权。模型仓库已经成为与 npm、PyPI 并列的软件供应链攻击面。攻击者的目标不一定是模型本身——你的服务器算力、训练数据、API 密钥、内网跳板,都可以是猎物。一个“免费好用”的模型权重,可能就是精心包装的毒蛇。

放蛇的人甚至不用露面——把带蛇的礼盒放到人流密集的“快递架”(开源仓库)上,等着贪图方便的人顺手拿走。在 AI 领域,这个“快递架”叫 HuggingFace Hub。

EvilP→dumpsD→uploadR→downloadVictim→loadRCE\text{EvilP} \xrightarrow{\text{dumps}} D \xrightarrow{\text{upload}} \mathcal{R} \xrightarrow{\text{download}} \text{Victim} \xrightarrow{\text{load}} \text{RCE}
通俗说明:恶意对象 EvilP 序列化为字节流 D,上传到模型仓库 R,受害者下载并 torch.load,最终触发远程代码执行。
  1. 恶意对象构造定义 Evil 类,__reduce__ 返回 (os.system, 命令)
    Evil.__reduce__↦(os.system,[cmd])\text{Evil.}\_\_\text{reduce\_\_} \mapsto (\text{os.system}, [\text{cmd}])
  2. 序列化投毒通过 pickle.dumps 转为字节流
    D=pickle.dumps(Evil())D = \text{pickle.dumps}(\text{Evil}())
  3. 供应链投放上传到公开仓库等待受害者下载
    D→RHuggingFaceD \to \mathcal{R}_{\text{HuggingFace}}
  4. 受害者触发torch.load 解包时执行命令
    torch.load(D)→os.system(cmd)\text{torch.load}(D) \to \text{os.system}(\text{cmd})

四、安全检测:收件前的安检扫描

知道了礼盒可能藏蛇,最直接的办法就是收件时过一遍 X 光安检机。Pickle 的安检机,就是静态扫描工具。

静态扫描不执行文件内容,而是“读取折纸图纸上的文字”,看看有没有可疑的折叠指令和危险模块。Python 自带的 pickletools 库可以逐条列出 pickle 文件里的 opcode;基于此,安全工具 fickling(Trail of Bits 出品)和 picklescan 能还原出 pickle 的执行语义。

一个典型的扫描流程:用 pickletools.genops 遍历所有 opcode,然后用“危险模块名单”匹配 GLOBAL / STACK_GLOBAL 指令引用的模块(比如 os、subprocess、sys、builtins),同时关注 REDUCE / BUILD / INST 等代表“执行操作”的指令。如果两者同时出现,文件就高度可疑。不过注意,扫描有盲区:攻击者可以通过对象方法、运行时动态解析等手段绕过简单的模式匹配。所以静态扫描是“第一道安检”,不是“绝对免检”。

安检机不能阻止所有危险品,但能把 90% 的“带蛇礼盒”挡在门外。过了安检关,我们还需要更好的解决方案:直接把礼盒换成透明盒子。

Risk(D)={高危,∃ 危险模块∧执行指令可疑,∃ 执行指令低危,其他\text{Risk}(D) = \begin{cases} \text{高危}, & \exists\ \text{危险模块} \wedge \text{执行指令} \\ \text{可疑}, & \exists\ \text{执行指令} \\ \text{低危}, & \text{其他} \end{cases}
通俗说明:安检机按规则给文件打安全等级:危险模块+执行指令同时出现=高危;只有执行指令=可疑;都不存在=低危。
  1. 危险模块检测存在 GLOBAL 指令引用 os、subprocess 等危险模块
    ∃ GLOBAL(os)\exists\ \text{GLOBAL}(\text{os})
  2. 执行指令检测存在 REDUCE、BUILD 等调用执行型操作码
    ∃ REDUCE/BUILD\exists\ \text{REDUCE} / \text{BUILD}
  3. 风险分级判定危险模块与执行指令同时命中则判定为高危
    高危  ⟺  危险模块∧执行指令\text{高危} \iff \text{危险模块} \wedge \text{执行指令}

五、安全格式:换成透明收纳盒

与其天天拆礼盒过安检,不如从源头上让“机关”无处可藏——这就是 safetensors 的思路:把魔术礼盒换成透明收纳盒。

safetensors 是 HuggingFace 牵头设计的模型张量存储格式。它的文件结构极其简单:一个 JSON 头部 + 一段二进制张量数据。JSON 头描述每个张量的名称、形状和偏移量,二进制区存原始数值。整个文件里没有任何指令流,自然不存在“执行代码”的入口。

具体来看,safetensors 的头部是一个严格校验的 JSON 对象,比如 {"model.embed.weight": {"dtype": "F32", "shape": [128, 256], "data_offsets": [0, 131072]}}。加载器只负责把偏移区间的字节解析成张量,完全不存在调用函数的机会。PyTorch 官方已支持 safetensors.torch.load_file,Transformers 库也默认使用 safetensors。ONNX 格式同样安全——它本质上是带数据流的图结构,不嵌入 Python 执行指令。实践上,在 HuggingFace 下载模型时优先选 safetensors 版本;自己保存模型时养成 save_file 的习惯,逐步替代 torch.save。

透明收纳盒的设计哲学是“所见即所得”——盒子里只有张量,没有弹簧。就算有人想放蛇,也没有夹层可藏。这是比任何安检都彻底的安全。

safetensors=JSON 头部⏟元数据  ∥  二进制张量区⏟纯数据\text{safetensors} = \underbrace{\text{JSON 头部}}_{\text{元数据}} \; \| \; \underbrace{\text{二进制张量区}}_{\text{纯数据}}
通俗说明:safetensors 文件由两部分拼接:JSON 头部描述张量信息,二进制区存原始数值,二者都不含可执行指令。
  1. JSON 头部描述张量名称、形状、偏移,供加载器精确定位
    header={tensor_name:(dtype,shape,offsets)}\text{header} = \{\text{tensor\_name}: (\text{dtype}, \text{shape}, \text{offsets})\}
  2. 二进制区连续存储的张量数值,按偏移读取
    data=bytes[offset0:offset1]\text{data} = \text{bytes}[\text{offset}_0:\text{offset}_1]
  3. 纯数据承诺没有 opcode、没有 callable,从结构上消除执行面
    ∄ 指令流⇒∄ RCE\nexists\ \text{指令流} \Rightarrow \nexists\ \text{RCE}

六、运维实践:建立快递签收规矩

即使有了透明收纳盒,现实中的旧模型、第三方权重总有用到 pickle 的场景。这时候就要靠“快递签收规矩”来兜底。

安全运维的核心原则是“默认不可信”:任何模型文件,除非能证明来源可靠,否则一律当成“陌生快递”处理。具体落地有三个抓手:信任链、隔离环境、签名验证。

第一,来源信任链。只从官方仓库、组织内部镜像站下载模型,建立可信 URL 白名单,阻断开发者从不明链接下载。第二,隔离环境。必须在沙箱、容器(Docker 无网络/只读网络)中加载不可信的 pickle 文件,尽量配合最小权限账号,把 RCE 的影响限制在壳内。第三,签名验证。模型发布方对文件计算哈希并签名,加载方用公钥验签,确保权重没有被篡改。在 CI/CD 流程中,可以把扫描集成到模型上线的流水线:下载 → 验签 → 扫描 → 试运行(沙箱)→ 上线。AI 安全运维团队需要把“模型即数据”的认知升级为“模型即供应链”。

快递签收规矩总结成一句话:“不认识的快递不收,收了的先过安检,过了安检在门外拆。” 模型供应链的信任,不能靠一句“应该没事”来维系。

Accept  ⟺  Verify(hash,sign)∧Scan(D)≠高危\text{Accept} \iff \text{Verify}(\text{hash}, \text{sign}) \wedge \text{Scan}(D) \neq \text{高危}
通俗说明:只有签名验证通过且扫描不是高危,模型文件才允许进入生产环境。
  1. 验签条件文件哈希匹配发布方签名
    Verify(hash(D),sign)=True\text{Verify}(\text{hash}(D), \text{sign}) = \text{True}
  2. 扫描条件静态扫描不判定为高危
    Scan(D)∉{高危}\text{Scan}(D) \notin \{\text{高危}\}
  3. 加载放行双条件同时满足才允许加载
    Accept  ⟺  (1)∧(2)\text{Accept} \iff (1) \wedge (2)

AI 应用场景

场景一:模型仓库安全审核

阶段:模型供应链安全管理

解决的问题:HuggingFace 等开源模型仓库每天有大量上传,攻击者可能投放恶意 pickle 模型,开发者在毫不知情时下载导致服务器被控。传统审核靠人工抽查完全跟不上规模,需要自动化检测手段。

具体案例:某大型 AI 平台的安全团队部署了基于 picklescan 的自动审核服务:用户上传模型时先解包,对所有 .bin/.pth/.pt 文件执行静态扫描,命中危险模块(os、subprocess)与执行指令组合的模型直接拒绝上架并标记红队复测。上线以来拦截了 10+ 个恶意模型。

场景二:企业内部模型资产管理

阶段:模型训练与部署运维

解决的问题:企业内多个算法团队用 torch.save 存模型共享,缺少统一安全口径。员工从网盘、聊天软件下载的模型权重无人审查,存在严重的 RCE 隐患。

具体案例:某公司的 AI 中台搭建了一个“模型保险库”:所有模型上传统一转成 safetensors 格式,原始 pickle 文件必须经过扫描+验签,并记录来源元数据(上传者、时间、项目)后才允许分发。公司发布规范要求新项目默认使用 safetensors API 保存权重。

场景三:红蓝对抗演习

阶段:AI 安全测试与攻防

解决的问题:安全团队无法确认扫描和隔离措施是否真正有效,需要实战演练验证防御体系的薄弱环节。

具体案例:红队构造了一个“带后门的预训练模型”,投放到内部测试的模型共享目录。蓝队通过扫描工具发现恶意指令流,成功阻断加载;同时红队尝试绕过检测(利用对象内联方法),蓝队依靠沙箱隔离捕获了完整攻击链。演习之后,安全团队增加了运行时行为监控。

Python 演示

python53 行
"""Pickle deserialization vulnerability demo and static scanning.""" import pickleimport pickletoolsimport subprocess DANGEROUS_MODULES = {"os", "subprocess", "sys", "builtins"}SAFETENSORS_MAGIC = b"== safetensors =="  class EvilPayload:    """Hijack object reconstruction via __reduce__."""     def __reduce__(self):        # Return (callable, args); unpickle will auto-invoke it.        return (subprocess.Popen, (["echo", "pwned"],))  def scan_pickle(data):    """Enumerate pickle opcodes and flag risky modules/operations."""    modules = set()    risky_ops = []    for opcode, arg, pos in pickletools.genops(data):        if opcode.name in ("GLOBAL", "STACK_GLOBAL"):            module = arg.split(" ")[0] if arg else ""            if module in DANGEROUS_MODULES:                modules.add(module)        if opcode.name in ("REDUCE", "BUILD", "INST", "OBJ"):            risky_ops.append(opcode.name)    return sorted(modules), risky_ops  def check_format(data):    """Distinguish safetensors (pure data) from pickle (code-capable)."""    if data[:8] == SAFETENSORS_MAGIC:        return "safetensors: pure tensor data, no code execution"    return "unknown/pickle-like format: scan before loading"  def main():    malicious_bytes = pickle.dumps(EvilPayload())    print("[1] malicious pickle size = %d bytes" % len(malicious_bytes))     modules, risky_ops = scan_pickle(malicious_bytes)    print("[2] dangerous modules = %s" % modules)    print("[3] risky opcodes = %s" % risky_ops)    verdict = "RCE RISK" if (modules or risky_ops) else "no signal"    print("[4] verdict: %s" % verdict)    print("[5] %s" % check_format(malicious_bytes))  if __name__ == "__main__":    main()
目的:构造一个恶意 pickle 负载,演示 __reduce__ 劫持反序列化过程,并用 pickletools 静态扫描出危险模块与危险操作码,最后对比 safetensors 格式的判别方法。
  1. class EvilPayload:定义恶意对象,__reduce__ 返回 (subprocess.Popen, [命令]),反序列化时自动执行。
  2. pickle.dumps(EvilPayload())生成恶意字节流,模拟攻击者投放到模型文件里的载荷。
  3. for opcode, arg, pos in pickletools.genops(data)逐条遍历 pickle 指令流,是静态扫描的核心机制。
  4. if opcode.name in ("GLOBAL", "STACK_GLOBAL")拦截引用外部模块的指令,提取模块名。
  5. if module in DANGEROUS_MODULES与 os、subprocess 等危险模块黑名单比对。
  6. risky_ops.append(opcode.name)记录 REDUCE、BUILD 等代表“执行操作”的指令。
  7. data[:8] == SAFETENSORS_MAGIC用文件魔数识别 safetensors 格式,纯数据文件无执行风险。
  8. scan_pickle(malicious_bytes)对攻击载荷执行扫描,输出危险模块和危险指令清单。

常见误区

误区“把 model.pth 重命名为 model.safetensors,就变成安全格式了。文件后缀一变,加载器就按新格式解析。”

正解safetensors 是一个结构性完全不同的二进制格式:JSON 头 + 纯张量二进制块。文件扩展名只是名字,加载器通过读取文件内容判断格式。把 pickle 内容贴上 safetensors 的后缀,加载器照样用 pickle 解析,攻击代码照常执行。就像给毒蛇贴上“宠物猫”标签,它依然是毒蛇,咬人功能一点没少。

为何常见:人们容易把“格式安全”误解为“文件标识”,而忽略了安全来自内部结构——真正决定风险的是文件内容的组织方式,而不是文件名。

误区“我用的模型都是自己训练的、从公司内部拿的,根本不会中招。网上那些恶意模型跟我没关系。”

正解风险不只来自网上公开下载。迁移学习中用到的第三方预训练权重、同事从外部拷贝的模型文件、被入侵的内部共享目录,都可能混入恶意 pickle。供应链攻击经常通过“可信渠道”的失陷来间接传播——攻击者先攻破一个你信任的中间人,再把毒礼盒递到你手上。

为何常见:人们习惯用“来源是不是熟人”来判断安全,但供应链攻击恰恰擅长利用信任网络做借道渗透,熟人给的模型也一样要过安检。

误区“用 picklescan 扫过一次说安全,这个文件就可以放心反复加载了。工具都说没问题,那肯定没问题。”

正解静态扫描基于已知特征模式(危险模块、危险 opcode),攻击者可以构造不命中黑名单的多态 payload,或者利用 Python 对象内部的动态解析绕过。一次扫描安全不等同于文件值得信任。正确做法是:来源验证 + 静态扫描 + 沙箱加载的纵深防御组合,任何单一环节都不能作为绝对保证。

为何常见:把检测工具误当作“疫苗”是常见心理——忽略了检测本质上是一个“查症状”的过程,只能降低风险,不能根除风险。

误区“safetensors 是社区炒作,我用 pickle 好几年了从来没出过事,凭什么要换格式?”

正解safetensors 的设计文档、代码完全开源,PyTorch、Transformers、Diffusers 等主流框架均已集成。它的安全性来自“结构中无执行能力”这一事实,而非任何商业背书。至于“没出过事”,更多是运气好或未被发现——RCE 攻击本来就是静默的,服务器被植后门后未必立刻有可见症状,可能半年后你的训练数据已经被偷偷传走了。

为何常见:幸存者偏差让人低估了小概率高影响事件。在安全领域,最危险的不是“发生过的事”,而是“发生了但你没发现的事”。

面试题

Q1. 在 Python 中,将对象转换成字节流以方便存储的过程叫做什么?

单选题 · easy

  • A.将字节流还原为对象
  • B.是将源代码编译成字节码
  • C.把对象转换成为字节流← 正确答案
  • D.将文本编码成为向量表示

答案:C。序列化就是把内存中的对象(如模型权重)转成可存储、可传输的字节流。A 描述的是反序列化,B 是编译,D 是词嵌入。就像把立体礼物压平成纸板叫序列化,再沿折线复原叫反序列化。

Q2. Pickle 反序列化最大的安全风险是什么?

单选题 · easy

  • A.序列化过程会压缩模型体积
  • B.加载时可自动执行任意代码← 正确答案
  • C.反序列化速度慢影响部署
  • D.不支持跨语言模型格式转换

答案:B。Pickle 的字节流包含操作指令(opcode),加载时会逐条执行这些指令。攻击者可在权重中注入恶意指令,加载即触发远程代码执行。这就像魔术礼盒的盒盖带弹簧,一打开就弹射。A 是误解(pickle 反而更占空间),C/D 不是安全问题。

Q3. 为什么 .pth 模型文件可能携带恶意代码?

单选题 · easy

  • A.因为 PyTorch 使用 pickle 封装← 正确答案
  • B.因为权重文件没有做加密保护
  • C.因为模型训练数据集被污染过
  • D.因为模型在 GPU 上执行计算

答案:A。torch.save 底层调用 pickle 序列化,.pth 文件本质上是 pickle 字节流,恶意指令可以藏在其中,torch.load 时被触发。权重是否加密(B)和数据污染(C)都跟远程代码执行无关,GPU 计算(D)更是毫不相干。

Q4. reduce 返回 (callable, args),反序列化时会发生什么?

单选题 · mid

  • A.元组数据被存储为模型元数据
  • B.反序列化器自动执行 callable 函数← 正确答案
  • C.元组内容被转换为 JSON 字典
  • D.反序列化器忽略该可调用对象

答案:B。__reduce__ 是 pickle 协议中指定“如何重建对象”的钩子,返回的可调用对象和参数会在反序列化时自动执行。攻击者把 callable 设为 os.system 等危险函数,即可实现 RCE。这就是礼盒夹层里的弹簧——按设计工作,却被恶意利用。

Q5. pickle 文件中出现哪个模块的引用最值得警惕?

单选题 · mid

  • A.collections 集合工具类模块
  • B.subprocess 子进程调用模块← 正确答案
  • C.torch.nn 神经网络架构模块
  • D.functools 函数式工具模块

答案:B。subprocess 用于启动系统进程,是 RCE 最直接的武器;os、sys、builtins 同样危险。collections 和 functools 是纯数据操作模块,torch.nn 用于模型定义——正常模型加载流程中不会从 pickle 文件里调用 subprocess。

Q6. safetensors 能杜绝代码执行的根源是什么?

单选题 · mid

  • A.官方对上传模型进行了严格审核
  • B.文件采用加密算法保护内部数据
  • C.加载器拥有沙箱运行的权限隔离
  • D.文件结构只有数据没有任何指令流← 正确答案

答案:D。safetensors 设计为 JSON 头 + 纯二进制张量区,整个文件不含任何 opcode 或可调用对象,加载器只做“按偏移读字节”,没有执行代码的入口。就像透明收纳盒没有夹层可藏机关。A 是运营措施,B/C 是外部防护,都不是格式本身的特性。

Q7. 模型供应链投毒攻击中最常用的载体是什么?

单选题 · hard

  • A.在仓库 README 中嵌入钓鱼链接
  • B.修改开源数据集的样本标签
  • C.上传恶意 pickle 格式的模型权重← 正确答案
  • D.向框架官方仓库提交恶意代码

答案:C。把恶意 pickle 载荷伪装成“预训练权重”上传到 HuggingFace 等仓库,受害者一旦 torch.load 立即触发 RCE——“加载即中招”是供应链投毒的最高效形态。README 钓鱼需要用户主动点击,改数据集是数据污染而非 RCE,提交官方代码库门槛太高。

Q8. 关于静态扫描工具的局限性,以下说法正确的有哪些?

多选题 · hard

  • A.扫描只能识别已知的危险特征模式← 正确答案
  • B.攻击者可构造绕过特征检测的载荷← 正确答案
  • C.扫描结果可以替代沙箱隔离措施
  • D.扫描工具只能检查文件的文件头

答案:AB。静态扫描基于已知的危险模块和指令组合做特征匹配,属于“已知模式识别”,因此 A 对;攻击者可以通过间接引用、动态解析构造未知特征载荷绕过检测,B 也对。扫描不能替代沙箱(C 错),好的工具会解析全部 opcode 而非只看文件头(D 错)。安检机再强也拦不住所有新型违禁品,所以需要纵深防御。

Q9. 以下哪些属于防御 pickle 攻击的正确措施?

多选题 · mid

  • A.加载前先进行静态特征扫描检测← 正确答案
  • B.在沙箱隔离环境中加载不可信模型← 正确答案
  • C.只从自定义可信白名单渠道获取← 正确答案
  • D.将模型文件后缀名修改为 txt 格式

答案:ABC。扫描、沙箱、信任白名单是三层纵深防御,分别对应“检测已知威胁”、“隔离未知威胁”、“从源头减少威胁”。改后缀只改变文件名,不改变文件内容,pickle 加载器照样按 pickle 解析,恶意代码照常执行——包装纸换得再好看,蛇还是那条蛇。

Q10. 关于 Pickle 反序列化攻击链,下列说法正确的有哪些?

多选题 · extreme

  • A.恶意对象利用 reduce 指定执行函数← 正确答案
  • B.攻击者把恶意载荷伪装成模型权重← 正确答案
  • C.静态扫描可以识别全部类型的载荷
  • D.加载模型时触发恶意函数执行系统命令← 正确答案

答案:ABD。完整攻击链是:构造 __reduce__ 恶意对象 → pickle.dumps 生成载荷 → 伪装成模型权重上传到仓库 → 受害者 torch.load → 反序列化自动执行 callable → 系统命令在服务器上运行。A、B、D 分别对应攻击链的构造、投放、触发三个环节。静态扫描只识别已知特征,无法覆盖全部载荷类型,C 错误。