THE SECURITY PS BLOG:
Observations and insights from the Security PS Team.

Websockets: The Importance of a Firm Handshake

In the previous post on websockets security, we discussed several attacks that could occur in websocket implementations when security concerns were overlooked. All of these attacks required a websocket connection to be established between a client (browser) and server. In this post, we will focus on several attacks that can occur during the initial negotiation that establishes the websocket connection, and I'll also point out the security practices that strengthen the negotiation to defend from such attacks.

Handshakes

Before discussing any specific attacks, we need to briefly discuss how the initial negotiation, or handshake, process for websockets works. To establish a websockets connection, the client sends an HTTP request to the server with several unique headers. These headers identify the request as a websockets handshake request. One possible example of this request is shown below, with the websocket-specific headers highlighted in green:
GET /chat HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 1RZ1202NWUUIxSAVA/XpFA==

Origin: example.com

If the server supports the websocket protocol, it will respond to the request above to indicate that it is switching the communication protocol. An example of this response is shown below:
HTTP/1.1 101 Switching Protocol
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Hu/nwPq2Q50L0v2bgQwb6Sm7BjY=
Content-Length: 0

Once this response is received by the client, all future traffic will be sent between the client and server through websockets. Now that we have covered the handshake process, we can explore some attacks that target it.

CSRF

In the example above, there is no way to identify the user initiating the request. To identify users, most applications will use some sort of session cookie. This cookie is then sent by the client when making a request, which allows the server receiving the request to uniquely identify the user. For example, using a cookie to identify the user initiating a websockets handshake, a client’s request might look something like:
GET /chat HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 1RZ1202NWUUIxSAVA/XpFA==
Origin: example.com
Cookie: session=9cj0asd9012n3JC89014NJASD

When cookies are involved in any process, developers need to consider how to defend against possible Cross-Site Request Forgery (CSRF) attacks. A CSRF attack takes advantage of the fact that cookies are sent automatically with any request that a client makes. In general, an attacker injects HTML or JavaScript into a web page that causes a visitor's browser to make a request to a different site. The request is aimed at functionality that performs a sensitive action (such as changing a password or transferring money). Then, if the attacker can trick a victim into visiting this web page, the target application will receive a request that appears to be from the victim.

Due to websocket handshake requests being done over HTTP, they are also vulnerable to CSRF attacks. However, unlike a traditional CSRF attack in which an attacker can only make requests as the victim, conducting a CSRF attack against a websockets implementation will allow the attacker to both make requests and receive responses as the victim. This behavior can be used to retrieve sensitive user information by making websocket requests to endpoints that return things like user database identifiers or social security numbers. In addition, due to the requests and responses appearing to originate from the server that handles websockets, these attacks can bypass any CORS protections applied to the application.
CSRF WebSocket Handshake
CSRF WebSocket Handshake

There are many solutions to CSRF, the most common being the use of anti-CSRF tokens. It is important that these same protections are applied to any websocket handshake requests.

Fallbacks

In contrast to the two example handshake requests above, what happens if a request is made to initiate a websocket connection and the client or server does not support the websocket protocol? To handle this case, a majority of websocket libraries will “fallback” into a model known as HTTP long-polling. In HTTP long-polling, the client constantly sends requests with long timeouts to the server. The server will then either respond with information or the request will timeout if there is no new information to retrieve.

While HTTP long-polling is not vulnerable in its design, security vulnerabilities can arise if developers do not account for communication falling back into this mode of operation. For example, one application we examined supported websockets and would verify the original session cookie used to initiate the handshake to handle authorization. However, when the application was forced to fallback into HTTP long-polling mode, HTTP requests would instead include a JWT in their body to handle authorization. Since this behavior was not expected, many of the endpoints in the application did not properly check this JWT. This allowed users who forced a fallback to HTTP long-polling to gain access to unauthorized functionality, including administrative-only operations.

In applications that support websockets, it’s critical to test the fallback workflow to ensure that authorization controls are applied consistently. This can be done by modifying the handshake’s response in a man-in-the-middle proxy, such as Burp Suite or OWASP Zap, to force the connection to fallback to HTTP long-polling. Instead of the “HTTP/1.1 101 Switching Protocol” response, the response should be modified to look like:
HTTP/1.1 200 OK
Content-Length: 0

This response will force most websocket implementations to fallback into HTTP long-polling mode. After doing this, the application can be navigated normally and authorization controls can be verified to work correctly in this mode.

Publishing and Subscriptions

On top of their websocket implementations, many applications will use a publishing and subscription(pub/sub) model to send identical messages to multiple users. For example, in a shared document editor, any changes to the document will be sent to all the users viewing the document. To accomplish this, the client would subscribe to the “document_updated” event. When the document is updated, the server would go through all subscribers and publish this event to all of them.

Clients often subscribe to events immediately following the initial websocket connection. If authorization controls are not enforced on these subscriptions, attackers can subscribe to unauthorized publishers.

In one application we examined, users could subscribe to events regarding document updates. For example, to subscribe to content updates, the (simplified) websocket request looked like:
{“document_id”:”1234-5678-9012-3456”, “event”: “update”}

The application would then send responses over websockets containing information regarding the event:
{“document_id”:”1234-5678-9012-3456”, “event”: “update”, “content”: “new text”}

However, in this application, there were no authorization controls enforced on the “document_id” field. By substituting in different document_id’s, attackers could subscribe to events on other user’s documents. In addition, like many pub/sub models, this application supported the “*” wildcard. This wildcard is commonly used by pub/sub models to mean “all”. In this application, the attacker could subscribe to all document updates from all users by issuing the following request:
{“document_id”:”*”, “event”: “*”}

When pub/sub models are implemented on top of websockets, it’s critical that developers ensure that users have permission to subscribe to a resource before granting it.

Conclusion

So, while websockets handshakes have some unique considerations regarding design and security, they require the same type of security scrutiny that must be applied to all traditional traffic. In particular, developers should take the following steps to secure their websocket handshakes:
  1. Ensure that websocket handshakes have some sort of CSRF protection.
  2. Understand your application’s fallback mechanism and review whether the fallback mode uses a different authorization model.
  3. Restrict subscriptions even if GUIDs are difficult to guess.


    Blogger Comment
    Facebook Comment

0 comments:

Post a Comment