Hey,
I'm trying to implement CSRF tokens on a site of mine, but all the existing solutions have one of two problems;
1) The site runs on multiple servers (4 to be precise) so we can't use anything in _SESSION
2) The site gets over 400k hits a day on average so using a DB to store tokens would be too much of a performance hit.
The solution I've come up with at the moment is to generate three tokens, a unique 'hash' which uses a secret key known only to the server, the URL, base64 encoded and the request time to expire requests.
PHP:
<?php
//Server secret key
$CSRFKey = "some-secret-key";
//Generate the tokens
$RequestTime = time();
$Output["CSRF"] = sha1($_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT'].$_SERVER['REQUEST_URI'].$RequestTime.$CSRFKey);
$Output["CSRF_KEY"] = base64_encode($_SERVER['REQUEST_URI']);
$Output["CSRF_TIME"] = base64_encode($RequestTime);
//Post requests should have CSRF keys
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
//Generate a second token from the submitted data
$CSRF_URL = base64_decode($_POST['CSRF_KEY']);
$CSRF_TIME = base64_decode($_POST['CSRF_TIME']);
$CSRF = sha1($_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT'].$CSRF_URL.$CSRF_TIME.$CSRFKey);
//Compare the generated token to the submitted token
if ( $CSRF != $_POST['CSRF'] ) {
header('HTTP/1.0 500 Forbidden');
die("Request forbidden!");
}
//Check the expiry time of the request
//Assume the user will submit a form within 10 minutes but take longer than 10 seconds
if ( $CSRF_TIME < $RequestTime+10 || $CSRF_TIME > $RequestTime+60*10 ) {
header('HTTP/1.0 500 Forbidden');
die("Request expired!");
}
}
HTML:
<head>
<!--CSRF-->
<meta name="CSRF" id="CSRF" content="{{CSRF}}" />
<meta name="CSRF_KEY" id="CSRF_KEY" content="{{CSRF_KEY}}" />
<meta name="CSRF_TIME" id="CSRF_TIME" content="{{CSRF_TIME}}" />
</head>
Javascript (With jQuery):
var CSRF_KEY = $('#CSRF_KEY').attr('content');
var CSRF_TIME = $('#CSRF_TIME').attr('content');
var CSRF = $('#CSRF').attr('content');
$('form').each(function(i,elm){
$(elm).append('<input type="hidden" name="CSRF" value="'+CSRF+'" />');
$(elm).append('<input type="hidden" name="CSRF_KEY" value="'+CSRF_KEY+'" />');
$(elm).append('<input type="hidden" name="CSRF_TIME" value="'+CSRF_TIME+'" />');
});
(I haven't tested this code but dry running it seemed to work with no errors).
As you can see I already have a rough idea how to do it but I just wanted to check I wasn't missing anything blindingly obvious.
-Sam