| 研究员 | Dostxodjayev Abdullox (@squeeze440) |
| 公告 | GHSA-5293-fcm6-fh8v |
| CVE | CVE-2026-87009 |
| CVSS 3.1 | 5.4(中危)— CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L |
| 弱点 | CWE-352、CWE-306、CWE-346 |
| 状态 | 已在 v0.46.0 中修复 |
摘要:inference-gateway 默认绑定到 0.0.0.0 并关闭身份验证(AUTH_ENABLED=false),其 ANY /proxy/:provider/*path 透传路由会无条件剥离调用方提供的 Authorization 头,并在转发上游之前将其替换为网关运营方自己服务器配置的提供商 API 密钥,且没有任何 CORS 策略,也没有任何形式的 CSRF 防护,这使得受害者浏览器访问的任何网页都能静默地通过受害者自己的 OpenAI/Anthropic 等账户发起计费的 LLM 请求。
产品:inference-gateway/inference-gateway — 自托管、云原生 LLM 网关(Go、Gin)。
测试版本:commit 6677da6afd0a606899f833c2351635edeef386f5(main,2026-08-04)。受影响版本:<= 0.45.0。
三个相互独立的事实共同构成了该漏洞。
1. 默认关闭身份验证且绑定到公网。
config/config.go:77 — AuthConfig.Enabled 默认为 false。config/config.go:94 — ServerConfig.Host 默认为 0.0.0.0。在身份验证禁用的情况下,NewOIDCAuthenticatorMiddleware(api/middlewares/auth.go:27-30)返回 OIDCAuthenticatorNoop,其 Middleware()(api/middlewares/auth.go:48-52)是纯透传。在此模式下,任何路由都没有逐请求的身份检查。快速入门文档 examples/docker-compose/basic/docker-compose.yml 发布了 8080:8080 且未设置 AUTH_ENABLED,因此文档所述的入门路径恰好产生此配置。
2. /proxy/:provider/*path 总是注入运营方自己的提供商密钥。
api/routes.go:102-131(ProxyHandler)调用 applyProviderAuth,api/routes.go:287-312:
func applyProviderAuth(req *http.Request, provider core.IProvider) error {
req.Header.Del("Authorization") // caller's own Authorization header is discarded
token := provider.GetToken() // the operator's configured key (env var, e.g. OPENAI_API_KEY)
switch provider.GetAuthType() {
case constants.AuthTypeBearer:
req.Header.Set("Authorization", "Bearer "+token)
...
不存在使用调用方自身凭据的代码路径;该设计始终替换为网关配置的密钥。结合事实 1,未认证的调用方可以免费获得运营方的真实密钥。
3. 中间件链中任何地方都不存在 CORS 策略和 CSRF 防护。
cmd/gateway/main.go:271 使用 gin.New()(无默认中间件)构建路由器;中间件链(:273-290)为 otel → logger → telemetry → OIDC auth → guardrails → MCP。go.mod/go.sum 中不包含任何 CORS 包。从不发送 Access-Control-Allow-Origin 头。代理处理程序无需自定义头或 CORS 不安全的 Content-Type 即可工作(它转发原始请求体,然后在 api/routes.go:254 处将出站 Content-Type 覆盖为 application/json),因此浏览器发送的“简单”跨源 fetch()(Content-Type: text/plain,无自定义头)无需预检。服务器端的计费请求独立于浏览器读取侧的 CORS 强制执行而完成。
最终效果:受害者浏览器访问的任何源,只要该浏览器能访问到网关(回环、局域网,或者如果运营方遵循了文档所述的 8080:8080 发布模式则为公网),就能通过运营方的真实提供商账户发起任意攻击者选择的聊天补全,零身份验证,且没有任何用户可见的提示。
已使用真实 Chrome 浏览器在两个不同的回环源之间发起真正的跨源请求(网关位于 127.0.0.1,攻击者页面位于 127.0.0.2),动态端到端确认。参见 poc/:
poc/attacker_site/attack.html — 从攻击者源提供的精确页面;其加载时的唯一动作是一次对 /proxy/openai/chat/completions 的 fetch()。poc/mock_upstream.py — 代替 api.openai.com,记录其接收到的 Authorization 头、Origin 和请求体。poc/README.md — 完整运行步骤。观察结果:跨源浏览器请求(Origin: http://127.0.0.2:8000)到达模拟上游时携带 Authorization: Bearer sk-proj-VICTIM-REAL-BILLED-KEY-... 以及攻击者选择的请求体 {"messages":[{"role":"user","content":"CSRF-DRIVEBY-MARKER-8271"}]} — 攻击者页面从未拥有、看到或被要求提供任何凭据。浏览器网络检查确认 POST http://127.0.0.1:8081/proxy/openai/chat/completions [200] 从 127.0.0.2:8000 页面发出。完整的浏览器驱动证据(网络日志、截图)附于 GHSA-5293-fcm6-fh8v。
任何以文档所述默认配置运行 inference-gateway 的运营方,其配置的提供商 API 密钥都可被任何能访问到网关端口的浏览器所访问的网页使用,无需凭据、Cookie 或特殊网络位置,只需“能向网关地址发送 HTTP 请求”。具体而言:运营方自己的提供商账户上发生未经授权的计费/配额消耗,由运营方(或同一局域网上的任何人)在网关运行期间打开的任何第三方网站、广告或被入侵页面盲目驱动。浏览器阻止攻击者读取模型输出(无 CORS 头),因此这是一次盲目的强制交易,而非读取原语。
Origin/Sec-Fetch-Site 检查,也没有 CORS 限制。AUTH_ENABLED=false(文档所述的默认值)时,除 /health 外的每个路由都没有逐请求的身份检查。已在 v0.46.0 中修复(维护者加固了默认值)。建议措施:
/proxy/:provider/*path(以及其他状态变更路由)上要求一个自定义的、非安全列表内的头,这会强制跨源调用方进行 CORS 预检,并给网关一个执行源允许列表的位置。这无需启用身份验证即可关闭“简单请求”绕过。SERVER_HOST 的默认值从 0.0.0.0 改为 127.0.0.1,要求显式选择加入更广泛的接口(正如 Ollama 针对同类漏洞所做的那样)。AUTH_ENABLED=false 且 SERVER_HOST 不是回环地址时,发出启动警告或拒绝启动。README.md / Configurations.md 中记录该风险。Dostxodjayev Abdullox (@squeeze440)