[控场AI]
· 9 分钟阅读· 4,770 字

Hugging Face模型安全:加载时究竟执行了什么

Hugging Face模型安全:加载时究竟执行了什么

本地部署AI模型时,模型仓库中的Pickle序列化与远程代码路径是真实的代码执行入口,而非单纯的数据文件。

本文系统梳理了本地部署AI模型时被忽视的安全盲区:模型仓库不只包含数值权重,还可能夹带可执行的Pickle序列化对象和Python代码,加载时触发的代码执行权限与宿主进程相同。文章以三个有据可查的案例为支撑——JFrog记录的Goober Two反向Shell、ReversingLabs分析的nullifAI跨平台载荷,以及Checkmarx演示的路径解析歧义——分别说明了不同的攻击入口和前提条件。同时明确区分了"已证实的机制"与"可能的后果":凭据泄露需要可读凭据与可用网络通道同时成立。防御层面,文章指向Safetensors格式、PyTorch 2.6+默认的`weights_only=True`、锁定修订版本以及环境隔离等互补措施,并强调每条加载路径需作为独立信任决策审查。

本地部署AI模型让你掌控运行环境,但模型安全不只取决于权重文件本身,更取决于加载器在这一过程中执行了什么代码。安全研究人员已经在Hugging Face的模型包中发现了反向Shell载荷(reverse-shell payload),而不安全的加载路径会让这些恶意代码获得与你的Python进程同等的访问权限。换句话说,你的机器下载到的可能不只是模型,还有一位野心勃勃的"新同事"。

Model security on Hugging Face: what executes when you load

模型安全在第一个回答之前就开始了

Hugging Face是一个共享机器学习模型、数据集和应用的平台。一个仓库是方便的分发单元:它可以包含权重、解释权重所需的配置,以及针对自定义架构的实现代码。但这些内容的加载行为各不相同,因此需要分别审视。

权重是训练学到的数值,由张量数据读取器负责解析其形状和取值。而Python对象序列化(Pickle)则描述了如何重建对象,其中可能包括对函数的调用。当你的应用授权运行仓库中的Python代码时,又引入了另一条执行路径。把这些文件统统归入"模型"这个名目下,让安装变得方便,却也让信任边界容易被忽视。

Hugging Face的Pickle扫描文档把核心警告说得很直白:"加载pickle文件时可能遭遇危险的任意代码执行攻击。"关键的动词是加载(load)。把字节保存到磁盘,与把这些字节交给不安全的反序列化器,是两种截然不同的操作。恶意包并不需要说服模型在回答中泄露什么——如果周边软件已经让它的代码跑起来,推理还没开始,进程就已经开始"接待访客"了。

Pickle安全:拆开那个包裹

Pickle以"重建指令"的形式存储Python对象,反序列化会按顺序读取这些指令。不安全的加载器可能在重建过程中解析并调用函数,这就让一个不可信的文件获得了对宿主进程的影响力。

可以把模型包想象成一个包裹,加载器就是拆包裹的人。下载只是把包裹放到了货架上,而加载则是按照包裹内附的组装说明去操作。如果这些说明能够调用任意函数,行为就从"读取数据"越界到了"执行发布者的选择"。

具体的加载API、库版本和设置都很关键。关于某一条不安全路径的安全结论,不能推广到所有模型加载器。现代的受限加载模式会刻意减少对象重建被允许做的事情;反过来,如果因为某个陌生检查点"需要"而强行覆盖拒绝机制,就会重新打开你本来依赖的那道边界。

这也是为什么"投毒权重"这个说法需要澄清。它既可能指通过训练或编辑数值参数改变模型行为,也可能指围绕模型分发的可执行打包和仓库代码。本文讨论的案例属于后者,它们有自己独立的入口、权限和防御手段。

从技术层面理解Pickle的风险,需要了解其指令集机制。Python的pickle模块使用一套称为"操作码"(opcode)的虚拟机指令序列来描述对象重建过程,其中包括GLOBAL指令,可以在反序列化时导入任意Python模块并调用其中的函数。攻击者只需在Pickle字节流中插入类似os.system或subprocess.Popen的调用,就能在pickle.loads()执行的瞬间运行任意系统命令,而这一切发生在任何应用层逻辑检查之前。这与SQL注入的根本性质类似:数据通道同时承担了指令通道的角色。正是因为这一设计,Python官方文档明确注明"永远不要对来源不可信的数据调用pickle.loads()",但在模型加载的实际使用中,这一警告常常被便利性所掩盖。

三个具体案例,以及它们的边界

Goober Two:一个有据可查的反向Shell

JFrog曾分析过baller423/goober2仓库。其Pickle载荷在不安全加载过程中启动了一个反向Shell。反向Shell会主动向外连接,并把命令访问权限交给连接的另一端。

该仓库后来被删除。JFrog还讨论了一些线索,暗示这可能是研究人员所为,因此归因仍不确定。报告为载荷的行为提供了证据,但并未证明有犯罪分子窃取了某个具名受害者的云密钥,也不足以编造一个受害者数量。

即便如此,这个机制依然足够令人担忧。以加载器账户身份获得的命令访问权,本身就是一个有意义的立足点——而开发环境往往赋予该账户远超一次模型实验所需的权限。

nullifAI:异常的打包方式和迟到的报错

ReversingLabs描述了两个模型样本(包括glockr1/ballr7),命名为nullifAI。两者都含有能感知运行平台的反向Shell载荷。

一个重要细节是归档格式。这些样本使用了7-zip打包,而常规的PyTorch加载预期的是另一种结构。默认的torch.load会拒绝这种打包,危险路径需要先解压再进行不安全的Pickle反序列化。如果有人画一张"普通模型下载后直接打开这些样本"的示意图,就会跳过一个真实存在的前提条件。

报告还考察了损坏的Pickle流以及历史上的扫描器盲点。其无害的文件写入测试显示:较早的指令可能在较晚的报错中止加载之前就已经执行。等加载器开始抱怨时,文件早已存在。ReversingLabs披露后,Hugging Face在二十四小时内移除了报告中的样本并修改了扫描器。这个案例描述的是一次历史性发现及其应对,提供了调查扫描器失效原因的理由,而非"同样的绕过方式至今未修复"的断言。

Model Confusion:缺失的文件夹解析到了别处

Checkmarx的Model Confusion研究展示了一次演示,涉及某家未具名公司的样本。像checkpoints/some-model这样的相对标识符既可能指向本地路径,也可能指向公共模型命名空间。如果本地路径缺失,解析过程可能会选中一个同名的远程仓库。

把这种歧义,与不安全加载及远程代码信任(trust_remote_code)结合起来,被选中仓库的代码就可能被执行。据Checkmarx称,该公司的样本在一天内得到了修正。报告明确说明这是一次演示,并表示在其高亮的敏感命名示例中并未发现恶意活动。

这里有两个条件在起作用:选中了意料之外的远程包,以及授权了一条具备执行能力的加载路径。只检查字符串看起来是否像本地文件夹,会漏掉解析这一步。一个缺失的目录,不应该被允许自行"即兴"决定去投靠谁。

反向Shell(reverse shell)是一种特定的远程访问技术,其方向性与传统的远程登录相反。在传统连接模式中,攻击者主动连接到受害机器的监听端口;而反向Shell由受害机器主动向外发起连接,连接到攻击者控制的服务器。这一区别在实践中意义重大:大多数网络边界的防火墙规则会严格限制入站连接,却对出站连接相对宽松(例如允许所有出站HTTP/HTTPS流量)。反向Shell正是利用这一不对称性绕过入站过滤。一旦连接建立,攻击者便获得一个交互式命令行会话,能够以运行该进程的用户身份执行任意命令、读取文件、枚举环境变量,并进一步横向移动。这也解释了为什么文章后续将"可用的网络通道"列为凭据外泄的独立前提条件之一。

可读的凭据从哪里进入这个流程

一旦代码在进程中运行,它的访问能力取决于账户、文件系统挂载以及该进程周边的其他控制。一个可访问的云密钥、环境中可用的令牌,或者一个可读的私钥文件,都可能成为目标。而被保存在该访问边界之外的秘密,仍然在直接读取路径之外。

把秘密发送到别处还需要可用的网络通道。读取权限和出站连接是两个相互独立的条件:阻断出站流量可以约束传输;而把秘密排除在进程之外,则可以从源头上阻止读取。纯粹的数值权重两种能力都不具备。

本文选取的反向Shell报告记录的是命令访问载荷。凭据收集和外泄是这种访问的"条件性后果",而非这些报告中被证实的受害结果。因此,整个事件链条中的关键环节应标注为"可能的后果"。

这正是常见开发配置变得尴尬的地方。一个把你的主目录或凭据挂载进去的容器,等于把这些被挂载的资源暴露给了容器内的代码。命名空间边界有其用处,但你刻意放进去的密钥依然在里面——一个装满秘密的容器,不过是一个带资源限制的"礼品篮"。

开发环境中常见的凭据暴露面值得具体列举。云服务商CLI工具(如AWS CLI、gcloud)默认将凭据写入~/.aws/credentials或~/.config/gcloud/;容器化工作流中,为了方便访问私有模型仓库,开发者常将Hugging Face令牌通过环境变量HUGGING_FACE_HUB_TOKEN或挂载.cache/huggingface/目录传入;SSH私钥通常位于~/.ssh/。这些路径对同一用户下运行的任何进程都是可读的,不需要提权。当模型加载器以开发者身份运行时,其访问边界与开发者本人几乎等同。将模型加载限制在专用的最小权限服务账户下、或使用不挂载主目录的容器镜像,其实质是在缩小"如果加载行为出乎意料,进程能触及什么"这一问题的答案范围。

Safetensors、已修补的加载器与有用的隔离边界

Safetensors提供了一种张量数据格式,专为安全存储设计,不含Pickle那种可执行的对象重建。选用它可以解决序列化这条路径的问题。但仓库里仍可能在张量文件旁夹带Python代码,所以代码加载路径应作为一个独立的决策来审查。

Hugging Face的自定义模型加载文档警告了无意中执行恶意代码的风险,并介绍了trust_remote_code=True,同时建议锁定(pin)一个具体的修订版本。先审查你打算运行的确切版本,然后在评估期间保持这个决定稳定。

PyTorch的序列化文档说明,从2.6版本起,在未传入pickle_module时,torch.load默认weights_only=True。受限的反序列化器收窄了远程代码执行的攻击面。不过文档也保留了关于拒绝服务和可能的内存损坏的局限说明,因此这个设置的实际作用范围值得如实看待。

对于不熟悉的包,建议把"获取与检查"同"执行"分开。准备一个干净的环境,只放实验所需的文件,把个人密钥留在外面;按任务需要限制网络访问;并把对仓库代码的信任作为一个明确、经过审查的选择。真正有用的问题是:如果加载行为出乎你的意料,这个进程能够触及什么?

Safetensors由Hugging Face开发并开源,其安全性来自格式本身的设计约束:文件头部是纯JSON结构,描述张量的数据类型、形状和字节偏移;正文是原始的数值字节流。整个格式不含任何可调用的对象引用,解析器只需执行内存映射和类型转换,不存在"执行发布者指令"的语义空间。与此同时,该格式还在头部长度上设置了上限(默认100MB),以防止通过超大头部触发解析器的内存耗尽。值得注意的是,Safetensors解决的是序列化这一条路径的问题,仓库中独立的modeling_xxx.py等Python源文件在trust_remote_code=True时仍会被直接导入执行,两条路径的信任决策需要分开评估。

结论:需要审查(NEEDS REVIEW)

开放模型值得保留,但权限边界应当围绕加载器来设置。这些有据可查的案例说明,模型分发需要我们像对待其他可执行输入一样的关注。免费获取模型固然有价值,而决定谁能访问开发环境同样有价值。

#Hugging Face安全#模型安全#Pickle反序列化#反向Shell#Safetensors#trust_remote_code#AI供应链攻击#torch.load
分享:

相关推荐