Do not let client code pick ids

I am having a discussion over on Meteor Hacks about how bad it is to let the client code pick ids.

The meteorhacks post shows how to make new items show up in a sample Meteor program before the server has completed its processing. The meteor folks call it latency compensation.

The problem is that latency compensation (in its current form) relies on the client code choosing the permanent id.

Template.addPost.events({
  "click button": function() {
    var title = $('#title').val();
    var content = $('#content').val();

    var post = {
      title: title,
      content: content,
      _id: Meteor.uuid()
    };

    Meteor.call('addPost', post, function(err) {
      if(err) {
        alert(err.reason);
      }
    });

    Router.go('/post/' + post._id);
  }
});

Key points for non-Meteor developers to be aware of:

  1. Meteor is a node.js framework. Server code is in javascript
  2. good Meteor code is written so as to be used both on the client and the server.
  3. Meteor has a ‘minimongodb’ that acts as a partial cache of the items in the server database.

Now if we rely on the client to select the database ids used by the server, there are a number of interesting attacks.

The original example was inserting new Posts. If we wanted to make a more exciting example, a permission table (i.e. Users or Roles could be the attack target).

Example #1: Insert race condition so the attacker can own the inserted post:

The attacker deliberately chooses an id known to exist. If the server code in question uses Posts.upsert() rather than Posts.insert() the attacker can modify an existing post and take control of the post.

Remember that in good Meteor code, the server and the client will share a lot of code, thus it is likely that an attacker could spot upsert() usages. But that isn’t really necessary.

The “counter” is “but, but the original insert will then fail”. However, lets use some real examples.

In the real world, the good client is trying to send the new post to the server over a flaky internet or to a busy server.

  1. The good client tries to send the new post but gets no response from the server.
  2. The good client tries again which succeeds on the server-side. However, the server response was lost.
  3. The good client tries a third time, the server reports the post exists.
  4. Finally, client then tells the server to please add that post to its ‘hot list of posts’ that is always displayed.

Flaky internet / busy servers are a fact of life.

Now make a slight alteration. On step #2 instead of the good client sending the second attempt it is really the attacker.

  1. The good client tries to send the new post but gets no response from the server.
  2. The good client tries againThe attacker sends a post insert which succeeds on the server-side.
  3. The good client tries a second time, the server reports the (attacker’s) post exists.
  4. Finally, client then tells the server to please add the attacker’s post to its ‘hot list of posts’ that is always displayed.

But how could the attacker know the the id that the client used? ( see below for that answer)

Example #2: (modifying an existing record).

However, if instead of Posts.insert() the post creation code is using upsert then the malicious client can happily modify whatever it wants into the database. Because the post upsert code is shared between client and server. The malicious hacker can just scan the code looking for places where upsert is used.

“But, but why would anyone use upsert on Posts creation?” Why was upsert created? Because it is useful for cases where the developer does not want to do a query + insert combination. Client id generation + upsert() = attacker fun.

This can be most fun in situations where an attacker can grant some privileges to the victim. So the victim’s call look successful, but the attacker can still control the database Post entry.

Example #3: ( Flooding the Db – and why the attacker does not need to know the actual id generated)

This is a brute force attack – but lets say that there was a weakness in the way the client chose the id. A predictable pattern. Remember uuid() is concerned with uniqueness NOT unpredictability.

If an attacker can predict what the set of possible uuids will be, the attacker can flood the server with bogus posts having likely ids as predicted by analyzing the uuid() algorithm. Note that an attacker can confirm their ability to predict the id by generating their own posts.

If the attacker targets a table that is likely to have a large number of new entries, the change for the attacker to have a successful collision increases.

Fundamentally, the client should never be trusted with the ability to select anything that effects how things work.

This entry was posted in technical. Bookmark the permalink.

8 Responses to Do not let client code pick ids

Leave a Reply

Your email address will not be published. Required fields are marked *