/FROM0TOPENTESTINGHERO

Cross-Site Websocket Hijacking

Not so long ago, to make website’s content appear in real time it had to be kind of simulated. For example from the level of JavaScript - by sending a request to the server every few seconds and downloading the latest content. The more often we sent requests, the faster the user got the response. These times are behind us. Now, for this purpose, websockets are used - they allow for two-way communication between the client and the server in real time.

How websockets work

Source: https://www.pubnub.com/learn/glossary/what-is-websocket/

But what traps we can fall into if we want to implement this functionality on our site? Today’s episode of “from 0 to pentesting hero” is about a little-known attack named: Cross-Site Websocket Hijacking. You will learn more about this from the blog of the person who invented it. For this video I will use a trivial websocket implementation in Java as an example.

Websocket implementation

Let’s look at the code. Due to the simplicity of the example, only incoming messages are supported here. If something come up in the websocket - I append the content with the text: secret socket and return it to the user. At the same time, we have a very simple code in JavaScript, that is trying to connect to the endpoint, and if this happens - send a simple message hey to it.

JavaScript code

In addition, we listen to all messages from the server and display them in the form of alerts - so that they are easily visible. Let’s see how it works in practice. After refreshing the page - we get the message: Secret socket: hey. So how does this communication work? Let’s follow it in Burp. At the beginning, our site sends http or https request to the server, in which it informs that it wants to switch to websockets.

HTTP request

In response, the server returns the code 101 - and from this moment communication is no longer based on the http protocol but the websocket frames.

HTTP response

So where is the vulnerability today?

Websocket frame

To simplify the example as much as possible, user authentication method is not visible. The protocol itself does not define how the server should check whether the person has access to the given resource. Therefore, cookies are usually used for this purpose. Let’s create an example cookie with test content and let’s see our example again. We see that value of the cookie has been sent in our request - let’s assume that the server identified the user based on it and granted access.

Cookie

The problem is that the browser will automatically attach a matching cookie during the execution of the request. And it does not matter where this request comes from. We know this functionality - creator of any page can attach a facebook button on the website - and when we visit it, facebook knows that we are logged in - because the browser automatically sent the cookie to the server.

Like button

At the same time, the creator of the website - knows nothing about the cookie - this is how modern browsers work - cookies can only be sent to the server if the hostname, protocol and port match. This is something that can be nice from the viewpoint of usability, however can be a danger in terms of security. Let’s create the attacker’s website, available at baddomain. Here we place the same JavaScript code as before. As you can see, this code worked again. How is this possible?

In the JavaScript code, we’re referring to a gooddomain. As we know - when initiating the connection with the websocket, the http request is sent first. So, when browser saw gooddomain it attached the matching cookie. The server checked the cookie and returned the appropriate socket.

So now the baddomain can read and write any data using this mechanism. Pay attention to the fact that the attacker didn’t have to possess any login and password, it is enough that the person that was attacked logged into the gooddomain and his cookie was still correct and existed in the browser’s storage.

Origin header

So how to protect yourself against this attack? Apart from attaching cookies to requests, the browser also adds the origin header, which contains the name of the domain which the given request originated from. So if we try to connect to the socket using the baddomain, then it will appear in this header. Each browser is required to include this header automatically. We can therefore check its content on the server side - and verify if the request comes from our website or from an external site. Then - even if the cookie is correct - and the request comes from a foreign domain - we will be able to recognize and reject it.

Implementation

The implementation is not complicated. We create an additional Configurator class in which we implement the checkorigin function. We check the value of the origin header here - comparing it with the name of the correct domain. If they match - we allow access. Let’s check our case one more time. First, on the correct page - everything still works fine.

Now baddomain. As you can see, we did not receive an alert. If we take a closer look - we will notice that this time, the server returned 403 error during the initialization of the connection.

403 Error

Thus, despite sending the correct cookie - the server rejected our request and the browser did not receive access to the websocket. And that’s all in today’s episode. I hope that this rare vulnerability was interesting to you.