Let’s talk a bit about session riding also referred to as Cross Site Request Forgery (XSRF). It seems to be as common of a vulnerability as it is underestimated in terms of a risk it poses to the data kept in and processed by the application.
The general idea behind the attack is to execute certain command in a legitimate user’s authentication context, thus saving the trouble of hacking into an application the hard way. It’s all about convincing a user, preferably with the highest access privileges possible, to enter a maliciously forged URI or webpage while their session in the vulnerable application is active (i.e. they are logged-in and have a valid session cookie).
An example of a maliciously forged request may look like this:
http://site.com/change_password.php?
id=user1&password=hiya
Which it is pretty easy to hide in an otherwise innocent looking page or a CSS file as, say, a reference to an image. When executed it will change password of a user user1 to hiya.
Another example of such a request is a self-submitting hidden form like this:
...
<body onload='document.myform.submit();'>
...
<form name='myform' method='post' action='http://site.com/change_password.php'>
<input type='hidden' name='id' value='user1'>
<input type='hidden' name='password' value='hiya'>
</form>
...
In order to get it working and not get caught it’s probably best to put it into some kind of a hard to spot iframe out of the screen, as the submitted URI or form will bring the application site that follows successful or unsuccessful execution of a forged command. Needless to say, from the attacker perspective it’s not desirable to show this loaded page to a user, so that they wouldn’t be warned about what’s going on.
It’s worth noting that one doesn’t need to personally convince a targeted user to visit the webpage that the malicious content has been planted on. Instead very often it’s just enough to post some content serving as a bait on a blog, news forum or such and make sure that the targeted person finds out about it. If an attacker is also able to determine victims IP address, they may code the bait-page in such a way, so that the malicious content is served only to the victim’s IP. This way the amount of unsuccessful requests to the application is very likely to stay below the IPS (Intrusion prevention System) alert level. Not to mention that all of these requests seem legitimate (because of the right source IP) and look just like the effect of an expired session, which in fact they are.
Having shown what does the beast look like, let’s look at the ways to fight it
As it has already been mentioned before, to successfully execute this type of attack the application has to be vulnerable to it, meaning that certain variables need to be predictable, such as the id variable in the example above, otherwise, an attacker wouldn’t be able to successfully forge the request.
Basing on the vulnerable example above, let’s walk through possible solutions that may be useful in defending our application.
The first idea comes from an observation that a forged request may actually be distinguished from the legitimate one. The difference of course lies in the referrer field – the illegitimate request is out of the logical context of the application. So the first shot is to
1) define accepted referring locations for each action in the application.
Unfortunately, even though a nice idea, it’s a real pain in the neck when implementing. Let’s think about it for a second or two – in order to get it working we need to define all the legitimate location that this request ma originate from. This means we seriously impair flexibility and in case of even a slight business logic redesign, we need to redesign the referring locations schema or we risk certain functionalities to break.
Since it’s very far from the ideal solution, let’s move on and look for a better one. The next thing that comes to mind is to
2) enforce re-authentication each time a sensitive function is about to be performed.
The idea is to make sure no active session is used to perform certain tasks (like sensitive information alteration) without a legitimate user’s consent. This is done by having two sessions – one long lasting allowing to perform certain insensitive operations, and the other one, expiring after couple of minutes, allowing to perform potentially harmful operations.
It is a pretty good safeguard in the sense that it lowers the chance for all the session hijack based attacks to succeed, nevertheless, it has its cons and does not relive us from the risk of such an attack being successfully performed.
The obvious con is an authorisation overhead. Re-authorising over each sensitive action that is about to be performed is acceptable only if it’s done once in a while. So, it’s OK to have it done as guys at LinkedIn have done it – you need to reauthorize each time your action may be visible to others (e.g. you change something in your profile, send a message to someone, etc.), but all the day-to-day activities are done based on your long-lasting session. It works, and works fine. But imagine everything you do is important and it’s crucial to be sure that no unauthorised person gains access to the data – in such a case reauthorisation per-task is an unacceptable overhead from user perspective.
Adding insult to injury, unless the application implements a real per-task authorisation, there is still some period of time, in which the session is established and active, that allows for session riding based attacks. It’s shorter, but it’s still there.
So, this trick doesn’t actually solve the problem, it only shortens the period when the attack may successfully take place or removes the possibility entirely in cases when a per-task authentication is required. But along with that it inevitably impairs the session mechanism or even disables it entirely.
Then, since it’s not the best for every application and since it more of lowers the risk then actually solves the problem, lets see how else we could deal with session riding attacks. It’s important to realise that the whole trouble we’re having comes from the fact that an attacker is able to easily figure out what the values that he passes to the application should be in order to invoke certain functions and achieve certain effect.
In our example it’s really easy to figure out that the id is a username and that usually is by no means a secret information. So, in order to solve the problem and make sure the code is not vulnerable to session riding we need to
3) make sure the arguments passed to the application are not predictable.
In fact, the harder they are to predict, the more secure the application is.
The first what tends to come to mind here is to obfuscate the data that serves the purpose of identifying the record that should be affected by the requested action, which may be the right way to go, but we need to remember that just hashing the sensitive identification data or mangling it in any way is not enough. Actually, to be honest, the only thing it does is give a false sense of security. First of all it’s a security by obscurity case, which is not the best approach especially when the super-secret way of protecting the information is to md5 it.
Purists may also be interested in the fact that it’s against the Kerckhoffs’ principle stating that in any cryptographic application only the key should be kept secret and any other part of the cryptosystem should be a public knowledge (generally because it’s fricking hard to keep this much of information secret, especially when there is a lot of people involved in creating and using it). This way or the other, it’s enough to say that it’s hard to do the mangling of the sensitive information right basing just on the algorithm secrecy. Which in other words means, if the solution is expected to serve the purpose and be secure, it should not be designed this way.
Instead it’s reasonable to introduce salt (being the secret) into the mangling process, at the same time making sure that the mangling function is a proper irreversible hash function. We need the salt in order to make sure that, knowing the hashing algorithm, an attacker is not able to generate the identification information and forge the request anyway. The irreversibility of the hash function, in turn, is necessary to keep the salt safe from the attacker’s eyes and again to prevent them from successfully forging the request.
So, in pseudo-code, instead of having:
...or
id = "user1"
...
...
id = sha1("user1")
...
we have:
...In this case, o2j3yud7gem3 being the salt.
id = sha1("o2j3yud7gem3" + "user1")
...
Provided the salt is long enough, we may be pretty sure an attacker won’t be able to crack the hash or look it up in rainbow tables and get the initial value of the salt out. In case you are one of those who constantly see someone follow them and like to bolt the door twice, assigning an individual, randomly selected, salt to each user, instead of using one salt throughout the whole application, should let you stop worrying about it being cracked out of the hash and enjoy your good night's sleep.
But, it’s still not the best solution and here is why. In order to figure out what object does the request apply to, the application needs to hash all the object ids together with the respective salt value (as the hash function is irreversible by design), which is a needlessly time and resources consuming exercise. Actually, if the number of objects is big enough it may take forever to have it done. Unless of course the hashes are being pre-computed and stored in a database ahead of time constituting sort of long and non-predictable ids. Which in turn leads us to:
The solution you’ve been waiting for since the beginning of all this
It’s best to:
4) assign each object a unique and randomly selected id long enough to render any brute-force or guessing attack unsuccessful.
The key point here is that the information which object is represented by a given id should be kept secret on the server side. This way an attacker won’t be able to successfully execute a forged request, because they won’t be able to figure out what is the id of the object that given user has permissions to interact with. The example URI from the beginning of this text would then look something like this:
http://site.com/change_password.php?
id=j38sg5wj9t8we87kwh5di6&password=hiya
The 38sg5wj9t8we87kwh5di6 being a randomly generated id that cannot be derived from the value of user1 in any other way then referring to the relationship table held in the database.
In this case also the reverse lookup of the object being given the id and having access to the database is efficient and straightforward, causing this approach to be probably the best in defending against session riding kind of attacks.
The only trouble with the long random id values associated to certain objects is that a person that snoops the values in any way may after all be able to forge and successfully run a request. But this is pretty easily dealt with, by changing out the id values once every so often – be it at every log-in, once a day, or so – and keeping the communication channel secured. It’s pretty important to keep in mind that too frequent regeneration of those id values may render using the application or, in case of per-request regeneration, two parallel instances of the application (say in two separate browser windows or tabs) impossible.
It’s worth noting that all of the ways of fighting the vulnerability more or less seriously impair static deep-linking to certain functionalities of the application, but, after all, that’s exactly what we are after, isn’t it?
Finally, it’s important to realise that there is couple of entirely wrong ideas as how to defend against XSRF-based attacks. What’s common amongst them is that none of them increases security in respect of session riding even a wee bit. Here is
What is by no means a solution
- to use POST instead of GET – of no help at all, because it is equally easy to create a self-submitting form hidden in an innocent-looking webpage and convince a victim to visit it, as to do the same with a maliciously forged URI;
- to rely on any kind of a “secret” cookie or a hidden form field with a predictable value in order to confirm legitimacy of a request – the cookies are sent by the browser regardless of the fact whether the request was intended by the user or not, and predictable fields by definition may be predicted, what perfectly defeats their purpose;
- to require additional confirmation of a sensitive action – unless the confirmation gets user to re-authenticate (e.g. re-enter their credentials), in a stateless protocol of HTTP, where for a context to be preserved necessary parameters need to be resent, it’s fairly easy to forge a request to the action just after the confirmation. So, if none of the techniques of ensuring the passed parameters are not predictable is incorporated, the application is just as vulnerable as without it.
Finally-finally, it’s crucial to keep in mind that an attack exploiting an XSRF vulnerability is just as powerful as is the vulnerable application itself. So, I guess the bottom line here is that it’s pretty much worth it to make sure your application is not vulnerable to this kind of attack.
Comments (7)
Interesting article! I tought of another solution being a variation of solution #1: any application's state modifying request should have referring location of site.com. What do you think Michał?
Posted by Marcin Olak | August 3, 2007 11:12 AM
Posted on August 3, 2007 11:12
Yes, it may be the answer, provided it's not possible for an outsider to publish a link on the site. And, as it may be the case when we're talking about a CMS, an information site, or such, it is definitely not that good with blogs, news forums and sites that allow users to voice their opinion and post links.
So, it's all about a logic behind the application — sometimes it's enough to specify a domain name as a legitimate referring location, and sometimes it's not.
Of course, all this is true unless something like this http://www.cgisecurity.com/lib/XmlHTTPRequest.shtml happens again. So, generally referrers are not the best approach here, as it strongly relies on browser security.
Posted by Michal Sobiegraj | August 3, 2007 1:59 PM
Posted on August 3, 2007 13:59
Often browsers not relaying correctly referrer. Why?
1) bug in browser
2) some kind of security reasons (e.g. from http://en.wikipedia.org/wiki/Referrer)
3) and many more
Referrer isn't a good point in security view.
Posted by Przemyslaw 'rezos' Skowron | August 3, 2007 10:58 PM
Posted on August 3, 2007 22:58
Thanks to your response I clearly see the flaw of this solution now - after all referrer is just a part of HTTP Request, isn't it? Hope to read more of such articles on your blog Michal. Keep on good blogging :)
Posted by Marcin Olak | August 4, 2007 7:41 PM
Posted on August 4, 2007 19:41
@Przemyslaw 'rezos' Skowron: Exactly my point one comment above.
@Marcin Olak: Yes, the part that is generated by victim’s browser, which may turn out to be vulnerable to referrer spoofing. Exactly as we've seen already (the link to the description is in a comment above). Thanks a lot!
Posted by Michal Sobiegraj | August 4, 2007 9:28 PM
Posted on August 4, 2007 21:28
In the solution 4 you propose to assign every object an randomly chosen, long enough id. And to change out the ids, for example every day. What do you think about another solution, which is generating for each request a new object id, which would be valid only for one action, and then destroyed. Additionaly, this id could be valid only 5 minutes after it was generated. So, if the user hasn't completed the action after 5 minutes, the key would became invalid.
Posted by Tomek Meka | October 19, 2007 10:41 PM
Posted on October 19, 2007 22:41
Well, as I said in the text, such a solution has a serious pain-in-the-ass factor. Meaning, OK, you can invalidate those IDs after five minutes, but it will effectively invalidate user’s session (if that’s what you’re after, cool, if not, you would probably like the expiration time to be a bit longer).
It’s a similar case with the per request ID invalidation. It might look like fun thing to do, unless you expect your users to use your application in two web browser windows/tabs simultaneously (the inherent implication of the per-request ID change is that after performing a request in one tab, all the links in the other one get invalidated and unusable).
So, in some cases, maybe such a rigorous URL invalidation policy would prove itself useful. But mostly I guess it would be too much of a pain for users. Something along the lines of security going only as far as it earns/saves you more money than it costs you (in regards of pissed off customers who are taking their money somewhere else too).
Posted by Michał Sobiegraj | October 22, 2007 3:15 PM
Posted on October 22, 2007 15:15