CVE 状态: 已申请,等待分配。此发现已发布为 GHSA-q94x-p9rc-q89f。在 CVE 分配后,此仓库将重命名为
CVE-YYYY-NNNNN-pasteguard-PoC,并且此横幅将替换为 CVE 链接。
| 研究员 | Dostxodjayev Abdullox (@squeeze440) |
| 公告 | GHSA-q94x-p9rc-q89f |
| CVSS 3.1 | 7.6(高) |
| 弱点 | CWE-352, CWE-942 |
摘要:PasteGuard 0.9.1 中 LLM 代理路由缺少 CORS/CSRF 保护,允许远程攻击者(操作员浏览器访问的任何网站,或本地网络上的任何主机)使用 PasteGuard 的服务器端回退 API 密钥,向操作员配置的 OpenAI/Anthropic API 触发经过身份验证的请求,并通过跨源 fetch() 到 /openai/v1/chat/completions(以及同类的 /anthropic/v1/messages)读取响应。
产品:PasteGuard (github.com/sgasser/pasteguard)
测试版本:提交 100718191499c52934f3b2ccca72ca16373fb765(2026-07-30),package.json 版本 0.9.1
估计 CVSS v3.1:CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H — 7.6(高)
UI:R — 操作员必须在 PasteGuard 运行时让其浏览器打开一个页面(任何网站上的任何页面);不需要其他交互。C:L / I:L — 攻击者自己的 JS 只能读取其自己选择的提示的响应(而不是操作员过去的对话/仪表板数据,这些数据已正确地从 CORS 中排除),但这仍然确认/使用了一个活跃的已充值账户,并以操作员的身份污染了账户的使用/审计记录。A:H — 现实且无限制的影响:攻击者页面可以无限循环此请求,对操作员的提供商账户/配额产生真实的计费使用(财务消耗,可能导致速率限制耗尽或提供商侧的滥用标记),且没有任何速率限制或源检查来阻止。S:U — 影响保持在相同的信任边界内(使用的是代理自身配置的凭据);没有跨越单独的安全权限。详情:
PasteGuard 的 HTTP 层对除仪表板之外的每个路由应用了宽松的通配符 CORS 策略:
src/index.ts:40-45 — const corsMiddleware = cors();(Hono 的 cors() 不带选项时默认为 Access-Control-Allow-Origin: *)通过 app.use("*", ...) 应用于所有内容,仅对 /dashboard 和 /dashboard/* 有明确的例外。正上方的注释(src/index.ts:34-39)显示作者仔细考虑了仪表板的 CORS 暴露,但没有将同样的推理扩展到其下方的代理路由。src/config.ts:153 — host: z.string().default("0.0.0.0"),在 config.example.yaml:13 中确认生效。代理默认监听所有接口,而不仅仅是回环接口,因此这不仅可以从操作员自己的浏览器访问,还可以从局域网访问。src/providers/openai/client.ts:48-53:
// Use client's auth header if provided, otherwise fall back to config
if (authHeader) {
headers.Authorization = authHeader;
} else if (config.api_key) {
headers.Authorization = `Bearer ${config.api_key}`;
}
由于通配符 CORS 策略位于持有此回退凭据的路由之前,任何源都可以 (1) 使浏览器发送不带 Authorization 头的请求,导致服务器附加其自己的真实 API 密钥,并且 (2) 读取 JSON 响应,因为存在 Access-Control-Allow-Origin: *。没有 Origin/Referer 检查或 CSRF 令牌保护这些路由。
概念验证(动态确认,而不仅仅是静态追踪):
config.yaml,设置 providers.openai.base_url: http://127.0.0.1:9091(模拟上游)和 providers.openai.api_key: "sk-VICTIM-SECRET-DO-NOT-LEAK-12345",pii_detection.enabled: false(仅模拟检测器 /health,以避免需要完整的 GLiNER 模型服务),secrets_detection.enabled: false。使用 bun run src/index.ts 启动 PasteGuard,确认 /health → 200。127.0.0.1:9091 上启动一个模拟上游(mock_upstream.py),记录它收到的任何 Authorization 头。http://127.0.0.2:8001/attack.html(不同的字面回环 IP,根据 Chrome 的站点隔离规则——不是同源的 测试)通过 提供真实的攻击者页面。该页面在加载时的唯一操作:
原始证据:~/engagements/pasteguard/evidence/cross_origin_csrf_success.png,~/engagements/pasteguard/evidence/mock_upstream_log.txt。
/anthropic/v1/messages 路由已静态确认(上述文件:行)共享相同的回退 + 通配符 CORS 模式,但未单独通过实时浏览器 PoC 重新运行,因为在 /openai 上已确认根本原因后,遵循了会话的深度优先于广度/5 分钟规则指导。
影响:PasteGuard 操作员浏览器访问的任何网站(恶意广告、被入侵的网站,或鉴于默认 0.0.0.0 绑定而在同一局域网上的攻击者)都可以通过操作员本地的 PasteGuard 实例,静默地通过操作员自己的 OpenAI/Anthropic 账户驱动无限制的计费请求,除了保持标签页打开外无需任何用户交互,操作员除非查看其提供商计费仪表板,否则无法察觉。这是一个直接的财务滥用/配额耗尽向量,其次还会用攻击者选择的内容污染操作员的提供商账户使用/审计记录。
弱点:
修复建议(建议):
api_key 时,不要将通配符 cors() 中间件应用于 /openai、/anthropic、/codex(以及 /api/mask,如果它将来扩展到使用服务器持有的凭据)。至少,使这些路由的允许源显式/可配置(例如仅浏览器扩展的已知源),默认不进行跨源访问,而不是 *。src/index.ts 中已应用于 /dashboard 的推理保持一致。src/config.ts:153 和 config.example.yaml:13 中的 server.host 默认值从 0.0.0.0 更改为 127.0.0.1,要求显式选择加入才能绑定所有接口。致谢:Dostxodjayev Abdullox
报告渠道:仓库中不存在 SECURITY.md(通过 gh api repos/sgasser/pasteguard/contents/SECURITY.md → 404 确认,审计时重新检查)。也没有先前发布的安全公告(gh api repos/sgasser/pasteguard/security-advisories → []),因此这似乎不是先前披露问题的重复。推荐渠道:GitHub 默认的私有漏洞报告流程,位于 https://github.com/sgasser/pasteguard/security/advisories/new。
Authorizationproviders.openai.api_keyconfig.example.yaml:22-24src/providers/anthropic/client.ts:37-61 为 /anthropic/v1/messages 实现了相同的回退模式(x-api-key/Authorization 回退到 config.api_key)——确认为同一根本原因的同类实例,但未独立进行端到端重新验证(见下方 PoC 范围)。localhostpython3 -m http.server 8001 --bind 127.0.0.2fetch("http://127.0.0.1:3000/openai/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: "cross-origin drive-by call, attacker supplied NO api key" }] })
}).then(r => r.json()).then(data => { /* render on page */ });
http://127.0.0.2:8001/attack.html。通过 DevTools 网络检查确认:
origin: http://127.0.0.2:8001,sec-fetch-site: cross-site,攻击者页面没有发送 Authorization 头。access-control-allow-origin: *,HTTP 200,JSON 正文可被攻击者页面的 JS 完全读取。../evidence/cross_origin_csrf_success.png。RECEIVED AUTH HEADER: Bearer sk-VICTIM-SECRET-DO-NOT-LEAK-12345 — 证明 PasteGuard 在服务器端附加了操作员的真实配置密钥,而攻击者页面从未知道或提供它。