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:
- Meteor is a node.js framework. Server code is in javascript
- good Meteor code is written so as to be used both on the client and the server.
- 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.
- The good client tries to send the new post but gets no response from the server.
- The good client tries again which succeeds on the server-side. However, the server response was lost.
- The good client tries a third time, the server reports the post exists.
- 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.
- The good client tries to send the new post but gets no response from the server.
The good client tries againThe attacker sends a post insert which succeeds on the server-side.- The good client tries a second time, the server reports the (attacker’s) post exists.
- 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.
I think you got it wrong. See my comments: http://meteorhacks.com/introduction-to-latency-compensation.html#comment-1517034746
(I’m not a security specialist, if you still found this is something to concern, I can direct the question to someone who can answer properly)
My reply:
You can’t just hand wave that security is somehow orthogonal to the sample code. Your sample actively pretends that security is not a problem. Please stop teaching insecure code.
His reply:
And my reply:
Yeah, ” Please stop teaching insecure code really. Is that the point of this article.”
“You are just playing with me or playing yourself. I will stop here and good luck. ”
This was too strong. Because I understand that you are trying to present a concept with a simple example.
Understand that I have a high degree of frustration around the insecure code that I keep on seeing popping up in production code.
Over and over again.
Rails apps had this problem with mass assignment ( google “Rails mass assignment”) PHP has this problem with sql injection.
Why did so many Rails apps have the same class of problem: because the developers were taught that this (insecure) way of doing things was correct.
Why do PHP apps have sql injection problems? Because PHP examples suggest that sql statements can be constructed with concatenation using user input to form the sql string.
Now we will start with Meteor apps having the same common vulnerability: “this is how you do latency compensation, ain’t it cool” … I will predict that if meteor gets significant mind share that there will be a metasploit (http://www.metasploit.com/) module that fingerprints and attacks Meteor apps for exactly this vulnerability.
I can see the DefCon talk now.
Hello Patrick
I’m fairly new to the Meteor JS Framework. Do you have any source code that proves the points shown in this article? I would like to test and understand it.
Hi Mario —
I didn’t. Keep in mind that the attacks specified are opportunistic – meaning a large number of attempts (>100,000) would be needed to get a single success.
Because of this, developers dismiss such possible attacks as “1 in a million” where “1 in a million” is the same as “impossible”. As of July 2014, Twitter gets over 58,000,000 tweets a day. In other words, a “1 in a million” event happens 58 times every day. or once every 30 minutes.
An example of how this “1 in a million” attitude is just wrong are timing attacks. It is possible to determine a user’s password with a timing attack.
For example, checking password like this will reveal the user’s password:
return Arrays.equals(hmac.doFinal(), sigBytes);
Read more about timing attacks here
Its hard enough to defend against attacks – so why make securing a website even harder by allowing an attacker to pick their own database id?
Hey Patrick,
Interesting discussion here. In theory, you could create an example Meteor app and use timeouts and similar tricks to simulate this, no? I think this might garner more serious attention & scrutiny from the Meteor community. Just my 2¢. I’d actually love to see something like that. We’re in the process of optimizing a Meteor app currently in beta stage, which is implemented entirely using methods. Of course, it’s very tempting to change some of this to client-side insertion to leverage latency compensation as it makes the UI appear much more responsive.
I have watched the security field for years, but I am not a security expert. The conclusion I have reached is that it is best to be absolutely paranoid. Anything that if altered could cause harm should not be allowed. It doesn’t matter if I as a non-expert can’t think of how the ability to alter or pick client ids can be used. I have to assume that people who spend their entire professional life and waking day will figure out how to leverage that ability in some way. I am just better off by not allowing the client to ever pick the database ids. Who knows, maybe someone does a SQL injection into the id field? or maybe some sort of xss attack?
Security blackhats like the NSA are paid very well and they know way more than I do.
Once again: If something bad could happen if the client could give a bad X, don’t allow the client to do X – even if you don’t know how the bad X could be generated.
As a side note: I will have to try out XSS injection in an id.