摘要
在这篇博客中,我们展示了攻击者如何攻击Hugging Face的Safetensors转换空间及其相关的服务机器人。这些服务是一个在Hugging Face上广受欢迎的服务,专门用于将不安全的机器学习模型转换为更安全的版本。我们随后展示了如何通过Hugging Face自身的服务,发送带有攻击者控制数据的恶意拉取请求到平台上的任何仓库,以及如何劫持通过转换服务提交的任何模型。我们使用一个被劫持的模型来证明了这一点,这个模型是设计用来测试转换服务的,该服务允许攻击者通过冒充Hugging Face转换机器人来请求对平台上的任何仓库进行更改。我们还展示了如何可能在服务中持久化恶意代码,以便在模型转换时自动劫持模型。
尽管转换服务的代码在Hugging Face服务器上运行,但系统是在Hugging Face Spaces中容器化的 — 这是一个平台,任何用户都可以在其中运行代码。因此,大部分风险不是针对Hugging Face本身,而是针对托管在该网站上的仓库及其用户。我们的团队感到有义务将这项研究公之于众,以便在造成任何损害之前找到任何可能被破坏的模型。在我们的公开报告漏洞之后,我们还联系了Hugging Face,以便在发布之前给他们时间关闭转换服务或实施安全措施。
介绍
任何人工智能系统的核心都是一个机器学习模型 — 是在给定数据集上进行大量计算的结果,经过训练、调整和优化,以执行特定任务或一般的应用程序。在模型可以部署到产品中或作为服务的一部分使用之前,它必须被序列化(保存)到磁盘上,这被称为序列化格式。通过有效地将模型简化为二进制表示,我们可以在它被训练的系统之外部署模型,或者与我们希望的任何人共享。在行业中,这些模型通常被称为“预训练模型”,并且它们已经风靡全球。
预训练的开源模型是人工智能广泛采用的主要推动因素之一,使数据科学团队能够共享、下载和重用现有模型,以适应他们的特定应用程序,而无需从头开始创建所需的庞大资源。事实上,模型的共享已经变得如此普遍,以至于围绕这一前提创建了公司。Hugging Face拥有一个强大的社区,迄今为止已经上传了超过500,000个预训练模型到平台。
模型即代码
如果您一直在关注我们的研究,您会知道模型其实就是代码,一些广泛使用的序列化格式在某种程度上允许执行任意代码,并且正在被野外利用。最大的罪魁祸首是Pickle,尽管它是最脆弱的序列化格式,但却是最广泛使用的。Pickle是PyTorch库的基础,并且是Hugging Face上最普遍的序列化格式。
为了缓解由易受攻击的序列化格式带来的供应链风险,Hugging Face团队开始开发一种新的序列化格式,这种格式从一开始就以安全为考虑构建,以便不能用于执行恶意代码 — 他们称之为Safetensors。
理解转换服务
Safetensors正如其名,允许安全的反序列化机器学习模型,主要是因为它只存储模型权重/偏移,而不存储可执行代码。为了帮助用户基础转向这个更安全的替代方案,公司创建了一个转换服务,通过拉取请求将仓库中的任何PyTorch模型转换为Safetensors版本。转换服务的代码(convert.py)直接来自Safetensors项目,并通过Hugging Face Spaces运行,这是一个在浏览器中运行Python代码的云计算产品。
在这个空间中,一个Gradio应用程序与convert.py捆绑在一起,提供了一个Web界面,用户可以在其中指定要转换的仓库。该应用程序只允许针对PyTorch二进制文件进行转换,并要求仓库中存在名为pytorch_model.bin的文件才能启动转换过程,如下图所示:
要转换的huggingface存储库
用户可以导航到转换服务的Web界面,并以以下格式输入仓库ID:
<用户名>/<仓库名称>
对于我们的测试,我们创建了以下仓库,其中包含我们特别制作的PyTorch模型:
转换服务Web UI
如果用户提供了一个包含可解析PyTorch模型的有效仓库,并且格式正确,转换服务将转换模型,并通过‘SFconvertbot’用户在原始仓库中创建拉取请求。下图中显示的过程的第一步,我们不需要输入目标仓库所有者的用户提供的令牌,这意味着我们可以向任何项目提交转换请求,即使这些项目不属于我们。
SafeTensors 转换机器人“SFconvertbot”向存储库发出拉取请求
识别攻击向量
我们对转换机器人如何加载PyTorch文件感到好奇,因为一个简单的torch.load()调用就足以危害宿主机器。可以看到在convert.py中,有一个安全警告,必须在命令行直接运行时手动确认(而不是通过捆绑的Gradio应用程序app.py):
convert.py 安全警告
出乎意料的是,其张量确实是使用torch.load()函数加载的,如果PyTorch模型中存储了恶意代码,这可能导致任意代码执行。我们猜测是否在Hugging Face Spaces中的转换机器人有什么不同呢?事实证明,并没有什么不同!
convert.py 转换脚本中使用的 torch.load()
在这一点上,我们意识到。是否可以利用它设计的转换功能进而劫持托管转换服务呢?
制作攻击载荷
我们开始将我们的想法付诸实践,通过制作一个恶意的PyTorch二进制文件,使用预训练的AlexNet模型,并注入我们的第一个payload eval("print('hi')") 一个简单的eval调用,会打印出'hi'。
我们没有在实时服务上进行测试,而是部署了一个本地版本的转换服务来评估我们的代码执行能力,并查看是否会创建拉取请求。
我们能够确认我们的模型已经被加载,因为我们可以看到输出中的'hi',但有一个奇怪的错误。似乎通过添加我们的利用代码,我们改变了模型的文件大小超过了1%,这最终阻止了模型的转换或机器人创建拉取请求:
本地运行 convert.py 的终端输出
面对这个错误,我们考虑了两种可能的方法来规避问题。要么使用一个更大的模型文件,要么通过某种方式来绕过大小检查。由于我们希望我们的利用能够在任何类型的PyTorch模型上工作,我们决定采用后者,并分析文件大小检查的逻辑。
check_file_size函数
起初,我们想要找到一个可行的方法来修改文件大小以跳过条件逻辑。然而,当PyTorch模型被加载时,Safetensors文件还不存在,导致了错误。但由于我们的恶意模型在文件大小检查之前就已经加载了,因此我们可以在运行时动态修改convert.py脚本,并重写函数指针,以便调用不同的函数而不是check_file_size。
由于check_file_size没有返回任何内容,我们只需要一个接受两个字符串并且不抛出异常的函数。我们的潜在替代函数os.path.join完美地符合这个标准。然而,当我们尝试重写函数时,我们发现了一个问题。PyTorch不允许在任何字符串中使用等号'=',这阻止了我们以这种方式为函数指针赋值。为了应对这一点,我们创建了以下payload,使用setattr来手动重写函数指针:
用于覆盖 check_file_size 函数指针的 Python 代码
在对我们的PyTorch模型进行了上述payload修改后,我们就能够使用我们的本地转换器成功转换模型。此外,当我们通过Hugging Face的转换器运行模型时,我们能够成功创建拉取请求,现在就有了破坏托管转换机器人的系统的能力:
成功转换恶意 PyTorch 模型并使用 Hugging Face 服务发出拉取请求
模仿是最好利用
虽然在沙箱中任意执行代码已经危害很大,但我们注意到了一个更大的威胁。转换服务生成的所有拉取请求都是通过SFconvertbot发出的,这是一个专门为此目的的官方机器人。如果一个不知情的用户看到一个来自机器人的拉取请求,声称他们的模型有安全更新,他们可能会接受更改。这可能允许我们上传不同的模型来替换他们希望转换的模型,从而植入后门,或完全更改模型——构成巨大的供应链风险。
由于我们知道机器人是在与convert代码运行在相同的沙箱中创建拉取请求的,我们也知道机器人的凭证很可能也在沙箱里。
分析代码时,我们看到它们被设置为环境变量,并且可以使用os.environ.get("HF_TOKEN")访问。虽然我们现在有了访问令牌的权限,但我们仍然需要一种方法来窃取它。由于容器必须下载文件并创建拉取请求,我们知道它会有一定的网络访问权限,所以我们决定测试一下。为了确定我们是否可以访问Hugging Face域空间之外的域,我们创建了一个远程webhook,并通过恶意模型向钩子发送了一个get请求:
从运行Hugging Face转换服务的系统接收Web请求
成功了!我们现在有办法窃取Hugging Face SFConvertbot令牌,向网站上的任何仓库发送恶意拉取请求,冒充合法的官方服务。
但我们还没有完成。
你无法打败真实的东西
我们不满意仅仅模仿机器人,我们决定检查服务是否在每次用户尝试转换模型时重新启动,以评估留下持久性后门的机会。为了实现这一点,我们创建了我们自己的Hugging Face Space,基于Gradio SDK,使我们的空间尽可能接近转换服务。
在创建我们自己的测试空间时选择 Gradio SDK 选项
现在我们已经设置好了空间,我们需要一种方法来模仿转换过程。我们创建了一个Gradio应用程序,它接受用户输入,并使用内置的Python函数'exec'执行它。
然后,我们包含了一个名为'greet_world'的虚拟函数,无论用户输入什么,都会输出'Hello world!'。这让我们能够非常接近地模拟转换函数的环境,允许我们以类似于torch.load()调用的方式执行代码,并给我们一个目标函数来尝试在运行时覆盖。我们真正的目标是convert.py中的save_file函数,它负责将转换后的SafeTensors文件保存到磁盘。
我们来自 Hugging Face Spaces 的测试代码
在设置好并运行起来后,我们进行了一个简单的测试,看看应用程序在执行一些代码后是否会返回“Hello World”。
我们在自己的空间中测试的Gradio应用程序
我们采用了与绕过get_file_size函数类似的方法,尝试使用setattr覆盖greet_world。在我们的利用脚本中,我们限制了自己只能在torch.load()的上下文中允许使用的内容。我们决定采用创建一个本地文件方式,将我们想要的代码写入其中,获取一个指针,并用我们自己的恶意函数替换它的方法。
我们成功覆盖了greet_world函数
正如上图所示,响应从“Hello World!”变为了“pwned”,这是我们的成功佐证。现在真正的测试开始了。我们必须看看一旦我们在浏览器中刷新了空间,我们对空间所做的更改是否会持久生效。通过这样做,我们可以看到实例是否会重新启动,以及我们的更改是否会持久。再次,我们输入了最初的良性提示,但这一次,我们新刷新的页面上的结果依然是“pwned”。我们实现了持久性。
我们对受损空间进行提示测试。
我们已经证明了,攻击者可以在任何人尝试转换他们的模型时运行任何任意代码。用户自己没有任何迹象,他们的模型可能在转换时被劫持。更重要的是,如果用户希望转换他们自己的私有仓库模型时,我们可以有效地窃取他们的Hugging Face令牌,破坏他们的仓库,并查看该用户可以访问的所有私有仓库、数据集和模型。请注意:在进行这项研究时,我们没有泄露SFConvertbot令牌,也没有对Hugging Face系统进行恶意操作。我们相信发现漏洞是为了修复它们,一旦我们确认了我们的发现,就会立即停止。
这意味着什么
Hugging Face的用户范围囊括了个人研究者到核心组织,他们上传模型供社区自由使用。平台上上传的50w+个机器学习模型中的许多都容易受到不安全文件格式的恶意代码注入。为了遏制这一点,Hugging Face引入了Safetensors转换机器人,任何用户都可以将他们的模型转换为一个更安全的替代方案,免受恶意软件的影响。然而,我们展示了这个过程如何被劫持,并公开质疑这项服务是否可能已经被利用,可能导致重大的供应链风险,其中很多组织已经接受了这个机器人对他们模型的建议更改。
我们已经确定了像Microsoft和Google这样的组织,他们共同在Hugging Face上托管了905个模型,作为可能面临针对性供应链攻击的风险,因为他们接受了这个机器人对他们Hugging Face仓库的一些更改。任何作为拉取请求的一部分创建的更改都被视为来自受信任的Hugging Face关联机器人,因此通常不会受到质疑。虽然用户可以要求转换他们自己的仓库,但这并不一定要起源于该用户 — 任何用户都可以为公共仓库提交转换请求,这将导致机器人在相关仓库中创建拉取请求。
如果攻击者愿意,他们可以使用上述的方法创建原始模型的自己的版本,并植入后门以触发恶意行为,例如绕过面部识别系统或生成虚假信息。比较机器学习模型之间的更改需要仔细审查,因为模型本身是非人类可读格式存储,这意味着比较它们的唯一方法是程序化的,靠肉眼比较将不起作用。因此,在Hugging Face上接受拉取请求时,并不知道模型是否已被劫持或更改。因此,我们建议您彻底调查您控制的任何仓库,以确定是否有任何非法篡改您的模型权重和偏差,作为这种不安全的转换过程的结果。
一个Google仓库,该仓库接受了来自Hugging Face SFconvertbot的唯一接受的拉取请求
正如上图所示,Google的vit-base-patch16-224-in21k模型接受了来自SFConvertbot的拉取请求,并拒绝了另一个试图更改README的拉取请求。在下面的图中,我们可以看到,仅在过去一个月,该模型就被下载了3,836,972次。虽然我们还没有在这个模型中检测到任何被攻击的迹象,但这证明了即使是最大的组织也对转换服务寄予了隐含的信任。
同一个Google仓库在过去一个月中下载了3,836,972次
结论
通过恶意的PyTorch二进制文件,我们展示了如何可能破坏Hugging Face的Safetensors转换服务。同时展示了如何窃取官方Safetensors转换机器人的令牌,代表其向网站上的任何仓库提交拉取请求。我们还展示了攻击者如何接管服务,以自动劫持提交给服务的任何模型。
这种攻击的潜在后果是巨大的,因为对手可以植入自己的模型,将恶意模型推送到仓库中,或访问私有仓库和数据集。在已经转换的仓库的情况下,我们仍然可以提交新的拉取请求,或者在该仓库上传并使用受损的转换服务转换的新PyTorch二进制文件的情况下,去影响那些下载量达到数十万的仓库。
尽管Hugging Face生态系统中保护机器学习模型的初衷是最好的,但转换服务已被证明是脆弱的,并有可能通过Hugging Face官方服务引发广泛的供应链攻击。此外,我们还展示了攻击者如何获得运行服务的容器的权限,并破坏由服务转换的任何模型。
沙箱化是锁定应用程序的一个很好的方式,如果您担心机器上可能存在代码执行的话。然而,即使在沙箱化的情况下,也不应该允许任意代码在执行重要社区服务的同一应用程序中运行。我们理解处理已知的代码执行方法(如Pickle/PyTorch文件格式)可能会很棘手,这就是为什么我们是扫描机器学习模型以查找恶意内容的坚定支持者。
在Google和Microsoft的前10个最多下载模型中,接受机器人转换过的模型在过去一个月中下载量达到了惊人的16,342,855次。虽然这20个模型只是Hugging Face上托管的500,000多个模型的一小部分,但它们触达了惊人的用户数量,让我们不禁思考,考虑到机器人已经做了42,657次模型转换,有多少用户下载了可能恶意的模型?
来源:hiddenlayer