Question

If I want to protect my site and users from Cross Site Forgery (CSRF) attacks, I can generate a unique token $token = md5( time() * rand ); on every page that has a form. The token is is submitted in a hidden input field echo '<input type="hidden" name="token" value="'.$token.'">'; and at the same time stored in a session variable $_SESSION['token'] = $token;.

I will check if on any submitted form if($_POST['token'] == $_SESSION['token']) and proceed accordingly.

However some users may multitask. Which is something that I am actually doing right now, while I am posting this.

While composing my post I open different windows / tabs to possibly research information or look at some other questions on stack overflow. Stack overflow lets me submit the form with no problems.

But if I were to do that on my site doing this - meaning browse other pages while still composing a post/form - my $token would be regenerated each time I pull up a different page from my website. Making the hidden input token on the form I am working on and eventually want to submit incorrect, because it wont match the $_SESSION['token'] variable anymore, which has been regenerated when I visited a different page...

Any good ideas how to prevent this issue, or any better solutions to stop CSRF in the first place?

I want to allow my users to multi task AND want to be protected against CSRF...

Was it helpful?

Solution

I've had the same problem with what you state because of single CSRF and it gets replaced unless they submit the latest page, but if you use a array w/session it should solve your problem(s). Also you might want to include a captcha, I'd recommend Google's Recaptcha.

session_start();
function createToken(){
    $token = sha1(uniqid(mt_rand(), true));
    $_SESSION['Tokens']['Token'][] = $token;
    $_SESSION['Tokens']['Time'][] = time() + (10 * 60); #10 min limit
    #you can omit/change this if you want to not limit or extend time limit
    return $token;
}

function checkToken($token){
    clearTokens();
    foreach($_SESSION['Tokens']['Token'] as $key => $value){
        if($value === $token){
            return true;
        }
    }
    return false;
}

function clearTokens(){
    foreach($_SESSION['Tokens']['Time'] as $key => $value){
        if($value <= time()){
            unset($_SESSION['Tokens']['Token'][$key], $_SESSION['Tokens']['Time'][$key]);
            #remove last parameter if you aren't using token time limit
        }
    }
}

your HTML:

<input type="hidden" name="token" value="<?php createToken(); ?>">

PHP Token Checker

if(isset($_POST['token']) && checkToken($_POST['token'])){
    #valid token
}else{
    #create error message saying that they tried to repost data or session token expired
}

OTHER TIPS

The browser should keep the correct session id even between tabs and windows the session id should be the same. (dangerous assumption should be tested cross browser to be sure)

generate more tokens that should be valid based on session id.

so you could check something like this.

$tokenCorrect = false;

foreach($_SESSION['tokens'] as $token) {
  if ($token !== $_POST['token'])
     continue;
  $tokenCorrect = true;
}

if ($tokenCorrect == false) {
   die(); // 
   // Maybe log to database ?? but watch if possible Denial of Service because somebody can write your disk/ shared diskspace full with only making fast requests with a invalid CSRF token
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top