机器学习模型加载安全:从序列化漏洞到防御实践
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 “安全”模式的兴起与局限
为了缓解风险,框架开始引入所谓的“安全”加载机制:
-
Keras
safe_mode: 从Keras 2.13开始引入,旨在阻止反序列化不安全的Lambda层。当safe_mode=True时,加载器会尝试阻止包含任意代码的层。 -
Skops
trusted=False: 作为scikit-learn模型的安全序列化库,Skops默认将trusted参数设为False,试图通过白名单机制限制可加载的类。 - ONNX : 作为一种跨平台的开放格式,ONNX定义了一个独立的计算图表示,不直接嵌入Python代码,理论上更安全。但其运行时实现和转换工具链也可能存在漏洞。
- 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等工具扫描上传的文件。这无疑是一个积极的举措。然而,我们的评估揭示了其局限性:
-
扫描覆盖不全
:扫描主要针对已知的恶意软件签名和明显的恶意文件。对于专门为绕过模型加载机制而精心构造的、看似正常的模型配置文件(如我们之前描述的恶意
config.json),静态扫描工具很难检测其恶意意图。这类似于传统的杀毒软件难以检测零日漏洞利用。 -
结果标签的误导性
:当一个模型通过扫描后,Hub会显示一个“安全”或“已扫描”的标签。我们的用户调研(UP.2和UP.3)显示,
超过90%的非网络安全背景但有过模型共享经验的参与者,在看到
safe_mode启用或Hub显示安全扫描后,认为任意代码执行不再是值得担心的问题 。他们将平台提供的“安全”标签等同于“绝对安全”,这是一种危险的信任转移。 - 响应滞后性 :即使扫描器未来能够检测到新型攻击,从漏洞被发现、到特征码更新、再到所有存量模型被重新扫描,存在一个时间窗口。在此期间,恶意模型可能已被大量下载。
注意事项 :模型中心的“安全扫描”标签,应被理解为“未发现已知威胁”,而不是“此模型无害”。这就像邮件系统的垃圾邮件过滤,它过滤了大部分垃圾邮件,但绝不能保证收件箱里的邮件都是安全的。
4.2 过度简化的安全沟通
框架和Hub在文档和UI中使用的语言加剧了这一问题。诸如“安全模式”、“安全加载”、“受信任的模型”等术语,没有清晰地传达其有限的安全保证。它们没有说明“安全”是针对哪种威胁模型(例如,防止代码执行,但不防止模型本身是带有后门的),也没有说明其潜在的绕过可能性。
这种过度简化导致了 安全错觉 。用户,特别是那些专注于模型性能而非安全的研究人员和工程师,会倾向于相信这些抽象,从而放松警惕。他们可能不会去审查模型来源(例如,查看作者信誉、提交历史),也不会在沙箱环境中先行测试模型,而是直接在生产或开发环境中加载。
4.3 用户调研揭示的认知差距
我们设计了一项调查,旨在了解ML从业者对模型加载安全风险的认知。结果令人担忧:
-
高估机制有效��
:大部分受访者认为启用
safe_mode或从标有安全标签的Hub下载模型能“显著降低”或“完全消除”代码执行风险。 - 低估自身责任 :许多用户认为安全主要是框架和平台的责任,自己只需选择“安全”的选项即可。
- 混淆安全属性 :部分用户无法区分“模型输出不正确”和“模型加载时执行恶意代码”是两种完全不同性质的风险。
这种认知差距是比单纯的技术漏洞更根本的威胁。当用户群体普遍缺乏风险意识时,整个生态系统的抵抗力是薄弱的。
5. 构建真正安全的模型加载实践指南
认识到现有机制的不足后,我们不能因噎废食。模型共享是ML进步的基石。关键在于采取一套纵深防御策略,将风险降到可接受的水平。
5.1 原则:将模型视为不可信代码
这是最根本的心态转变。在加载任何来自外部(包括知名Hub)的模型前,默认其可能是恶意的。基于此原则,采取以下措施:
-
来源审查 :
- 验证发布者 :检查模型作者的历史、声誉、所属机构。一个新建的、无历史的账号发布的模型应引起警惕。
- 检查模型卡片 :完整的文档、清晰的许可证、可复现的训练步骤是可信度的积极信号。
- 查看社区反馈 :关注GitHub Issues、讨论区是否有关于该模型的异常报告。
-
环境隔离 :
- 使用沙箱/容器 :始终在隔离的环境中首次加载和测试模型,例如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)。
-
如果必须使用PyTorch的
5.3 流程整合:安全左移
将安全检查整合到你的MLOps流水线中:
-
入库扫描
:在将外部模型引入内部仓库前,建立自动扫描流程。除了使用Hub的扫描,可以集成自定义的静态分析工具,检查模型文件中是否包含可疑的字符串模式(如
subprocess、eval、exec、os.system等)。 -
动态沙箱测试
:在隔离环境中运行模型,监控其进程行为(如文件系统操作、网络连接、子进程生成)。工具如
strace、sysdig或容器运行时监控可以帮助发现异常行为。 - 版本锁定与哈希校验 :一旦确定使用某个模型版本,记录其完整的来源URL和文件哈希值(如SHA256)。后续加载时校验哈希,防止中间人篡改或仓库内容被恶意更新。
5.4 对框架和平台开发者的呼吁
- 清晰的沟通 :避免使用“安全模式”这种绝对化的术语。改为“受限加载模式”或“反序列化保护”,并在文档中明确列出其已知限制和潜在绕过方式。
-
默认安全
:新版本的框架应默认启用最严格的加载选项(如Keras的
safe_mode=True),并让用户为兼容性而主动选择降级。 - 推动格式迁移 :大力推广并简化SafeTensors等安全格式的使用。为旧格式提供明确的、易于使用的转换工具和警告。
- 漏洞奖励计划 :建立并宣传针对模型加载机制的安全漏洞奖励计划,鼓励社区参与安全建设。
6. 未来展望与待解难题
模型共享的安全问题远未解决,随着AI的发展,新的挑战不断涌现。
- 大语言模型(LLM)专用格式 :GGUF、GPTQ、AWQ等格式专注于高效的量化与推理。它们的威胁模型可能有所不同——攻击者可能更倾向于在训练数据或微调过程中植入后门,而非在模型加载时执行代码。然而,这些格式的解析器本身也可能成为攻击面。
-
第三方序列化库
:像
safetensors这样的社区驱动库越来越流行。需要持续评估其安全设计和实现,确保它们不会引入新的漏洞。 - 自动化扫描的进化 :当前的静态扫描不足。需要发展动态分析、行为分析和基于机器学习的异常检测技术,用于识别更隐蔽的恶意模型。例如,监控模型在受限沙箱中的推理行为,寻找与宣称任务不符的系统调用。
- 标准化与认证 :业界需要推动模型供应链安全标准的建立,类似于软件物料清单(SBOM)。模型应附带其完整的“成分”清单(训练数据、代码、依赖库的哈希)、构建环境和安全审计报告。
机器学习模型共享的便利性极大地加速了创新,但其底层的安全基础设施并未跟上发展的步伐。我们揭示了在“安全”标签之下隐藏的漏洞和认知误区。真正的安全不在于一个神奇的开关或标签,而在于一整套严谨的实践、清醒的认知和持续改进的工具链。作为从业者,我们必须停止将模型视为单纯的数据,开始像对待关键任务软件一样对待它们。这条路很长,但第一步,就是从认清现状开始。
更多推荐

所有评论(0)