Leviathan Security Group - Penetration Testing, Security Assessment, Risk Advisory

View Original

WebSockets and Meteor: Attacking Meteor Applications with eighthundredfeet

Features 

In its current version, eighthundredfeet can perform the following tasks: 

  • Extract names of enabled packages, publications, and methods discernible from the application’s browser JavaScript code. 

  • Guess usernames, passwords, method names, and publication names using a wordlist, thereby replacing HTTP security tools like Hydra and dirbuster. 

  • Alter data types of method and publication parameters to identify type confusion bugs that may create NoSQL injection vulnerabilities. 

  • Run fuzzing attacks against method call and subscription messages using regular expression replacements and a wordlist. 

  • Extract DDP messages from a Burp project file, including identifying and deduplicating type confusion targets. 

  • Open a harness web server that translates HTTP requests to WebSocket messages, enabling pen testers to leverage Burp’s scanning logic against a Meteor app. 

Up-to-date documentation, including a brief tour of the code and advice on adapting eighthundredfeet to other WebSocket protocols, is available in the project’s README.md file

Extracting DDP messages from Burp project files 

Some of the attacks described above, such as type confusion attacks, require samples of valid method call and subscription messages.  If the user is using BurpSuite on the target app, such sample messages should be stored in the Burp project file.  However, the BurpSuite extension API has zero support for WebSockets, and the Burp project file format is not an open standard.  Reverse engineering the file format would be a major hassle, but luckily, there is no need to. 

Cursory examination of a project file shows that WebSocket messages are stored in cleartext and are preceded by two length fields: 

HxD hex editor screenshot of Burp project file showing record length field (000053), message length field (00004b) and WebSocket message body

This sample message is preceded by two 32-bit big-endian integers with values 83 and 75.  Since the message itself is 75 characters long, these two length fields appear to contain, respectively, the length of the entire message record, including the eight bytes for the two length fields, and the length of the raw message.

These observations, combined with the fact that DDP messages follow a predictable format, are enough to reliably extract messages from a Burp project file.  That enables eighthundredfeet to use a project file to seed its fuzzing and type confusion attacks with information about the application’s attack surface.

Leveraging HTTP vulnerability scanners with the harness server

Part one of this series discussed why HTTP vulnerability scanners cannot simply add support for WebSockets. Since WebSockets lacks a neat pairing between requests and responses, scanning tools cannot interpret the impact of an action taken by the client without some understanding of the application’s communication protocol.  But since eighthundredfeet is programmed to understand DDP, is it possible to lend that understanding to Burp?

Again, Burp’s extension API does not support WebSockets at all, so building this functionality into Burp with custom Scanner checks is a non-starter.  Writing a Meteor package that accepts traditional HTTP requests and translates them into DDP messages may be possible but would only be feasible for clear-box engagements where the application can be reconfigured and redeployed.

The most flexible approach is to attack the problem from the middle.  eighthundredfeet implements a harness server that acts as an adapter between HTTP and WebSockets.  It accepts POST requests at /proxy /proxy with JSON-formatted DDP messages in their bodies and forwards them to the Meteor app.  Since eighthundredfeet knows how to interpret DDP messages, it can construct an HTTP response that represents the result of the client’s original DDP message.

The harness server’s full process for handling an HTTP request to /proxy is as follows:

  • Open a DDP WebSocket connection to the target application

  • Perform any necessary connection setup, including sending the connect message and logging in

  • Forward the HTTP request’s body to the Meteor application as a DDP message

  • Listen for DDP messages from the Meteor app that could represent part of the message response chain.

  • Await the Meteor app’s response and send it back to the client as an HTTP response with Content-Type: application/json.

Diagram illustrating HTTP communications between browser and harness server, and WebSocket communications between harness server and Meteor app.

In effect, the harness server creates a second application running on localhost:9010 with the same attack surface and vulnerabilities as the Meteor application, but that communicates over HTTP.

Before any of Burp’s tools are run against this new HTTP application, Burp’s Proxy history must be populated with sample requests containing valid DDP messages. To that end, the harness server accepts a command line argument specifying a seed file containing a set of DDP messages. The confusertargets command generates a deduplicated list of method call and subscription messages that can then act as a seed file for the harness server.

Once the harness server is up, the user can then visit http://localhost:9010/seed in their browser. JavaScript running on this page will automatically send requests to /proxy with the DDP messages contained in the seed file. As a result, Burp’s Proxy history and Site Map will include one HTTP request to /proxy for each of the seed messages, and the Scanner can then be used on any of these requests[1].

In addition to Burp’s active scanning tools, the harness server enables the use of various specialized web security tools against Meteor apps. Virtually every major class of vulnerability found in web applications has at least one open-source exploitation tool, and any of these tools that can formulate a JSON-formatted HTTP request body can now attack a Meteor app via the harness server.

Handling Subscription Output

Recall that when a client subscribes to a publication, the server usually responds with multiple messages containing the documents constituting the publication, and that these documents can even belong to multiple collections. For most of the tasks in eighthundredfeet’s feature set, such as brute-forcing publication names, the only output needed to determine the result is the ready message indicating that the publication has been populated, or the error message indicating that the publication name was invalid. Other tasks, such as purpose-built fuzzing or injection attacks, may need to capture the actual documents returned from a subscription.

To help with such tasks, the DDPClient class automatically stores a client-side copy of all documents sent from the server, keeping them up to date with each message that adds, changes, or removes a document. Whenever the harness server receives documents back from the Meteor app, it will return them to the client in a response body with this structure:

{
  "finalMessage": {
    "msg": "ready",
    "subs": [
      "91fd8d76-ab97-45e8-8696-4c6a6b038e04"
    ]
  },
  "collections": {
    "users": [
      {
        "emails": [
          {
            "address": "admin@example.com",
            "verified": false
          }
        ],
        "username": "admin",
        "id": "PowDZLy5jGoeazfo9"
      }
    ],
    "movies": [
      {
        "title": "Citizen Kane",
        "id": "Bj7PJyZRRBG57hPxG"
      }
    ]
  }
}

Documents from extraneous collections can wind up in the results if the server sends them to the client gratuitously. In the example above, the server sent the current user document to the client when the user logged in, so in addition to the movies collection, there is a users collection as well.

If the client terminates a subscription with a nosub message and reopens that subscription in the same connection, documents that the client has already received will not be present. This behavior can make it impractical to reuse existing connections for certain testing tasks, such as fuzzing against publications.

The simplest way to get around this problem is to use the harness server and an HTTP client for such tasks, because the harness server is designed to send each message in its own isolated DDP connection. Alternatively, the attack script could disconnect and reconnect the client each time a trial is finished:

let msg = {'msg':'sub', 'name':’pub1', 'params':[{'key':'k'}]};
let values = [/* ... */];
let client = new ddp.autoClient(args);
let results = {};
let currentValue = undefined;
client.on('booted', () => {
  currentValue = values.shift();
  let myMsg = util.ejsonClone(msg);
  myMsg.params[0].value = currentValue;
  myMsg.id = util.randomId();
  client.send(myMsg);
});
client.on('ready', (readyMsg) => {
results[currentValue] = client.collections.collections;
if (values.length == 0) {
  client.close();
  console.log(JSON.stringify(results, 4, 4));
} else {
  client.reconnect();
}
});
client.start();

Conclusion

Testing a WebSocket application is not as easy as testing a traditional HTTP app, and it probably never will be.  Stateful protocols are generally more complicated than stateless ones, and the stateful logic that the testing client needs to understand will vary from application to application.  eighthundredfeet demonstrates two approaches to this problem:

  • Developing security tools that perform attacks over the target app’s WebSocket-based protocol

  • Using an application-specific adapter to bridge the gap between HTTP and WebSockets so that existing HTTP security tools can communicate with the target WebSocket app

As always, the main factors that determine the quality of a pen test are the skill and effort of the tester. Tools such as eighthundredfeet are designed to clear unnecessary complications out of the way so that the user can spend their time thinking creatively and looking for interesting bugs.

Feel free to submit any pull requests or issues for eighthundredfeet at the project’s GitHub page.

Footnotes

1. Meteor implements DDP using the SockJS library, which supports HTTP as a fallback transport in environments where WebSocket is unsupported (even though Internet Explorer 9 is the last major browser to lack support for the protocol). Therefore, an HTTP-only client can communicate with a Meteor app over DDP. But this fallback protocol requires the client to poll the server for server-to-client messages and does not synchronize messages into request-response pairs. For that reason, SockJS’s HTTP transport does not let HTTP-only security tools operate on a Meteor app.

Credits

This article was written by former Leviathan employee Cliff Smith. You can get in touch with Cliff on his LinkedIn profile.