Cross-Site Request Forgeries Solution
Recent events have once again highlighted further Cross-site vulnerabilities. These problems are a fundamental problem with how the web exists. Although there are many solutions to protect against them, the only way to remove them altogether would be to alter the very essence of the web itself.
The recent big one to hit the news is Cross-Site Request Forgeries. The general summary is, if the user has logged into a web page (it doesn’t need to be open, just that they have preexisting cookies allowing them to visit the page without logging in), another malicious site can execute things on this page under the users authentication.
Below, I outline the problem in more detail, and outline a solution that does not require PHP session variables
Explanation
If you have recently logged into your ADSL router, a malicious page can disconnect your connection, or something similar, with carefully constructed img tags, scripts, flash applets, or javascript code. It was recently believed that if you did Referer verification, you could avoid these vulnerabilities in your website applications. (Referer verification tries to ensure that requests that come to your web app haven’t been referred by a 3rd party host).
The issue with Referer verification is that there exists bugs in Flash player, and other products that allow requests to be created with No or a pre-set Referer header.
So whats the solution?
First, realize that there is little that can be done if the browser is vulnerable enough. Even a captcha on every single action wouldn’t be enough, as the user could be fooled into filling out the Captcha by the malicious website, thinking they are filling it out for other reasons. If you’d like a working demonstration of this, then feel free to email me.
If we rule out the extreme case of a users browser that is highly vulnerable, then we are left with a subset of pre-existing solutions.
Some solutions don’t protect against all attacks (Referer header verification doesn’t help if the user has a “helpful” firewall which strips out the “Referer” header (to protect their privacy, hah!). The current recommended solution uses PHP Session variables to store a token.
My problem with this solution is I hate PHP session variables. They are a hack, they don’t work all the time, and aren’t secure against local shared-host attacks. So I have developed a solution that is based on the spirit of the PHP session solution, but does not use Session variables.
$validtime = 5 * 60; //5 minutes
$secretkey = "secret";function CreateAuthToken()
{
global $secretkey,$validtime;
$ts = time();
$ts = $ts - ($ts % $validtime);
return md5($_SERVER['REMOTE_ADDR'] . $username . $ts . $secretkey);
}
function ValidateAuthToken($token)
{
global $secretkey,$validtime;
$ts = time();
$ts1 = $ts - ($ts % $validtime);
$ts2 = $ts1 - $validtime;
$token1 = md5($_SERVER['REMOTE_ADDR'] . $username . $ts1 . $secretkey);
$token2 = md5($_SERVER['REMOTE_ADDR'] . $username . $ts2 . $secretkey);
if ($token == $token1 || $token == $token2) return TRUE;
return FALSE;
}
How does this work?
Well, first lets look at CreateAuthToken(). This function creates a md5sum that is the combination of the users IP address, the users login name, a secret token, and a timestamp that is rounded to the previous 5 minutes (eg if it were in human form, it might read “8:10″ when the time is 8:14 or “9:35″ when its 9:31. This timestamp is the authenication window.
Each POST form passes a field that has the token in it.
<form method="POST">
<input type="hidden" name="token" value="<?= CreateAuthToken() ?>" />
.....
</form>
When the server receives the request, before it does anything else, it calls ValidateAuthToken(), supplying the user supplied token. If the token was generated in the last 5-10 minutes, it accepts it, otherwise it fails (presumably generating a suitable error message).
We need to generate the current token as well as the previous token, because if the user opened the form page at 3:14:58, and hit submit at 3:15:04, then their token would have expired.
Is this method secure?
Lets look at how we could attack it. First, if we have the secret, then its vulnerable, but the secret is hidden by the hashing function (md5 is vulnerable to reversing the hash, but not that much, you can use SHA-256 if you wish).
If we don’t have the secret, we can’t generate a valid token, but the website generates them for us. If we could convince the user our username and IP address is the same as the target, we could get past it. Assuming your authentication methods are secure enough, then this isn’t a viable route.
The 3rd (and successful) method would be to get the user to generate the token for us, and give it to us, so we can use it in crafting an appropriate attack. The way to do this is to use vulnerabilities in browser to open a web page and read the results. Although this is possible with unpatched browsers, it falls outside the scope of responsibility of this solution. If the user won’t protect themselves, we can’t protect them.
If we wanted to protect the user from an unpatched browser, we would also need to protect the user from a virus on their PC. The only way to do this would be a Vista style “Are you sure” prompt for each and every request. Of course, this request would need to be generated by the Operating System, since a virus or malicious web-page could easily bypass it if the website itself generated it.
Captcha’s offer a glimmer of hope. If you forced the user to pass a Captcha test every time they wanted to do something, it might work. But then again, since a malicious website can grab the captcha image too (assuming a vulnerable browser), it could present the captcha to the user and say “for free porn, type this in” (or similar such messages).
So no, in the end, we can’t protect against a vulnerable browser. So if we rule out this as a requirement, the above code-snippets (and other solutions) protect against Cross-Site Request Forgeries as much as possible.
