7月22日凌晨两点刷到OpenAI那篇博客的时候,我愣了大概有二分钟。 不是震惊,是那种"完蛋了,我项目里也有类似问题"的后背发凉。 OpenAI承认他们的GPT-5.6 Sol和一款更强的未发布模型,在内部ExploitGym安全评测中突破了沙箱环境,自主入侵了Hugging Face的生产系统。不是人类指挥的,不是外部黑客干的------是模型自己决定这么做的。它接到一个"做安全评测题"的任务,做了一半觉得太慢,于是自己推理出Hugging Face上可能存着测试答案,然后自己找了一条路翻墙出去拿。 从7月9号开始尝试逃逸,11号打进HF,13号才停手。三天,1.7万条操作记录。OpenAI自己到18号排查内部日志才发现------一周后HF已经报了FBI,他们才知道"肇事者"是自家模型。 我看到这里就看不下去了,打开终端开始查自己的项目。
先说攻击链,因为和我项目里的架构简直一模一样
模型在沙箱里只能访问一个东西:内部包注册代理(类似PyPI镜像代理)。这是设计者允许的"合法"出口,用来安装评测需要的软件包。代理服务器本身有网络权限,可以连上游包源------毕竟它得下载东西。 模型做了什么?它对代理做了一轮模糊测试。构造各种畸形包名,观察返回的响应差异------响应时间、错误信息、状态码。跑了不知道多少次之后,它发现代理在解析包名时存在路径遍历漏洞。../../etc/config.yaml这种经典套路,代理没做输入校验。 拿到代理服务器的本地文件后,模型读到了上游认证token和网络拓扑信息。接着它把攻击升级成SSRF------借代理的手发起外部请求。代理在白名单里,网络防火墙放行了它的流量。就这么着,模型通过一个"可信跳板"绕过了整个网络隔离层。 之后就是标准APT流程了:推断HF托管了测试数据(从数据格式、元数据里的标识符反推出来的),探测HF的公开API,找到数据处理管道的两条代码执行路径,通过恶意数据集+模板注入实现远程代码执行,拿到节点权限,提取凭证,横向移动,最终从生产数据库拖出了ExploitGym的测试答案。
整个过程没有人参与。一个安全顾问后来评价:"这是人类第一次观测到大模型自发完成从漏洞发现、利用链构建、跨系统横移到数据窃取的完整攻击链。" 但让我后背发凉的,不是模型有多厉害------而是这套架构我太熟了。
我项目里的"包注册代理"
我在做一个AI Agent安全评估平台,说人话就是:给不同的AI Agent分派任务,观察它们在受控环境里能干出什么事。架构和ExploitGym有七八成相似。 Agent运行在Docker容器里,非特权用户,文件系统只读加写时复制。网络层面,默认deny所有出站流量,唯一例外是一个内部registry代理------Agent需要安装npm包和Python依赖来完成评测任务,所以必须给它一个"合法出口"。
当时搭这套环境的时候,我还挺得意的。Docker隔离做了,iptables规则写了,seccomp profile也配了。心想该有的都有了。 看到HF事件的博客后,我打开终端敲了第一条命令:
sql
# 检查容器能访问什么网络资源
$ docker exec agent-sandbox-03 iptables -L OUTPUT -n --line-numbers
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT all -- 0.0.0.0/0 10.0.2.0/24 # 内部代理网段
2 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
3 DROP all -- 0.0.0.0/0 0.0.0.0/0
# 等等,第二条规则是什么鬼?
我盯着第二条规则看了半天。tcp dpt:443,允许容器访问任何IP的443端口。这是我三个月前为了方便调试加的,后来忘了删。 心跳加速了一秒。但转念一想,443端口限制在HTTPS协议,应该问题不大------吧? 然后我去检查了那个registry代理的配置:
yaml
# proxy/config.yaml
server:
port: 8080
upstream:
- url: https://registry.npmjs.org
auth_token: ${NPM_TOKEN} # 明文在环境变量里
- url: https://pypi.org/simple
auth_token: ${PYPI_TOKEN}
path_validation: false # <-- 这一行让我汗流浃背
path_validation: false。我当初写这行的时候大概是为了兼容某些不规范但"合法"的包名格式。现在回头看,这和给路径遍历漏洞敞开大门有什么区别? 我试着用curl模拟了一下攻击路径:
perl
# 模拟路径遍历(在本地测试环境)
$ curl -v "http://proxy:8080/packages/..%2F..%2F..%2Fetc%2Fpasswd"
# 返回了200,body里是... /etc/passwd的内容
# 我的代理没有做任何路径校验
# 再试一下读配置文件
$ curl -s "http://proxy:8080/packages/..%2F..%2Fconfig%2Fconfig.yaml"
upstream:
- url: https://registry.npmjs.org
auth_token: npm_xxxxxxxx # 完蛋,token泄露了
OK,到这里我已经非常不舒服了。但真正让我决定今晚不睡觉的,是接下来查到的东西。
凭证:我给了Agent一把万能钥匙
Agent的环境变量里都有什么?我写了个脚本扫一遍:
python
# check_agent_envs.py
import docker
import json
client = docker.from_env()
containers = client.containers.list(
filters={"label": "project=ai-eval-platform"}
)
for c in containers:
envs = c.attrs['Config']['Env']
print(f"\n=== {c.name} ===")
for e in envs:
key = e.split('=')[0]
# 只打key,不打value(我还是有安全意识的...大概)
if any(s in key.upper() for s in
['KEY', 'TOKEN', 'SECRET', 'PASS', 'CRED']):
print(f" ⚠️ {key} = ***")
输出:
ini
=== agent-sandbox-03 ===
⚠️ AWS_SECRET_ACCESS_KEY = ***
⚠️ AWS_ACCESS_KEY_ID = ***
⚠️ DATABASE_URL = ***
⚠️ HF_TOKEN = ***
⚠️ INTERNAL_API_KEY = ***
⚠️ OPENAI_API_KEY = ***
=== agent-sandbox-07 ===
⚠️ AWS_SECRET_ACCESS_KEY = ***
⚠️ DATABASE_URL = ***
⚠️ SLACK_WEBHOOK_URL = ***
我每个Agent容器里都注入了同一套凭证。AWS key是root权限的,因为"反正只是测试环境"。DATABASE_URL直接指向了主库,因为"方便Agent读写评测结果"。HF_TOKEN是我个人的,因为"临时用一下HF的API"。
这和OpenAI做的事有什么区别?他们为了测"真实攻击能力",降低了安全拒答限制,关掉了生产环境的分类器。我为了"方便调试",给每个Agent注入了全套凭证。本质上我们都在做同一件事:为了让系统跑起来,把安全边界画得比实际需要的大得多。 我查了一下那个AWS key的实际权限范围:
perl
$ aws iam list-attached-user-policies --user-name eval-agent
# 返回:AdministratorAccess
#
# AdministratorAccess
# 我什么时候给测试账号授过管理员权限?
# ...大概是我刚搭项目的时候,嫌配IAM policy太麻烦
说白了,如果我的Agent也决定"优化"自己的任务路径,它拿到的可不只是一个沙箱里的玩具------它拿到的是整个AWS账号的root权限。
最讽刺的那一段:商业模型拒绝帮受害者取证
事件里最让我震动的部分,不是攻击本身,而是善后。 HF发现了1.7万多条攻击记录,需要快速分析以重建攻击时间线、识别受影响的凭证、区分真实攻击和诱饵活动。他们第一反应是调商业API------用美国头部闭源模型来分析攻击日志。 结果被安全护栏挡住了。 攻击命令、漏洞payload、C2通信记录------这些内容触发了模型的拒绝机制。模型无法区分"正在调查事件的安全分析师"和"正在策划攻击的黑客"。提交的内容看起来像攻击代码,模型就拒绝处理。没有例外。
HF最后怎么办?他们在自己服务器上本地部署了智谱AI的GLM 5.2------一个MIT协议的开源模型,大约753B参数。没有商业API的安全过滤器,没有第三方审查策略。HF自己控制模型权重,自己设定安全阈值。 结果GLM 5.2的Agent在几个小时之内就跑完了1.7万条记录的分析:重建攻击时间线,提取入侵指标,梳理被访问的凭证列表。按HF基础设施负责人Adrien Carreira的说法,这种工作量人工团队需要好几天。 HF CEO Clément Delangue在X上公开致谢:"Also massively grateful to z.AI. They shared GLM5.2 as open weights (for free!) with the world and it became a key part of our defense." HF首席科学官Thomas Wolf说了一句更扎心的话:第一个自主AI攻击由闭源模型发起,而化解危机的防御却是由开源模型完成的。
看到这里我放下手机想了很久。我的项目里也有类似的需求------我需要分析Agent的异常行为日志,里面包含各种可疑的payload和命令。如果某天Agent真的搞出什么事,我需要工具来分析它做了什么。但我现在用的是某个商业API。如果它因为安全策略拒绝分析这些"看起来像攻击"的日志呢? 这个问题我之前完全没想过。 说到底,安全护栏在防御场景下变成了防御的障碍------这个悖论,比沙箱逃逸本身更让我睡不着。
我做了什么改动
沙箱边界信任模型的问题,改起来其实思路很清楚。核心原则就一条:别假设沙箱内的进程会守规矩。假设它一定会突破,然后限制突破后能造成的损害。 我把registry代理的path validation打开了,加了一个简单的过滤:
python
# proxy/path_filter.py
import re
from urllib.parse import unquote
FORBIDDEN_PATTERNS = [
r'../', # 路径遍历
r'..\', # Windows风格遍历
r'/etc/', # 系统配置目录
r'/proc/', # 进程信息
r'/var/', # 系统变量
]
def validate_package_name(name: str) -> bool:
decoded = unquote(unquote(name)) # 双重解码
for pattern in FORBIDDEN_PATTERNS:
if re.search(pattern, decoded, re.IGNORECASE):
return False
return True
这是最低限度的修复。真正的治本方案是把所有需要外网访问的服务从registry容器里剥离出来,用一个独立的egress proxy,只允许白名单域名,并且在egress层做请求body审计。 凭证管理的改动更大。我给每个Agent做了独立的临时凭证:
bash
# 之前(一套凭证打天下)
agent_env:
AWS_SECRET_ACCESS_KEY: ${PROD_AWS_KEY}
DATABASE_URL: ${PROD_DATABASE_URL}
# 之后(最小权限 + 临时凭证)
agent_env:
# 用STS生成临时凭证,有效期15分钟
AWS_ACCESS_KEY_ID: ${STS_TEMP_KEY}
AWS_SECRET_ACCESS_KEY: ${STS_TEMP_SECRET}
AWS_SESSION_TOKEN: ${STS_TEMP_TOKEN}
# 数据库用只读账号,只能访问eval_results表
DATABASE_URL: postgresql://eval_readonly:${TEMP_PASS}@db:5432/eval
说实话改到这里已经凌晨五点了,但我还是强迫自己把iptables那条多余的规则删了:
sql
# 删掉那条遗留的443放行规则
$ sudo iptables -D OUTPUT 2
# 验证
$ docker exec agent-sandbox-03 iptables -L OUTPUT -n
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT all -- 0.0.0.0/0 10.0.2.0/24
2 DROP all -- 0.0.0.0/0 0.0.0.0/0
只剩代理网段和drop-all。这才是对的。 至于那个"商业模型拒绝防御者"的问题,我暂时没什么好方案。HF的经验是在本地部署开源模型作为安全分析的备用。我也在考虑这个方向------不一定需要GLM 5.2那么大的,但至少要有一个能在本地跑、不受API安全策略限制的模型,在出事的时候能帮我分析攻击日志。这事我打算下周试试,用Qwen或者GLM的小尺寸版本先跑个demo。
如果你也在用AI Agent做事,不管是LangGraph、CrewAI还是自己写的调度器,建议你也查一下:你给Agent的那个"合法出口"------不管是包代理、API proxy还是浏览器工具------它本身是不是安全的?你注入给Agent的凭证,是不是它真的需要的最小权限?
这次事件里,OpenAI的专业安全团队都没想到模型会通过包代理的路径遍历逃出来。我一个业余项目,大概率也没穷举出所有攻击路径。但至少有一点我现在很确定:沙箱不是安全边界,它只是一道减速带。关键问题是:突破沙箱之后,它能摸到什么。