1. 机器学习模型共享:一场被低估的安全危机

如果你在Hugging Face Hub、TensorFlow Hub或者Kaggle Models上下载过一个预训练模型,然后直接 model.load() 或者 from_pretrained() ,那么恭喜你,你可能已经无意中在自己的机器上执行了一段来自陌生人的代码。这听起来像是危言耸听,但这就是当前机器学习模型共享生态中,一个被“安全模式”、“安全扫描”等标签所掩盖的残酷现实。作为一名在AI安全领域摸爬滚打多年的从业者,我见过太多团队和研究者在模型复用上栽了跟头,轻则环境被污染、数据泄露,重则整个计算集群沦为矿机或攻击跳板。

问题的核心在于,我们习惯于将模型视为一种“数据”或“参数集合”,但本质上,一个可加载、可执行的模型文件,尤其是那些由 pickle 、 torch.save 或特定框架格式保存的模型,就是一段 代码 。加载一个不受信任的模型,其风险等级与执行一段不受信任的二进制程序或脚本无异。然而,主流框架和模型中心提供的安全叙事,往往通过“安全模式”开关或“已扫描”标签,给用户营造了一种虚假的安全感。我们的实证研究和漏洞挖掘表明,这种安全感是危险的,许多被标榜为安全的机制本身存在可被绕过的漏洞,而用户对此的认知严重不足。本文将深入拆解模型共享链条中的安全陷阱,从框架层面的序列化漏洞,到模型中心的安全标签误导,并结合我们发现的真实0-day漏洞(已获CVE编号),为你呈现一幅完整且不容乐观的安全图景。无论你是算法工程师、MLOps负责人还是安全研究员,理解这些风险并采取正确措施,是避免成为下一个受害者的第一步。

2. 模型供应链安全:从软件到AI的范式迁移

2.1 软件供应链安全的核心理念

在传统软件开发中,供应链安全关注的是从源代码、第三方库、构建工具到最终交付物(如Docker镜像、可执行文件)的完整链条。攻击者可能在任何环节注入恶意代码,例如污染开源库(如著名的 event-stream 事件)、劫持包管理器(如Typosquatting攻击)、或篡改CI/CD流水线。防御思路围绕 完整性验证 (如代码签名、哈希校验)、 来源可信 (如使用可信源、审核依赖)和 最小权限 (如沙箱执行)展开。

当我们将视角转向机器学习,模型成为了新的“交付物”。模型供应链始于训练代码和数据,经过训练产出模型权重和结构,再通过序列化(Serialization)保存为文件,最终经由模型中心(Hub)分发,被下游用户加载并用于推理或微调。这个链条引入了独特的安全挑战:模型文件不再是简单的数据,而是 包含逻辑的序列化对象 。以Python生态中最常见的 pickle 格式为例,它本质上是一个用于重建Python对象栈的虚拟机指令集。反序列化(即加载)一个 .pkl 文件时,Python解释器会忠实地执行其中定义的指令,这自然包括了潜在的任意代码执行。

2.2 主流框架的序列化“原罪”

几乎所有主流ML框架的默认或历史序列化方式都继承了这一风险:

  • PyTorch ( torch.save / torch.load ) : 默认使用 pickle 。尽管PyTorch文档明确警告“不要加载不受信任的来源”,但简便性使其成为社区事实上的标准。
  • TensorFlow/Keras (SavedModel, .h5 , .keras ) : Keras在2.13版本前,其HDF5( .h5 )格式的 Lambda 层可以存储并反序列化任意Python函数。即便引入了 safe_mode ,其实现也存在缺陷(后文详述)。
  • scikit-learn ( joblib / pickle ) : 默认使用 joblib ,而 joblib 后端通常也是 pickle 。其官方文档的“安全持久化”章节篇幅短小,警示性强。
  • XGBoost/LightGBM : 同样依赖 pickle 进行模型序列化。

这种对 pickle 的广泛依赖,构成了模型供应链安全的“原罪”。框架开发者并非不知其风险,但出于兼容性、灵活性和历史包袱的考虑,安全往往让位于易用性。

注意 : pickle 的风险并非秘密。其官方文档开篇就警告:“ pickle 模块并不安全。只对你信任的数据进行反序列化。” 问题在于,在模型共享的语境下,“信任”变得极其模糊。你信任Hugging Face上一个有1000颗星的仓库作者吗?你信任Kaggle比赛冠军发布的模型吗?这种基于声誉的信任模型在软件供应链中已被证明是脆弱的。

2.3 “安全”模式的兴起与局限

为了缓解风险,框架开始引入所谓的“安全”加载机制:

  1. Keras safe_mode : 从Keras 2.13开始引入,旨在阻止反序列化不安全的Lambda层。当 safe_mode=True 时,加载器会尝试阻止包含任意代码的层。
  2. Skops trusted=False : 作为 scikit-learn 模型的安全序列化库,Skops默认将 trusted 参数设为 False ,试图通过白名单机制限制可加载的类。
  3. ONNX : 作为一种跨平台的开放格式,ONNX定义了一个独立的计算图表示,不直接嵌入Python代码,理论上更安全。但其运行时实现和转换工具链也可能存在漏洞。
  4. SafeTensors : 由Hugging Face推动的一种 仅存储张量权重 的简单格式。它彻底放弃了存储模型架构的能力,从而从根本上杜绝了代码执行的可能。用户需要额外通过架构定义文件(通常是Python代码)来加载权重。这是一种“安全但受限”的典范。

这些机制的共同点是试图在 灵活性 和 安全性 之间做出权衡。然而,我们的评估发现,许多这类机制要么可以被绕过,要么其安全保证被严重高估了。

3. 框架安全机制深度评估与漏洞挖掘

我们系统评估了TensorFlow/Keras、PyTorch、scikit-learn(及Skops)和XGBoost的模型加载方法。评估标准包括:是否默认安全、是否有可选的安全模式、该模式的实际有效性、以及其设计是否将安全责任转移给了用户。

3.1 Keras safe_mode :被攻破的“安全门”

Keras的 safe_mode 被宣传为阻止恶意模型加载的关键特性。我们的研究发现,它存在多个逻辑缺陷,可被组合利用以实现任意代码执行。我们发现了两个关键的0-day漏洞(已分配CVE编号),并已协同谷歌安全团队完成披露与修复。

漏洞KV.1:利用 inbound_nodes 注入任意函数调用

Keras的模型配置( config.json )中,每个层都有一个 inbound_nodes 字段,用于定义层的输入输出关系。在 safe_mode 下,Keras会检查层的 module 和 class_name 是否在白名单内(如 keras.layers )。然而,攻击者可以构造一个恶意层,其 module 为 subprocess , class_name 为 run ,并将攻击参数(如 /bin/sh )放入 inbound_nodes 的 args 列表中。由于 safe_mode 的检查逻辑未能深入验证 inbound_nodes 的结构和内容,在模型构建过程中,Keras会尝试将这个配置解析为层调用,从而触发 subprocess.run(["/bin/sh"]) 。这本质上是因为Keras将模型配置视为一个待实例化的对象图,而 safe_mode 的检查粒度不够细,未能拦截对非Keras模块的调用。

漏洞KV.2:通过Lambda层动态禁用 safe_mode 自身

这是一个更精巧的绕过方式。Keras内部有一个全局状态字典,其中包含 safe_mode_saving 等标志。我们构造了一个特殊的 Lambda 层,其函数指向一个用于设置全局属性的内部函数 set_global_attribute 。通过精心设置参数,该 Lambda 层在模型加载的早期阶段被执行,将 safe_mode_saving 设置为 False 。一旦这个标志被关闭,后续加载的其��恶意层(包括利用KV.1的层)将不再受到 safe_mode 的检查。这就好比保安的开关被模型内部的一个机关给关掉了。

实操心得 :这些漏洞的根源在于, safe_mode 试图在一个动态、复杂的对象图构建过程中实施静态的、基于字符串匹配的检查。当模型配置本身可以包含改变加载器行为的逻辑时,这种防御是脆弱的。修复方案需要将安全边界前移,在反序列化JSON配置的阶段就进行严格的语法和语义限制,或者采用完全不可变的模型表示格式。

3.2 Skops的安全白名单与绕过

Skops的设计理念更先进一些。它定义了一个自定义的序列化格式和一套“节点”(Node)类型(如 ObjectNode , MethodNode ),并维护一个 trusted 参数和模块/类的白名单。默认 trusted=False 时,只允许加载 sklearn 及其子模块中的类。然而,其信任链仍然可以被打破。

漏洞SV.1:利用方法链访问 __builtins__

Skops允许序列化对对象方法的调用( MethodNode )。攻击者可以首先实例化一个白名单内的对象(如 sklearn.discriminant_analysis.QuadraticDiscriminantAnalysis ),然后通过 MethodNode 调用它的某个方法(如 decision_function )。关键在于,这个方法返回的如果是一个可调用对象,第二个 MethodNode 可以继续对这个返回值进行操作。我们构造了一个攻击链,使得第二个 MethodNode 的目标是获取 __builtins__ 字典——这是Python中所有内置函数的集合。一旦获得了 __builtins__ ,攻击者就可以调用 __import__('os').system('id') 等危险函数。Skops的检查只验证了第一个节点的类型是白名单内的,但没有深度遍历整个调用链的最终效果。

漏洞SV.2:混淆操作符与类型字符串

Skops的序列化数据中包含一个 __loader__ 字段,指定用哪种“节点”加载器来重建对象。我们发现了 OperatorFuncNode 这个加载器,它用于处理像 operator.add 这样的函数。攻击者可以构造一个 schema.json ,其中 __loader__ 为 OperatorFuncNode ,但 __module__ 和 __class__ 字段拼接起来是一个看似合法的、白名单内的路径(如 sklearn.SGDRegressor.call )。Skops的验证逻辑可能只检查了类型字符串是否以白名单模块开头,而 OperatorFuncNode 在解析时,实际会去导入 operator 模块并获取 call 函数(这是一个内置操作符)。 operator.call 可以用于调用任何可调用对象,从而成为攻击跳板。

这些漏洞揭示了一个深层问题: 基于白名单的沙箱机制在Python这种高度动态和反射能力极强的语言中极难实现完备性 。攻击面不仅包括直接的对象实例化,还包括属性访问、方法调用、操作符重载等所有元编程途径。

3.3 其他框架的“责任转移”模式

  • PyTorch : 其安全立场最为“坦诚”——官方文档明确声明 torch.load 使用 pickle ,且用户应对数据来源负责。它没有提供内置的“安全模式”,将风险完全交给了用户社区和上层工具(如模型中心的安全扫描)。
  • XGBoost : 与PyTorch类似,其Python接口的序列化依赖 pickle 。它提供了一个 validate_features 参数用于检查输入数据特征是否与模型匹配,但这与防止代码执行无关。
  • ONNX : 格式本身相对安全,但漏洞可能存在于ONNX运行时(ORT)的实现中,或者存在于从PyTorch/TensorFlow模型转换为ONNX格式的工具链中。一个恶意的转换脚本可以在转换过程中注入后门。

下表总结了各框架模型加载方法的安全姿态:

框架/库 默认序列化方法 是否有“安全”模式? 安全责任方 核心风险
PyTorch Pickle 无 用户 任意代码执行(明确警告)
TensorFlow SavedModel Protocol Buffer 无(但结构固定) 框架/用户 相对较低,但依赖Proto解析器安全
Keras ( .keras / .h5 ) JSON+HDF5/Pickle safe_mode (可绕过) 框架(但机制有缺陷) Lambda层、全局状态篡改
scikit-learn Pickle (via joblib) 无 用户 任意代码执行
Skops 自定义安全格式 trusted=False (可绕过) 库(但白名单不完备) 通过方法链或操作符混淆绕过白名单
ONNX Protocol Buffer 无(格式本身限制) 运行时实现 运行时漏洞、转换工具链风险
SafeTensors 仅张量二进制格式 不适用 (无代码) 格式设计 几乎为零(需额外加载架构)

4. 模型中心的安全叙事与用户信任错觉

框架层面的漏洞是技术问题,而模型中心(Hub)的安全实践和宣传则深刻影响着用户的行为和认知。我们以Hugging Face Hub为主要研究对象,结合用户调研,发现了安全宣传与实际效果之间的巨大落差。

4.1 “安全扫描”的局限性

Hugging Face Hub集成了安全扫描功能,例如与Protect AI合作,使用ClamAV等工具扫描上传的文件。这无疑是一个积极的举措。然而,我们的评估揭示了其局限性:

  1. 扫描覆盖不全 :扫描主要针对已知的恶意软件签名和明显的恶意文件。对于专门为绕过模型加载机制而精心构造的、看似正常的模型配置文件(如我们之前描述的恶意 config.json ),静态扫描工具很难检测其恶意意图。这类似于传统的杀毒软件难以检测零日漏洞利用。
  2. 结果标签的误导性 :当一个模型通过扫描后,Hub会显示一个“安全”或“已扫描”的标签。我们的用户调研(UP.2和UP.3)显示, 超过90%的非网络安全背景但有过模型共享经验的参与者,在看到 safe_mode 启用或Hub显示安全扫描后,认为任意代码执行不再是值得担心的问题 。他们将平台提供的“安全”标签等同于“绝对安全”,这是一种危险的信任转移。
  3. 响应滞后性 :即使扫描器未来能够检测到新型攻击,从漏洞被发现、到特征码更新、再到所有存量模型被重新扫描,存在一个时间窗口。在此期间,恶意模型可能已被大量下载。

注意事项 :模型中心的“安全扫描”标签,应被理解为“未发现已知威胁”,而不是“此模型无害”。这就像邮件系统的垃圾邮件过滤,它过滤了大部分垃圾邮件,但绝不能保证收件箱里的邮件都是安全的。

4.2 过度简化的安全沟通

框架和Hub在文档和UI中使用的语言加剧了这一问题。诸如“安全模式”、“安全加载”、“受信任的模型”等术语,没有清晰地传达其有限的安全保证。它们没有说明“安全”是针对哪种威胁模型(例如,防止代码执行,但不防止模型本身是带有后门的),也没有说明其潜在的绕过可能性。

这种过度简化导致了 安全错觉 。用户,特别是那些专注于模型性能而非安全的研究人员和工程师,会倾向于相信这些抽象,从而放松警惕。他们可能不会去审查模型来源(例如,查看作者信誉、提交历史),也不会在沙箱环境中先行测试模型,而是直接在生产或开发环境中加载。

4.3 用户调研揭示的认知差距

我们设计了一项调查,旨在了解ML从业者对模型加载安全风险的认知。结果令人担忧:

  • 高估机制有效�� :大部分受访者认为启用 safe_mode 或从标有安全标签的Hub下载模型能“显著降低”或“完全消除”代码执行风险。
  • 低估自身责任 :许多用户认为安全主要是框架和平台的责任,自己只需选择“安全”的选项即可。
  • 混淆安全属性 :部分用户无法区分“模型输出不正确”和“模型加载时执行恶意代码”是两种完全不同性质的风险。

这种认知差距是比单纯的技术漏洞更根本的威胁。当用户群体普遍缺乏风险意识时,整个生态系统的抵抗力是薄弱的。

5. 构建真正安全的模型加载实践指南

认识到现有机制的不足后,我们不能因噎废食。模型共享是ML进步的基石。关键在于采取一套纵深防御策略,将风险降到可接受的水平。

5.1 原则:将模型视为不可信代码

这是最根本的心态转变。在加载任何来自外部(包括知名Hub)的模型前,默认其可能是恶意的。基于此原则,采取以下措施:

  1. 来源审查 :

    • 验证发布者 :检查模型作者的历史、声誉、所属机构。一个新建的、无历史的账号发布的模型应引起警惕。
    • 检查模型卡片 :完整的文档、清晰的许可证、可复现的训练步骤是可信度的积极信号。
    • 查看社区反馈 :关注GitHub Issues、讨论区是否有关于该模型的异常报告。
  2. 环境隔离 :

    • 使用沙箱/容器 :始终在隔离的环境中首次加载和测试模型,例如Docker容器、虚拟机或云端的临时实例。确保该环境没有敏感数据,网络访问受限。
    • 专用推理环境 :生产环境中,将模型加载和服务部署在与核心业务数据、数据库隔离的独立环境中,仅通过定义良好的API进行交互。

5.2 技术选型:优先选择“数据格式”

在格式选择上,安全性应优先于便利性。

  • 首选:SafeTensors :对于PyTorch等框架,积极拥抱SafeTensors格式。它强制将架构(代码)与权重(数据)分离。你需要显式地提供模型定义代码(这本身也需要审查),然后加载权重。这从根本上消除了通过模型文件执行代码的可能。
  • 次选:ONNX :如果需要跨框架部署,ONNX是一个较好的选择。确保从可信的转换流程生成ONNX模型,并使用官方维护的运行时。
  • 审慎使用:框架原生格式 + 额外加固 :
    • 如果必须使用PyTorch的 .pt 文件,考虑使用 pickle 的 Unpickler 子类并重写 find_class 方法,严格限制可加载的模块。但这需要深厚的安全知识。
    • 对于Keras,确保使用最新版本(修复了我们披露的漏洞),并将 safe_mode 视为一道 可能被绕过的额外防线 ,而非绝对保障。
    • 彻底避免使用 scikit-learn 的默认 joblib/pickle 持久化来共享不受信任的模型。考虑使用Skops(保持更新,并了解其限制),或手动导出模型参数为纯数据格式(如JSON、NPZ)。

5.3 流程整合:安全左移

将安全检查整合到你的MLOps流水线中:

  1. 入库扫描 :在将外部模型引入内部仓库前,建立自动扫描流程。除了使用Hub的扫描,可以集成自定义的静态分析工具,检查模型文件中是否包含可疑的字符串模式(如 subprocess 、 eval 、 exec 、 os.system 等)。
  2. 动态沙箱测试 :在隔离环境中运行模型,监控其进程行为(如文件系统操作、网络连接、子进程生成)。工具如 strace 、 sysdig 或容器运行时监控可以帮助发现异常行为。
  3. 版本锁定与哈希校验 :一旦确定使用某个模型版本,记录其完整的来源URL和文件哈希值(如SHA256)。后续加载时校验哈希,防止中间人篡改或仓库内容被恶意更新。

5.4 对框架和平台开发者的呼吁

  • 清晰的沟通 :避免使用“安全模式”这种绝对化的术语。改为“受限加载模式”或“反序列化保护”,并在文档中明确列出其已知限制和潜在绕过方式。
  • 默认安全 :新版本的框架应默认启用最严格的加载选项(如Keras的 safe_mode=True ),并让用户为兼容性而主动选择降级。
  • 推动格式迁移 :大力推广并简化SafeTensors等安全格式的使用。为旧格式提供明确的、易于使用的转换工具和警告。
  • 漏洞奖励计划 :建立并宣传针对模型加载机制的安全漏洞奖励计划,鼓励社区参与安全建设。

6. 未来展望与待解难题

模型共享的安全问题远未解决,随着AI的发展,新的挑战不断涌现。

  • 大语言模型(LLM)专用格式 :GGUF、GPTQ、AWQ等格式专注于高效的量化与推理。它们的威胁模型可能有所不同——攻击者可能更倾向于在训练数据或微调过程中植入后门,而非在模型加载时执行代码。然而,这些格式的解析器本身也可能成为攻击面。
  • 第三方序列化库 :像 safetensors 这样的社区驱动库越来越流行。需要持续评估其安全设计和实现,确保它们不会引入新的漏洞。
  • 自动化扫描的进化 :当前的静态扫描不足。需要发展动态分析、行为分析和基于机器学习的异常检测技术,用于识别更隐蔽的恶意模型。例如,监控模型在受限沙箱中的推理行为,寻找与宣称任务不符的系统调用。
  • 标准化与认证 :业界需要推动模型供应链安全标准的建立,类似于软件物料清单(SBOM)。模型应附带其完整的“成分”清单(训练数据、代码、依赖库的哈希)、构建环境和安全审计报告。

机器学习模型共享的便利性极大地加速了创新,但其底层的安全基础设施并未跟上发展的步伐。我们揭示了在“安全”标签之下隐藏的漏洞和认知误区。真正的安全不在于一个神奇的开关或标签,而在于一整套严谨的实践、清醒的认知和持续改进的工具链。作为从业者,我们必须停止将模型视为单纯的数据,开始像对待关键任务软件一样对待它们。这条路很长,但第一步,就是从认清现状开始。

更多推荐