Skip to content

Web Sockets

Web Sockets

  • A heartbeat is built in to the protocol
  • Uses the wss:// or ws:// schema
  • Do not tunnel other services though this because it would allow services to preform XSS attacks
  • Websockets do not respect CORS

Handshake

Initial Upgrade Request:

GET /?encoding=text HTTP/1.1
Host: echo.websocket.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Sec-WebSocket-Version: 13
Origin: https://www.websocket.org
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: 9lzQkbbAscpmOiwYWVSgMg==
DNT: 1
Connection: keep-alive, Upgrade
Sec-Fetch-Dest: websocket
Sec-Fetch-Mode: websocket
Sec-Fetch-Site: same-site
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

Initial Upgrade Response:

HTTP/1.1 101 Web Socket Protocol Handshake
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Headers: x-websocket-extensions
Access-Control-Allow-Headers: x-websocket-version
Access-Control-Allow-Headers: x-websocket-protocol
Access-Control-Allow-Origin: https://www.websocket.org
Connection: Upgrade
Date: Mon, 15 Mar 2021 21:32:46 GMT
Sec-WebSocket-Accept: H82Hn1AbYRO3N7uzo6yysC0KdM0=
Server: Kaazing Gateway
Upgrade: websocket

WebSocket-Key

The Sec-WebSocket-Accept value is generated from the initial key sent from the client and a static value in the RFC.

Generating the Sec-WebSocket-Accept:

import hashlib, base64

Static_Value = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
WebSocket_Key = b'9lzQkbbAscpmOiwYWVSgMg=='

out = hashlib.sha1(WebSocket_Key + Static_Value).digest()
print(base64.b64encode(out))
#b'H82Hn1AbYRO3N7uzo6yysC0KdM0='

URL Parsing

  • Web Socket URLs parse different from HTTP URLs
import urlparse
print(urlparse.urlparse('wss://foo/?bar=baz'))
#ParseResult(scheme='wss', netloc='foo', path='/?bar=baz', params='', query='', fragment='')

Authentication/Authorization

  • There is no authentication in the Protocol. The application must use TLS, HTTP Headers or Cookies to authenticate.
  • Need to Connect the Client IP to the account that requested the Websocket Upgrade.

Origin Header

  • Not restrained by Cross Origin Policy
    • Origin is sent in the Upgrade request
    • Make sure that the Server limits the allowed origins by checking the Origin Header in the Upgrade request
    • If this is not set then an attacker can so Cross Origin WebSockets. This includes sending request and getting responses responses

Framing

Source RFC

Tunneling

Security

Authentication/Authorization

Access Control:

Cross-Site WebSocket Hijacking (CSWSH)

Note

Make sure that you are checking the Origin Header

Websockets are not restricted by Cross Origin Policy. You can make a Websocket from another domain and this will send cookies alongside with it. This is up to the server to restrict.

Example CSWSH Request:

GET /v3/channel_1?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV&notify_self HTTP/1.1
Host: demo.piesocket.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://livepersoninc.github.io
Sec-WebSocket-Key: B65ortnvry9gUVYWTRJ+YQ==
Connection: keep-alive, Upgrade
Cookie: _gcl_au=1.1.1222441835.1654180959; _ga=GA1.2.1857463516.1654180960; _gid=GA1.2.265480995.1654180960; _fbp=fb.1.1654180960477.1170827820
Sec-Fetch-Dest: websocket
Sec-Fetch-Mode: websocket
Sec-Fetch-Site: cross-site
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

Example CSWSH Response

HTTP/1.1 101 Switching Protocols
Date: Thu, 02 Jun 2022 14:46:12 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: KsM7mIwWmDR0JLRLdXQ3TtvS18E=
X-Powered-By: Ratchet/0.4.4

Source

Testing

Web Socket Tester:

<html>

<head>
  <title>WebSocket Tester</title>
  <script language="javascript" type="text/javascript">
    var websocket;
    var ping;

    //Setup
    var connect = document.getElementById('connect')
    connect.addEventListener("click", doConnect);
    var disconnect = document.getElementById('disconnect')
    disconnect.addEventListener("click", doDisconnect);
    var send = document.getElementById('send').addEventListener("click", function () {
      doSend( $('#message').val() )
    });

    //Callback Functions

    function doConnect() {
      websocket = new WebSocket( document.getElementById("target").val() );

      websocket.onopen = function (evt) {
          onOpen(evt)
          var ping = setInterval(function () { doPing() }, 1000);
      };
      websocket.onclose = function (evt) {
          onClose(evt)
      };
      websocket.onmessage = function (evt) {
          onMessage(evt)
      };
      websocket.onerror = function (evt) {
          onError(evt)
      };
    }

    function doDisconnect() {
      websocket.close();
    }

    function onOpen(evt) {
      writeToScreen("CONNECTED");
    }

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

    function onMessage(evt) {
      if ((evt.data != "ping") && (evt.data != "pong")) {
          writeToScreen('RECIEVED: ' + evt.data);
      }
    }

    function onError(evt) {
      writeToScreen('ERROR:' + evt.data);
    }

    function doSend(message) {
      if (message != "ping") {
          writeToScreen('SENT: ' + message);
      }

      websocket.send(message);
    }

    function writeToScreen(message) {
      var output = document.getElementById("output")
      output.append(message + '<br /><br />');
    }

    function doPing() {
      if (websocket != "undefined") {
          doSend("ping");
      }
    }
  </script>
</head>

<body>

  <h2>WebSocket Tester</h2>
  Target:
  <input type="text" id="target" value="" />
  <br />
  <button id="connect">Connect</button>
  <button id="disconnect">Disconnect</button>
  <br />
  <br />Message:
  <input type="text" id="message" value="" />
  <button id="send">Send</button>
  <br />
  <br />Output:
  <br /> <pre><div id="output"></div></pre>

</body>
</html>

Testing Script:

python3 ws_tester.py -u wss://example.com -m unauthed-ws.txt -w IFUZZ:./SecLists/Fuzzing/numeric-fields-only.txt -w SFUZZ:./SecLists/Fuzzing/fuzz-Bo0oM.txt -s COUNT:count

Testing Script2:

python3 ws_tester.py -u wss://example.com -m unauthed-ws.txt -w IFUZZ:/./SecLists/Fuzzing/numeric-fields-only.txt -w SFUZZ:./SecLists/Fuzzing/fuzz-Bo0oM.txt -s COUNT:count -s 87331041:checksum -s TxxxxxxxxxOxxxxxxxxxxKxxxxxxxExxxxxxxxN:token