WebSockets and Meteor: Introduction to WebSockets for Penetration Testers
From HTTP to WebSocket
WebSocket is an HTTP extension that enables full-duplex communications between a browser and a web server. It is designed to provide an efficient and flexible transport mechanism for low-latency apps with a high volume of discrete messages sent in both directions. A WebSocket connection is created when the client and server complete a protocol upgrade handshake consisting of a single request and response. After the handshake is complete, the client and server can asynchronously exchange data using a message format specified in RFC 6455.
The differences between HTTP and WebSocket are stark:
- WebSocket messages must be sent inside an upgraded HTTP connection.
- HTTP is stateless[1]; the meaning of a request is unaffected by any other request or anything else happening in the transport layer. A WebSocket connection can have state in a variety of ways. One common example involves a client subscribing to a dynamic collection of data records. The server will send the collection’s initial contents, followed by updates each time a record is added, changed, or deleted. This pattern is stateful in that the server must remember what records it has already sent. If the connection is restarted, that state will be lost and rebuilt from the ground up.
- HTTP is synchronous, whereas WebSocket communications act asynchronous, which allows messages to be sent in either direction at any time, even in both directions simultaneously.
WebSocket also represents a major break from the same-origin policy. A standards-compliant browser will open a WebSocket connection to any URL on any origin without sending a preflight OPTIONS request or checking CORS headers in the handshake response. How, then, should an application safely authenticate incoming WebSocket requests and avoid cross-origin WebSocket-hijacking vulnerabilities?
The RFC suggests (see Section 10) validating both the Origin header and the session cookie in the browser’s handshake request. Assuming the session cookie’s SameSite attribute is either Lax (the default) or Strict, this approach should work. However, it has other shortcomings:
Because cookies cannot be set from within an established WebSocket connection, the RFC’s approach requires the browser to authenticate over HTTP instead of over WebSocket. That may unnecessarily complicate authentication for both browser and non-browser clients.
There is at least one known browser bug that may prevent cookies from being included with WebSocket handshake requests under some circumstances.
For those reasons, the most common WebSocket authentication pattern is to require the client to reauthenticate on every new WebSocket connection. To make sessions last longer than the lifetime of a single browser tab, the client can save a medium-term session token in localStorage and use it to authenticate when the user visits the page again.
The asynchronous nature of WebSocket defines the protocol’s significance to the Web. It also has critical implications for how security testers interact with the protocol.
Under HTTP, most security testing tasks fundamentally rely on the fact that each request is paired with a single response. For example, SQL injection scanners usually send a series of requests with injection payloads, then check each response for error messages or other changes in the output. In both cases, each trial is conducted with a single request, and a single response determines the result of each trial.
With WebSocket, this assumption breaks down. A successful SQL injection attack might result in a change in the number of messages the server sends in response to a search query, rather than a change in the contents of any one message. Even if the response is contained in a single message, there is no guarantee this response message will be the next one sent by the server.
Therein lies the reason why BurpSuite’s Scanner and Intruder do not support WebSocket: That would be impossible. An automated vulnerability scanner can only do its job against a WebSocket app if it has been programmed with some understanding of the app’s message semantics. Thus, an adequate penetration test on a WebSocket application requires development of bespoke tools designed with the target application in mind. In part 2 of this series, we will discuss Meteor, a JavaScript web application framework that makes heavy use of WebSocket, and investigate the framework’s weaknesses and attack surface. Part 3 will introduce a vulnerability scanner for Meteor applications that exploits known weaknesses and provides a toolkit for implementing other attacks.
Footnotes
1. Although HTTP supports persistent connections with the Keep-Alive and Connection headers (see Section 8.1 of RFC 2616), they are used to improve efficiency and have no impact on request semantics. HTTP/2 takes this concept even further by pipelining multiple requests and responses in a single TCP stream, but the messages still have the same meanings as in HTTP/1.1 (see generally RFC 7540).
Credits
This article was written by former Leviathan employee Cliff Smith. You can get in touch with Cliff on his LinkedIn profile.