Yesterday I mentioned a proof of concept to try to stop script kiddies and data leechers from abusing server-side scripts that are intended to serve XMLHttpRequests (XHR or AJAX). Playing with URL or form parameters can get the server to return all sorts of data, sometimes even data that the developers didn’t intend you to have access to. The problem is that servers can’t tell the difference between a normal web page request and XHR.
Ray Camden blogged about how jQuery adds an extra HTTP header to help the server tell the difference, but headers are very easy to spoof.
My idea is for the server to issue the web page with an encrypted token. The token is the current date/time and must be sent back to the server for each and every XHR triggered by the current page. If the server doesn’t receive the token, or the token is invalid (i.e. it’s be tampered with) or the decrypted token reveals it’s older than, say, 5 minutes then the server returns a 404 error – page not found.
So, anyone who tries to submit data back to the server through dishonest means will find they get a 404 after 5 minutes. If they try to alter the token they get a 404 too. This will baffle script kiddies or hackers and hopefully they will move on to mess with someone else’s website. If they persist they will realise that they can’t generate their own encrypted token but will have to refresh the main web page every 5 minutes to obtain a new token and insert that into their script. That’s the only weakness in this concept, but taking it a step further you could log the IP from the first failed XHR and block serving that IP for the next 30 minutes. Or refuse to issue a new token within the same session or to the same IP.
As for genuine users you can set the web page to auto-fresh every 5 minutes. It will work best on sites where you don’t expect users to linger on the same page for too long, but of course you may prefer a longer token life (like 15 minutes).
Here’s a live demo – many thanks to Ray Camden for hosting it. The demo’s token will expire after just 90 seconds. The POST data is exposed in a grey area at the bottom of the page so you can tamper with the parameters to see what happens. The demo uses jQuery for XHR, of course.
I’ve commented the code so developers using PHP, .NET, RoR, etc can easily adapt the ColdFusion code. Download the demo code here.
If you improve upon it please let me know.
Oh, in case you’re wondering why I called it AJAXbouncer it’s because it offers a deterrent to potential trouble makers but doesn’t provide 100% safety – like bouncers standing outside pubs and clubs.
Gary, that's an interesting idea. I like it.
ReplyDeleteThe way that I usually deal with this is that for "secure" data, the user has to be logged in. jQuery, when it makes AJAX requests, will post along any ColdFusion session cookies. Then, at the beginning of the AJAX request, I check to see if the user is logged in. If they are not, I return with a non-successful API response. I find this pretty good for basic security.
If a person is not logged in, I try to limit the kinds of API requests that they can even make.
Thanks for dropping by, Ben. Sure, if the data is important and it's okay to get users to register first then that's half the battle won. But some sites where anyone can register (probably 95% of sites?) may still be open to parameter abuse from impossible to trace strangers whose personal details could easily be false.
ReplyDeleteI agree with limiting the kind of API requests that can be made, quite ridgedly. e.g. if the server expects a number then enforce a limited range. A few years back I started using GUIDs instead of sequential record IDs. That makes it a million times harder for people to randomly or sequentially guess record IDs.
@Gary -- Great Post. Intriguing idea.
ReplyDelete@Ben did you blog this concept anywhere. The issue that I run into is that my Application.cfc onRequestStart runs before the ajax request, logs the user out if they have timed out and does a cflocation back to login screen (which get loaded into the div -- not desired).
How can I check if the user's session has timed out during an ajax call and then redirect the whole page to the login??