AI研究

从Hugging Face下载的预训练模型里可以被嵌入后门、数据窃取代码甚至反向Shell,加载即执行——当开发者把“下载模型”当成和“pip install”一样日常的操作时,模型文件的供应链安全到底该按代码依赖的标准来管,还是说pickle格式和权重的不透明性让传统软件供应链的整套检查手段从根上就失效了?

👤 为我痴狂 👁 4 阅读 ❤ 0 点赞 ➦ 0 分享 📅 2026-11-07
首页› AI› AI研究› 正文
从Hugging Face下载的预训练模型里可以被嵌入后门、数据窃取代码甚至反向Shell,加载即执行——当开发者把“下载模型”当成和“pip install”一样日常的操作时,模型文件的供应链安全到底该按代码依赖的标准来管,还是说pickle格式和权重的不透明性让传统软件供应链的整套检查手段从根上就失效了?

摘要:预训练模型的分发形态正在把“依赖引入”与“代码执行”合并成同一个动作。一个 .bin 或 .pt 文件在 torch.load 的默认语义下等价于一段待执行的 Python 字节码,而 safetensors 与 ONNX 只消除了其中一类风险。本文以“可执行性分级”为主线,先拆解 pickle 反序列化的攻击面与真实事件,再论证传统软件供应链检查(SBOM、哈希锁定、签名)在模型场景下的部分失效机制,最后给出一套可落地的分层治理路径:格式选择、静态扫描、权重差分、运行时沙箱与组织流程。核心结论是:模型供应链并非“传统手段全失效”,而是检查对象从“代码文本”迁移到“序列化图与权重张量”,需要重建检查面而非放弃检查。

目录

一、导语:一次 from_pretrained 到底执行了什么

在多数团队的日常里,加载一个开源模型只需要三行代码:导入 transformers、指定仓库名、调用 from_pretrained。这个动作在工程直觉上与 pip install 高度相似——都是从远端拉取制品、落到本地缓存、然后被当前进程使用。但两者的执行语义存在一个关键差异:pip install 安装的包在导入时执行模块顶层代码,而模型文件在“加载”这一步就可能触发反序列化回调,且这一过程发生在任何业务逻辑之前、发生在你还没来得及审查之前。

笔者在这里想先确立本文的分析主线:模型供应链的核心变量不是“文件是否来自可信域名”,而是“该文件在消费阶段的可执行性等级”。可执行性等级越高,传统基于文本与哈希的检查手段覆盖率越低;可执行性等级越低,检查手段越接近传统软件供应链。整篇文章的章节安排都围绕这条主线展开:先定义可执行性从何而来(第二、三章),再说明它如何让检查手段出现盲区(第四、五章),最后给出按可执行性分级设计的防御路径(第六、七章)。

需要提前说明的是,本文不主张“模型文件不能安全使用”,也不主张“传统供应链方法全部作废”。更准确的判断是:检查的对象发生了迁移——从源码文本迁移到序列化对象图,从依赖版本号迁移到权重张量的统计与结构特征。承认这一迁移,才谈得上重建有效的检查面。对于希望先建立直观认识的读者,可以配合阅读 Hugging Face 官方的安全文档与 picklescan 项目说明(Hugging Face Hub 安全文档、picklescan 仓库),这两份材料是理解后文机制的基础。

二、背景与问题定义:模型文件为何是“可执行制品”

2.1 三种主流分发格式的执行语义差异

要讨论“能不能按代码依赖来管”,先要把模型文件的格式谱系讲清楚。当前生态里最常见的三类制品,其执行语义差别极大,不能笼统称为“模型文件”。

格式 典型扩展名 加载时是否可执行任意代码 主要风险来源
Python pickle .bin / .pt / .pth / .ckpt 是(默认语义) 反序列化回调、__reduce__
safetensors .safetensors 否(纯张量容器) 权重投毒、元数据欺骗
ONNX .onnx 通常否,但自定义算子可引入 算子实现、外部数据文件
GGUF / TensorRT 引擎 .gguf / .engine 视运行时而定 运行时插件、序列化结构

这张表想说明的第一件事是:“模型”不是一个安全同质的类别。把 .bin 和 .safetensors 放在同一个“下载模型”的动作里讨论,会掩盖最关键的执行语义差异。pickle 是 Python 的对象序列化协议,它的设计目标就是“还原任意对象”,因此天然包含“构造对象时调用某函数”的能力;safetensors 的设计目标恰恰相反,它只存张量字节与极简头部,不承载可调用对象。本文评述:很多团队的安全讨论停留在“从哪下载”,但格式选择本身就是一个安全决策,且这个决策在下载之前就已经做出。

2.2 问题定义:把“加载”视为“执行”

如果把模型加载视为一次“执行”,那么它满足执行的两个要件:其一,它消费的是可被解释为指令或指令引用的数据;其二,它在当前进程的权限上下文中运行。pickle 加载满足这两点;safetensors 加载只满足“消费数据”,不满足“解释为指令”。因此本文把问题定义为:在模型消费链路上,哪些环节把数据提升为可执行语义,哪些环节只是数据搬运。这个定义的好处是它可以直接映射到工程控制点——凡是“数据提升为可执行语义”的环节,都必须有对应的检查或隔离;凡是纯数据搬运的环节,检查重点转向完整性与来源。

笔者认为,这个定义也解释了为什么“模型安全”讨论经常失焦:大家习惯用“文件是否恶意”来提问,但更可操作的问题是“这个文件在哪个消费环节获得了执行能力”。前者需要判定意图,后者只需要判定机制。机制是可检查的,意图不是。

三、攻击面解剖:pickle 反序列化的机制与真实事件

3.1 机制:__reduce__ 如何把数据变成命令

pickle 的反序列化不是“读字节还原结构”这么简单。协议中有一组 opcode 用于在还原过程中调用可调用对象,其中 REDUCE 是最常被引用的一个:它从栈上取出一个可调用对象和参数元组,执行调用,并把返回值压栈。一个类只要实现 __reduce__ 并返回 (callable, args),在反序列化时就会触发 callable(*args)。这意味着攻击者不需要修改 Python 解释器,也不需要目标程序存在逻辑漏洞,只需要构造一个 pickle 流,让还原过程调用 os.system、subprocess.Popen 或 eval 即可。

import pickle, os

class Payload:
    def __reduce__(self):
        # 反序列化时触发:等价于执行 os.system("id")
        return (os.system, ("id",))

blob = pickle.dumps(Payload())
# 将 blob 写入 .bin / .pt 文件,或嵌入权重字典的某个键
pickle.loads(blob)  # 加载即执行

上面这段代码是机制演示,不是攻击教程。它想强调的是:恶意载荷可以藏在“看起来像权重”的容器里。一个 .pt 文件本质上是一个 pickle 流,里面可以是一个 dict,键是层名、值是张量;也可以在这个 dict 里塞入任意对象。加载方调用 torch.load 时,整个流被还原,所有 __reduce__ 都会被触发。本文评述:这不是“漏洞”,而是 pickle 的设计特性;把它当漏洞去“修补”方向就错了,正确的方向是限制可执行语义的引入,或把加载放进隔离环境。

3.2 真实事件与研究脉络

公开研究里,pickle 模型的风险并非理论推演。2024 年前后,围绕 PyTorch 模型加载的供应链研究集中出现,其中较有代表性的是对模型仓库的大规模测量工作:研究者系统扫描了公开模型仓库中的 pickle 类文件,发现相当比例的制品包含可执行 opcode,并讨论了“模型即代码”的治理缺口(相关讨论可参考 Trail of Bits 关于 torch.load 与模型供应链的系列博客,以及 Fickling 工具的论文与文档)。需要说明的是,不同测量工作对“可疑”的判定口径不同,绝对比例不宜直接横向比较;本文引用这类结论时只取其定性方向——可执行 opcode 在公开模型中并不罕见,而非某个具体百分比。

另一条脉络是“模型投毒”与“后门”研究。BadNets 等工作证明了在训练数据中植入触发器可以让模型在特定输入下输出攻击者期望的结果;后续的权重分析研究进一步表明,某些后门在权重空间中留下可检测的统计痕迹。本文评述:把“反序列化执行”和“权重后门”混为一谈是常见误区,前者是加载期代码执行,后者是推理期行为操纵,两者的检测手段、缓解手段完全不同。本文主线之所以强调“可执行性分级”,正是为了把这两类风险分开治理,而不是用一个“模型安全”大筐装下所有问题。

要点:加载期风险看“是否可执行”,推理期风险看“行为是否被操纵”。前者靠格式与沙箱,后者靠数据与权重审计。两条线不能互相替代。

四、权重不透明性:后门、投毒与“看不见的载荷”

4.1 权重为何难以审查

即使把格式换成 safetensors,消除了加载期代码执行,权重本身仍然是一个高维、稠密、缺乏语义标注的数值集合。一个 7B 参数模型有约 70 亿个浮点数,人工审查不现实;自动化审查又缺乏“什么是正常权重”的严格定义。这带来一个结构性困难:代码可以逐行读,权重不能逐值读。代码审查依赖语义可读性,权重审查只能依赖统计特征、行为测试与差分对比。

笔者认为,这正是“传统供应链检查失效”说法的主要来源:SBOM 能列出依赖及其版本,但列不出“这个张量是否被微调过”;哈希能锁定文件,但锁不住“文件内容是否本来就恶意”。不过,说“失效”并不准确,更准确的说法是检查粒度与检查对象不匹配。传统手段检查的是“制品身份与来源”,模型场景还需要“制品行为与内容特征”,后者是新增检查面,不是前者的替代。

4.2 后门与投毒的工程含义

从工程角度看,权重后门的威胁模型与加载期执行不同。加载期执行一旦成功,攻击者获得的是当前进程权限,可以直接窃取凭据、建立反向连接;权重后门获得的是“模型在特定输入下的错误行为”,其危害取决于模型被用在哪里。一个被植入触发器的分类模型可能在正常测试集上表现无异常,但在含特定 token 的输入上稳定输出攻击者期望的标签。这类风险难以用“文件是否可执行”来判定,必须用行为测试与数据溯源来覆盖。

这里有一个常被忽略的边界条件:权重后门的检测高度依赖“你知道要找什么触发器”。开放集下的触发器搜索在计算上不可行,因此工程上更现实的做法是“来源可信 + 行为回归 + 差分对比”三件套,而不是追求“通用后门检测器”。本文评述:把后门检测宣传成通用能力,容易让团队产生虚假安全感;承认检测的边界,反而能推动更扎实的流程建设。

五、传统供应链检查为何部分失效:SBOM、哈希与签名的边界

5.1 SBOM 的覆盖盲区

SBOM(软件物料清单)的目标是回答“这个制品由哪些组件构成”。在模型场景里,SBOM 能覆盖的是训练框架版本、依赖库版本、数据集标识等外围信息,但很难覆盖“权重文件内部的序列化结构”。一个模型仓库可以附带完整的 SBOM,同时其 .bin 文件里仍然含有可执行 opcode。本文评述:SBOM 是必要的,但它回答的是“用了什么”,不是“里面有什么会执行”。把 SBOM 当成模型安全的充分条件,是典型的检查对象错配。

5.2 哈希锁定与签名的能力与局限

哈希锁定解决的是“我拿到的文件是否与预期一致”,签名解决的是“这个文件是否由某个身份发布”。两者都是完整性机制,不是内容安全机制。一个恶意模型完全可以被合法签名,只要发布者账号被攻破或发布者本身不可信。本文评述:在模型场景里,签名机制的价值在于“溯源与追责”,不在于“判定安全”。工程上应当把签名放在“来源可信”这一层,而不是放在“内容无害”这一层。

检查手段 能回答的问题 在模型场景的局限
SBOM 由哪些组件构成 不覆盖序列化结构与权重内容
哈希锁定 文件是否与预期一致 不判定内容是否恶意
签名 由谁发布 不保证发布者未被攻破
静态扫描 是否含可疑 opcode 存在绕过与误报,需持续更新规则

六、可落地路径:五层防御与工程操作步骤

6.1 第一层:格式选择与转换

最直接、成本最低的缓解是优先使用不可执行格式。工程上可以这样做:优先拉取 .safetensors;若仓库只提供 .bin,在隔离环境中先做一次“只读转换”,把权重导出为 safetensors,再进入业务链路。转换本身需要在沙箱内完成,因为转换过程仍要加载 pickle。操作步骤建议:①在无网络、无凭据的容器中执行转换;②转换脚本固定版本并纳入代码审查;③转换产物做哈希登记,后续业务只消费登记过的产物。

本文评述:格式转换不是“银弹”,它把一次不可信加载限制在隔离环境里,但没有消除“转换时被投毒”的可能。因此转换环境必须与业务环境隔离,且转换产物要进入后续的权重审计流程。对于想了解 safetensors 设计动机的读者,可参考其官方说明(safetensors 仓库)。

6.2 第二层:静态扫描与 opcode 白名单

对必须消费 pickle 的场景,静态扫描是第一道闸门。可用工具包括 picklescan、Fickling 等,它们的基本思路是解析 pickle 流、提取 opcode 与全局引用、按规则判定可疑调用。工程上建议把扫描做成 CI 步骤:模型文件进入制品库前必须通过扫描,扫描规则以“允许列表”为主而非“拒绝列表”为主——只允许已知安全的全局引用,其余一律阻断。

# 示例:在 CI 中对模型文件做静态扫描(示意,非完整脚本)
pip install picklescan
picklescan --path ./model_artifacts/  # 输出可疑 opcode 与全局引用
# 将退出码接入流水线,非零即阻断发布

需要提醒的是,静态扫描存在固有局限:pickle 是图灵完备的序列化格式,规则可以被构造绕过;扫描器也可能对合法但少见的对象产生误报。本文评述:把静态扫描定位为“降低概率的闸门”而非“证明安全的证书”,是更诚实也更工程化的态度。

6.3 第三层:权重差分与行为回归

针对权重后门,工程上可操作的手段是“差分 + 回归”。差分指把待用模型与已知基线模型在同一组张量上做统计对比,关注异常离群层、异常范数分布;回归指维护一组行为测试用例,覆盖安全敏感输入,观察输出是否出现异常偏移。操作步骤:①为每个模型建立基线指纹(层名、形状、统计量);②新模型入库时自动比对,超出阈值触发人工复核;③行为回归用例纳入版本管理,随业务迭代扩充。

本文评述:差分与回归都不能证明“无后门”,但能显著提高投毒的暴露概率,尤其是当攻击者复用公开基座、只做小范围微调时,差分更容易捕捉到局部异常。这里的数据均为方法描述,不涉及具体实验数值;若团队要做量化阈值,应基于自身模型族与业务分布做标定,不宜直接套用他人阈值。

6.4 第四层:运行时沙箱与最小权限

无论格式与扫描做到什么程度,加载环节都应假设“可能被突破”。运行时控制包括:在无网络命名空间中加载、以非特权用户运行、限制可写路径、禁用不必要的系统调用、对加载进程做短生命周期管理。操作步骤:①用容器或 microVM 承载加载与推理;②默认拒绝出站网络,仅按需放行;③凭据不进入加载进程环境变量;④加载完成后丢弃加载进程,业务进程只消费已序列化的安全中间产物。

笔者认为,这一层的价值常被低估。很多团队把精力放在“检测恶意文件”,却忽略了“即使检测失败,损失也可控”。最小权限与网络隔离是典型的“失效安全”设计:检测可能漏,但隔离能兜底。

6.5 第五层:组织流程与来源治理

最后一层是流程:建立模型准入清单、明确“允许的发布者与格式”、把模型纳入与代码依赖同级的变更管理、记录模型来源与转换链路。操作步骤:①维护内部模型登记表(来源、格式、哈希、扫描结论、审计人);②对新增发布者做准入评审;③把模型更新纳入变更评审,与代码发布同流程;④定期复扫已入库模型,规则更新后回溯。

本文评述:流程层的难点不在技术,而在“把模型当依赖管”的组织共识。一旦模型进入登记表、进入变更评审,它就不再是“随手下载的权重文件”,而是有身份、有审计、有责任的制品。这正是本文主线想落到的终点:用可执行性分级驱动检查强度,用流程把检查固化。

七、对比与局限:格式、扫描器与沙箱的能力边界

把五层防御放在一起看,会发现它们覆盖的风险类型并不相同。格式选择与静态扫描主要针对加载期执行;权重差分与行为回归主要针对推理期操纵;沙箱与流程则同时提供兜底与可追溯性。任何单一层都不足以覆盖全部风险,这一点在工程上必须说清楚,否则容易产生“上了某个工具就安全了”的错觉。

防御层 主要覆盖 明显局限
格式选择/转换 加载期代码执行 转换环境本身需隔离
静态扫描 已知可疑 opcode 可绕过、有误报
权重差分/回归 局部投毒与行为偏移 依赖基线质量,非通用证明
运行时沙箱 突破后的损失控制 增加运维复杂度
流程与来源治理 可追溯与责任落实 依赖组织执行力

还有一个常被忽视的边界:模型仓库的托管方能力有限。平台可以做扫描、可以做格式提示、可以下架恶意仓库,但它无法替使用者做“是否可信”的最终判断。本文评述:把平台扫描当成充分条件,与把 SBOM 当成充分条件一样,都是检查对象错配。平台提供的是信号,不是保证。

对于希望进一步动手的读者,可以按以下顺序做一次小范围演练:先在一个隔离容器里拉取一个只提供 .bin 的模型,用 picklescan 观察输出;再把它转换为 safetensors;最后用同一组输入对比转换前后的推理输出,确认转换没有改变行为。这个演练不追求覆盖所有风险,但能把“可执行性分级”从概念变成手感。相关工具与教程可参考 Transformers 仓库 与 Fickling 文档。

八、结论与展望:把“可执行性”纳入依赖治理

回到标题提出的问题:模型供应链该按代码依赖的标准来管,还是 pickle 与权重的不透明性让传统手段从根上失效?本文的结论是两者都不完全成立。传统手段没有“从根上失效”,它们失效的是“覆盖范围”而非“基本逻辑”:来源、完整性、可追溯仍然有效,只是不再充分;pickle 与权重不透明确实制造了新的检查盲区,但盲区可以被“格式选择 + 静态扫描 + 权重审计 + 运行时隔离 + 流程”组合覆盖。

笔者认为,更可操作的治理原则是把“可执行性等级”作为模型准入的一等属性:高可执行性制品走最严格的隔离与扫描,低可执行性制品走完整性与来源检查。这个原则不依赖某个具体工具,也不依赖某个平台的扫描能力,它可以直接写进团队的准入清单。展望来看,随着 safetensors 的普及与模型签名、模型卡规范的成熟,模型供应链会逐步向“可验证制品”演进;但在过渡期,工程团队仍需自己承担“加载即执行”的风险判断。

最后需要强调,本文所有机制描述均基于公开协议与公开工具的行为,不涉及对任何具体仓库的恶意指控;涉及比例与趋势的判断均取自公开研究的定性方向,具体数值请以原始文献为准。

九、主要参考文献与声明

本文参考的公开资料方向包括:Python pickle 协议规范与安全讨论、PyTorch 模型加载文档、safetensors 设计说明、ONNX 规范、模型供应链测量研究、模型投毒与后门研究(BadNets 系列)、模型仓库安全文档与扫描工具文档等。受篇幅所限,正文以机制与工程路径为主,未逐条展开文献细节;引用时请以原始文献为准。主要参考文献(节选,按主题归类):

  1. Python 官方文档:pickle 模块与协议说明。
  2. PyTorch 官方文档:torch.load 与 weights_only 参数说明。
  3. Hugging Face Hub 安全文档与模型卡规范。
  4. safetensors 项目设计说明与仓库文档。
  5. ONNX 规范与算子文档。
  6. Trail of Bits:Fickling 工具文档与模型供应链系列博客。
  7. picklescan 项目文档与规则说明。
  8. BadNets 等模型后门研究(公开论文)。
  9. 模型供应链测量与治理相关公开研究(含 2023—2025 年文献)。

文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。

内容仅供学习参考。如需引用,请以原始文献为准。  全文约 6800 字  |  参考文献 60 篇(主要 9 篇)。
🔒 复制本站文章内容需登录并达到 L3。当前:未登录

分享到

💬
微信
📷
朋友圈
🐧
QQ好友
🌐
QQ空间
👁
微博
📌
钉钉
🔗
复制链接
📑
复制图文

微信扫一扫分享

打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。

💬 评论 (0)

评论功能已关闭

⏸️ 本站暂未开放评论功能,不能进行评论,此为规划的后续开发预留
首页| 关于本网| 网站声明| 联系我们| 网站纠错| 服务| 网站地图
黔ICP备19010680号-1  |  邮箱:six528528@163.com
贵公网安备 52010302001819号
Copyright 2019-2026 http://www.databrush.com/ All rights reserved.
QQ
QQ扫一扫
Logo
DBN数据刷