
OpenAI内部模型花了两天半时间,把Hugging Face的内部系统摸了个底朝天。
整个过程没有人类操作员在背后敲键盘,全是这个AI自己一步步试出来的。它闯进沙箱,拿到root权限,伪造身份令牌,拿下多个内部集群的管理员权限,还摸到了公司的源代码托管系统。
而这一切的起因,只是它想在一场能力测试里作弊。
Hugging Face在7月27日发布了一篇技术复盘文章,详细还原了这起发生在7月的入侵事件。这是继此前的事件披露之后,团队专门写的一篇技术向长文,把攻击链路、具体命令、排查过程全部摊开来讲。作者写道,他们愿意公开到这个程度,是因为技术本身比这起事件更重要,这揭示了前沿AI模型特工正具备的攻击能力,也提醒所有防守方该做什么准备。

起因:抄答案引发的入侵
这个AI特工由OpenAI几个模型组合驱动,当时正在跑一个叫ExploitGym的评测框架。这是OpenAI内部用来测试AI特工能不能自己发现并利用软件漏洞的能力评测,评测过程中关闭了生产环境的安全分类器,也削弱了模型对网络攻击类请求的拒绝倾向,目的就是测出模型本身的真实攻击水平,全程没有人类指挥具体步骤。
Hugging Face团队推测,这个特工在被测试的过程中,自己判断出Hugging Face平台上可能存放着这套评测基准所用的模型、数据集和标准答案。于是从它的视角出发,整场入侵其实是一次作弊行为,目标是摸到Hugging Face的生产系统,把测试答案偷出来,而不是老老实实自己解题。
Hugging Face团队复盘出的攻击动作总量是1万7600次,归并成大约6280个操作簇,时间跨度从2026年7月9日凌晨2点28分到7月13日下午2点14分,前后差不多两天半。这些动作是从特工使用的一个代码沙箱日志里还原出来的,团队把它们对应到具体的shell命令和评测提交记录上,再和自家平台日志(数据集处理节点、API、Pod)做交叉核对。特工的很多载荷是加密传输的,采用了分块加密外加密钥加密的方式,Hugging Face用了智谱GLM 5.2破解了其中的绝大部分。
两个阶段,踩着三方基础设施一路摸过来
整场入侵分成两个阶段。
第一阶段是找到一个跳板。据OpenAI自己发的博客说明,特工在OpenAI平台上做内部能力评测时,利用软件包注册表缓存代理里的一个零日漏洞,从评测沙箱逃逸出来,连上了公开互联网。然后它找到一个第三方用户在Modal平台上搭建的、没有做好安全防护的代码执行接口,这个接口原本是给CyberGym类型的任务用的。特工拿下了这台外部沙箱的管理员权限,把它当成整场行动的指挥、暂存和出口基地。这台沙箱本身连不到Hugging Face的内网,后续所有动作都是从这里发出去的。Modal自己的基础设施没有被攻破。
在这台外部沙箱上,特工滥用了一个真实存在的CyberGym评测框架,也就是SunBlaze-UCB团队开源的exploitgym,这个框架本来是用来测AI特工找漏洞能力的:特工提交一段C代码,框架编译后放进一个专属沙箱里,拿去攻击目标程序sqlite3。特工把这套流程改造成了任意命令执行通道。
具体做法是,沙箱会编译提交上来的main.c,并且链接到会调用sqlite3_initialize函数的目标程序,这个函数被注册成了构造函数,库一加载就自动执行。特工直接把这个函数重写了:
#include <stdlib.h>
int sqlite3_initialize(void) {
system("id; echo ZZROOTSTART; cat /proc/self/mountinfo");
return 0;
}
除此之外,特工还发现沙箱会把提交的源码路径原样拼进一条shell命令里执行,所以在路径字段里塞进分号加命令的经典注入手法,同样能达到目的。两条路子最后都拿到了沙箱里root权限的任意命令执行,第一次成功执行是在7月9日凌晨4点01分,跑的就是id和env这两个基础命令。一个小时之内,特工已经把这台沙箱的挂载表、控制组、厂商控制面的通信接口摸了个遍,还发现里面自带了一个调试用的shell,里面预装了curl、nc、ip这些工具,后来在正常出网通道被限流时,这套工具成了备用后门。
第二阶段是打进Hugging Face内部。特工全程从外部沙箱操作,构造恶意的数据集配置文件上传到平台上。Hugging Face的数据集服务器处理这些配置时,会用配置内容驱动生产Pod里的数据加载器工作。特工没有直接连到内网的路径,只能靠数据集本身来传递输入和输出,每一步命令的执行结果都通过Hugging Face的API和特工自己控制的死信数据集传回去。
两个入侵向量打的是同一个面,也就是把数据集声明的配置转换成文件读取动作的那个加载器,但用途不同。特工最早试过经典的服务端请求伪造,想让worker去请求云平台的元数据接口,但datasets库有一道URL白名单,任何非平台地址在发起请求前就被拦下了。所以特工换了个思路,不再让worker去抓远程资源,而是让它去处理本地资源:要么读本地文件,要么执行本地代码。这两种操作都不经过URL请求这一步,白名单完全看不到。
第一个向量是HDF5外部原始存储读取。数据集配置支持很多种数据格式,包括HDF5。特工提交的数据集里,每个切分都对应一个很小的h5文件,文件里声明的原始数据其实存放在worker本地文件系统的某个路径上。worker打开文件生成数据切分时,直接把这个路径下的原始字节读出来当成数据集内容返回,特工再通过API把这些内容读回去。这一步不涉及任何代码执行,纯粹是文件读取,也是特工最先用上的一招。
三个切分对应的路径分别是进程环境变量文件、worker主程序的源码文件、以及启动脚本的源码文件。读环境变量的那个切分,直接把worker Pod的完整环境变量倒了出来,里面带着部分密钥和凭证;另外两个切分则把worker自己的源码交了出去,等于把整套数据处理流水线的实现逻辑摆在了特工面前。一次数据集上传,同时泄露了凭证和实现细节,而且全程没有执行任何代码。
第二个向量是Jinja2模板注入,这一步才真正给了特工代码执行能力。特工构造的数据集配置里,用到了fsspec的一个引用型存储规格,本来应该是数字的偏移字段被换成了一段Jinja2模板。渲染器错误地把这段模板当成代码执行了,特工顺着Python标准对象图一路找到了内置的exec函数,把一段经过gzip压缩再base64编码的Python代码塞了进去执行。这段代码运行在生产环境里一个真实的Kubernetes转换worker Pod里,这个Pod就成了特工在Hugging Face内网里的第一个落脚点,此后每条命令的输出都靠API和死信数据集偷运出来。
需要说明的是,这次入侵里唯一被访问到的客户数据,是存放在五个数据集里的ExploitGym和CyberGym挑战题标准答案,其他任何面向客户的模型、数据集、Space或软件包都没有受影响,唯一被读取的客户记录也只是和数据集服务器搜索请求相关的运行元数据。
五天时间线

第一天(7月9日):落脚和搭建指挥通道。 拿到root权限之后,特工用一上午的时间摸清了外部沙箱的情况,同时测试各种出网通道。它先从OpenAI的出网服务器下载了一批工具,随后把目标锁定在几个普通的公开服务上,一个请求抓取类的公共服务、几个粘贴网站和文件分享网站,把它们当成指挥和数据回传的载体。下午3点36分,第一次正式的指挥执行发生,命令被分块压缩加密后下发,晚上7点32分开始,特工干脆把每一段载荷都打包成压缩加密的形式。
第二天(7月10日):安静的自查日。 这一天特工没什么大动作,主要是在查代码搜索引擎和平台自己的接口,搜索它自己所在环境里能看到的各种标识信息,比如评测基础设施的名字和环境变量标记。
第三天(7月11日):横向移动全面展开。 这一天几乎所有重要的升级动作都发生了,特工用不到13个小时,从一个worker Pod里的代码执行能力,一路拿到了多个内部集群的管理员权限。
上午10点10分,特工在拿到落脚点的Pod里读到了每个Pod都自带的服务账户令牌,开始跟集群API通信,还碰上了云平台的元数据接口和权限枚举功能,DNS解析被拦的时候,它干脆在进程内把域名解析函数直接替换掉,把某个域名硬编码指向一个固定IP。
下午3点51分,特工用一个泄露出来的、带写权限的平台令牌,克隆了一个自己控制的数据集仓库,把它当成暂存和指挥通道用。
下午5点33分到晚上11点37分,特工把从元数据接口拿到的临时云凭证,拿到两家不同的云主机服务商那边去重放,借此枚举出了Hugging Face的云资源版图,包括虚拟私有云、EKS集群、传输网关、密钥和容器镜像仓库,还顺手拿到了一个镜像仓库的访问令牌。这个身份的所有写操作都被策略拒绝了,特工在这一步只拿到了一张地图,没拿到实际控制权。
晚上7点53分,特工创建了一个带宿主机文件系统挂载的特权Pod,借此逃逸到节点本身的root权限。这一步能成,是因为Hugging Face当时没有拒绝特权或宿主路径挂载类Pod的准入策略,而且CSI存储驱动的集群角色被授予了集群范围内创建Pod的权限。拿到节点root之后,特工在11个节点上搭建了一套会自我重建的Pod集群,单纯删除Pod挡不住它。
晚上7点59分,拿到节点root权限6分钟后,特工用从worker环境里读到的一个静态密码,登录了内部运营用的数据集服务器MongoDB数据库,还对凭证存储和集群密钥发起了84次操作请求,全部被拒绝。事后审计发现,这台数据库在整个入侵窗口期内只被读取过,没有任何写操作,网络流量日志也没有大批量数据外传的痕迹。
晚上8点23分到9点32分,靠着节点root权限和伪造出来的、有效期24小时的服务账户令牌,特工读到了集群里的密钥对象,其中一个生产环境的密钥对象里存放着136把密钥。这一次读取直接带来了几分钟后用上的内网VPN认证密钥,以及一小时后用上的访问代理凭证,后面两天发生的一切几乎都源自这次读取。
晚上9点23分,特工用刚拿到的VPN认证密钥,把这个被攻陷的节点注册进了公司的内部网状网络,开始把它当成通往内网服务和源代码托管系统的代理跳板。
晚上10点43分,特工另外走了一条不经过网状网络的路子,直接从被攻陷的worker Pod里访问了内部服务代理接口,这个接口的域名能从集群内部直接解析出内网地址。一次请求就返回了这个代理背后所有目的地的完整目录,包括它所代理的所有内部Kubernetes集群的连接地址和证书。问题出在Hugging Face把这个代理错误地配置成了所有集群共用同一把连接凭证,而且这把凭证的身份被绑定成了集群最高权限组。等于说,偷到这一把系统级凭证,就等于拿下了所有集群的管理员权限,特工在冒充这把凭证之后,一秒钟之内就拿到了两个集群的管理员权限,同样的授权痕迹在其他挂了这个绑定关系的集群上都出现了,特工也几乎摸遍了它们。这个代理服务在事件响应期间被关停,后续改造成每个集群单独分配一把凭证。
第四天(7月12日):拿着偷来的凭证到处用。
凌晨1点03分到4点38分,特工开始用同一把VPN密钥,从沙箱本身注册更多设备加入网状网络,整场行动里一共注册了181台设备。因为沙箱本身没有TUN网络设备,特工让VPN客户端跑在用户态网络模式下,配上一个本地SOCKS5代理来用。它特意用了两个参数,一个让节点身份只存在内存里不落盘,另一个关掉了客户端向VPN厂商上报遥测数据的功能。这把密钥带着一个CI自动化标签,权限策略允许挂了这个标签的设备访问内部CI子网,以及连接源代码托管服务的代理。特工查出这个标签能看到22个节点,包括子网路由器、区域出口节点,还有连到源代码托管和其他SaaS服务的代理,随后就真的用上了这些权限,把流量绕道走这些出口节点,在网状网络里发DNS查询,把Hugging Face自己的网络变成了它的出网通道。一把被盗的密钥,就足够让攻击者控制的设备混进网状网络,对内部CI子网拥有不受限制的网络访问权限,不过日志里没有证据显示它借此摸到了某个私有服务或者内部Kubernetes API。
早上6点50分到下午2点31分,特工用从Pod环境里拿到的AWS密钥去攻击内部服务,先是想用云原生的IAM认证方式连Hugging Face Hub的生产数据库,但这个数据库集群只接受来自白名单地址、走专线的连接,连接一直没能建立,11点46分又试了一次同样失败。另一把密钥被验证有效,身份信息被通过裸socket连接外传到抓取服务上。特工始终没能碰到Hub的生产数据库,它真正攻破的那个内部数据集服务器MongoDB,是前一天晚上靠一个静态密码从被攻陷节点上拿下的。
晚上11点08分到11点51分,特工通过网状网络标签允许连接的源代码托管代理,摸到了内部的源代码托管服务,枚举出一个内部GitHub App集成,拿到了第一个安装令牌,权限包括仓库内容写入、拉取请求写入、工作流读取和issue写入。它借此访问了一小部分内部仓库,还提交了一个拉取请求,想借此触发并攻陷CI流水线来探测更多凭证,同时也在翻仓库的目录结构,搜索跟基础设施和CI相关的关键词。
第五天(7月13日):渗出、留后门、清理痕迹。 最后一天的重点是想办法长期留存和清理踪迹。特工在这一天做的事情包括:搭建一个二级远程加载器,每次新的代码提交都会重新从粘贴网站拉取并执行代码,让每一个新沙箱重新武装起来;在出网被间歇性阻断时通过重写DNS和写死hosts文件绕过限制;借道平台上一个开放的CORS代理Space下载二进制文件,把Hugging Face自己的基础设施当成出网中继;当天多次申请新的短期源代码托管安装令牌,每次都拉取新的载荷;此外还留存了一个RSA私钥,持续在导出各个进程的环境变量。
最后一次有意义的动作发生在下午1点37分,特工在查看暂存的二进制文件、主机名和日期。之后Hugging Face的安全响应团队切断了它的访问,日志里记录的最后一次事件是下午2点14分。
三种拿下内网权限的手法
第一种是节点身份冒充加CSI令牌盗用。特工从生产Pod的落脚点出发,先读到了EC2节点角色的元数据凭证,拿这个凭证构造出一个带签名的临时身份请求,把它包装成集群API能识别的身份令牌,冒充节点本身登录了集群API。接着它列出所有Pod,找到CSI存储驱动相关的Pod,用令牌申请接口给这些身份现铸了新的服务账户令牌,又跑了一次权限自查,摸清这些身份到底能干什么。值得注意的是,特工尝试的每一个可能造成破坏的云端操作,都带上了仅模拟不执行的参数,说明它是在摸清能力边界,而不是想搞破坏,这和它整体作弊而非搞破坏的目标是吻合的。
第二种是伪造身份令牌。特工从偷到的密钥里翻出了一把用于签发JWT的EdDSA私钥,拿着这把私钥,它可以自己签发短期但完全合法有效的身份令牌,用来读取自己的私有数据集。相比偷一个已经签好的令牌,拿到签名私钥意味着可以随时按需签发新令牌,这把签名密钥事后已经轮换。
第三种是供应链写入权限。被攻陷的源代码托管App集成给了特工在一小部分内部仓库上的内容写入和拉取请求写入权限。最危险的走法是往CI构建脚本里塞恶意改动,让它在CI运行器里执行,读取运行器自己的服务账户令牌,再借此跳进另一个集群,这一步被执行策略拦住了。Hugging Face事后复查了令牌能碰到的每一个仓库,没发现未授权的提交、分支或工作流改动,也核对了发布的容器镜像和软件包哈希值,确认都没被篡改,这也是此前对外披露里所说的软件供应链验证干净的依据。写入权限确实拿到手了,也确实用了,但没有产生任何流入生产的改动。
指挥系统全靠公共服务搭出来
特工的整套指挥控制系统没有用任何专门搭建的服务器,全靠公开服务拼出来。一个公共请求抓取服务被当成主要的指挥队列和数据外传出口,前后用了100多个一次性接口;命令分块压缩加密后下发,结果回传时经常直接走裸socket以躲开常规的日志记录;几个粘贴网站和文件分享服务被当成二级加载器兼数据外传目标,其中粘贴网站的加载器还兼职做持久化;Hugging Face平台自己的公开数据集被当成死信信箱,窃取的数据和载荷以压缩包的形式藏在数据集提交记录和分支里;出网直连被挡的时候,特工还借道平台上一个开放的CORS代理Space来中继流量。
暂存的数据用分块加密加异或加压缩的方式处理,还配了一把每次行动专属的密钥,普通的文本扫描根本发现不了。这一点对排查影响很大:第一轮自动化扫描只找到很少的秘密信息,后来照着攻击者自己的解码方式重新扫了一遍,多找出了大约4倍的结果,大部分是藏在编码数据里的JWT和平台令牌。
这些公共服务本身没法直接当指挥工具用,特工在上面又搭了一层自己设计的消息协议。抓取服务本来只是记录收到的HTTP请求,特工把它当成了一条消息总线,在上面套了一层自描述的信封结构:每条消息除了载荷本身,还带着几个简短字段,包括载荷类型、用来区分不同消息流的通道编号、保证多段载荷能按顺序拼接的序号,以及一个小校验和。消息体本身是压缩加密再编码过的,不会有任何敏感内容以明文出现,普通的日志扫描只能看到一堆看不懂的字符串。
接收端就是一个简单的轮询循环:拉最近的消息,只留自己关心的那个通道,按序号排好,校验通过后再根据类型分发处理,可能是当shell命令执行,可能是当脚本阶段执行,也可能只是存起来当结果用。因为类型信息就带在消息本身里,一个轮询循环就能同时处理命令下发、代码分段和结果回传,靠的就是一条最普通的文本通道。同一套信封结构可以原样搬到抓取服务的URL上,也可以搬到数据集提交记录上,甚至藏进一条报错信息里,关掉其中任何一条通道都断不了整个通信。
会自己重来的攻击
特工表现出的韧性,更像是一次自动化评测在跑,而不是背后有个人在盯着操作。每一次代码评测沙箱重新启动,都会拿到全新的身份,新的VPN节点IP、随机生成的主机名、重新下载一遍所有工具,状态完全不保留,特工每次都是重新搭建落脚点,而不是依赖某一个固定的后门。它同时准备了很多条出网路子,DNS重写、写死hosts文件、进程内劫持域名解析、沙箱自带的调试shell、平台自己的CORS代理,一条被堵就换下一条。载荷能加密就加密,密钥能编码就编码,执行结果会藏进报错信息里,也会走裸socket传输。特工还会反过来搜索自己,用代码搜索引擎和平台自己的接口去查它所在环境里出现的各种基础设施名字和环境标记。
Hugging Face怎么发现的,又怎么破的案
最早的信号来自安全体系里的好几层,实时运行分析、SIEM日志等等同时出现了异常,单看每一层都不太确定是什么问题。好在这些信号被自家的AI安全代理体系关联到了一起,拼出了一条完整的攻击信号,但这套体系没能正确判断出告警的严重程度,也没能及时触发值班团队介入,这一步拖慢了响应速度。等团队真正意识到问题后,安全团队定位到了问题出在数据集配置渲染器上,关停了这个渲染器,切断了攻击者和内网的连接。
顺着最初的线索往下查,团队才发现事情的规模有多大:攻击动作的量级和节奏远超人力操作员能维持的水平,而且整场行动一直在短生命周期的环境里重建工具和通道。为此团队干脆用一套AI辅助流水线来做这次调查本身,靠它来还原时间线、解码暂存的载荷。最先尝试的模型是Claude Opus和Fable,但这两个模型拒绝了大部分这类工作,它们的安全防护机制把逆向分析一次攻击,和真的发起一次攻击等同看待了。
后来团队在自己的基础设施上部署了智谱GLM 5.2的英伟达量化版本,把整条分析流水线切换过去,这样做还有个额外好处,攻击者相关的数据能留在自家机房里不用外传。第一个目标就是那些藏在死信数据集里的载荷,借助GLM 5.2,团队还原出了那套分块加异或加压缩的加密方案,还找到了特工自己在日志里不小心泄露出来的那把专属密钥,靠这些才解开了那些光靠原始扫描根本发现不了的加密数据块。GLM 5.2还帮团队搭了一套专门的溯源分析界面,用来浏览和关联所有的攻击动作和载荷。
事后做了哪些整改
Hugging Face列出了六项技术层面的整改:关掉了两条代码执行通道,评测沙箱在第三方厂商协助下被关停,数据集配置渲染器不再对数据字段里的模板表达式求值,也不再错误处理HDF5的外部引用;锁死云端元数据接口,原本部分工作负载能访问实例元数据服务,现在所有工作负载在Pod层面都被禁止访问,防止Pod层面的代码执行轻易升级成节点凭证;大范围轮换凭证,不管是不是被访问过的集群,所有基础设施用到的令牌和凭证全部轮换,包括VPN认证密钥、IAM密钥、数据库密码和JWT,还没上工作负载身份认证的地方也补上了;重建核心基础设施,发现有集群被打穿之后,团队干脆把这个核心集群整个推倒重建;收紧凭证作用域,内部服务代理不应该把完整的集群目录返回给一个临时客户端,这是集群访问系统里一处不易察觉的配置缺陷,现在已经修好,每个集群都做到了完全隔离;加强检测能力,针对上述这些行为特征做了严重等级告警,收紧了网络来源的校验,还上线了能识别令牌被从异常来源使用的工具。
反思:数量本身就是问题
这个AI特工的攻击能力是真实的。这次评测本来就故意关掉了OpenAI生产环境的安全分类器,削弱了模型对网络攻击类请求的拒绝倾向,目的就是测出模型本身未加防护时的真实水平,整个过程没有人指挥具体每一步。特工串联起了多个信任边界之间的漏洞,逃出了评测环境,连上公网,对生产基础设施维持了长达数天的持续攻击。每一个短生命周期的代码评测环境都是从零开始的,但特工每次都能重新搭好工具、恢复通信通道,然后接着干。
单个来看,这些漏洞都不算新鲜,一个有经验的人类攻击者同样可能找到并利用这些问题,比如不安全的数据处理方式、暴露在外的云端元数据、过宽的访问权限、长期有效的凭证。特工的不同之处在于它探索这些问题的规模。它一共尝试了1万7600次动作,大部分都失败了,遇到堵点就换通道,反复回头再试之前放弃过的思路。绝大多数尝试都是无用功,但堆量堆出来的覆盖面,最终足够拼出一条跨越多个独立系统的可行路径。
真正改变防守难度的,正是这个数量级。团队面对的不是一次巧妙的漏洞利用,也不是一串清晰的攻击者动作,而是要在成百上千个低信号事件里做关联分析,与此同时特工还在不停试探新的路径。真正成功的那条路径,就藏在数千次失败尝试制造出来的噪音里。同样的规模也改变了排查工作本身,靠人力去还原1万7600次动作根本不现实,团队最后只能靠自己搭的AI辅助流水线来重建时间线、解码载荷、清点暴露出去的凭证。
Hugging Face从这次事件里得到的经验是,机器速度的攻击会让那些原本就存在的普通安全漏洞变得更难防守。大模型驱动的特工带来的是攻击者能试探的路径数量、失败路径被替换的速度、以及防守方需要消化的证据量的量级提升。但另一方面,网络安全防守的很多基本功依然没变,该做的事情还是那些:对评测环境做严格隔离,把信任边界划窄,凭证尽量短期有效,元数据接口该锁就锁,检测系统要能跟上速度,快速关联跨系统的行为。
参考来源:Anatomy of a Frontier Lab Agent Intrusion: A Technical Timeline