Advanced CSRF Usage in PHP

This expands on my post, Securing against Cross Site Request Forgery (CSRF) exploits in PHP. In the previous post we discussed the basics of how to protect against CSRF exploits. I recommend reading that if you haven’t yet. Now let’s cover a more advanced use case.

Multiple CSRF Tokens

The previous solution only covered basic, one time use protection but what if you need to allow users to use multiple tabs or protect AJAX calls in addition to regular ol’ forms. You don’t want a user’s form token to expire simply because an AJAX call fired off in the background.

The solution is to support multiple CSRF tokens at once.

Here was our old code to generate a token

1
2
3
4
5
6
7
8
9
10
11
12
	//assuming the rest of the form class here

	static function generateCsrf() {

	  $token = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);

	  Session::flash('csrfToken', $token);


	  return $token;
	}
	


and here is our new function



1
2
3
4
5
6
7
8
9
10
11
//assuming the rest of the form class here

	static function generateCsrf() {

	  $token = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);

	  $_SESSION['csrfTokens'][$token] = time();

	  return $token;
	}
	


What this does is instead of storing the token as the value we are storing an array of tokens and then their corresponding creation timestamps. The previous example used the Laravel Session class but in this example I’m using standard PHP session handling since I don’t want this to be confusing.



Now the validation of the CSRF tokens looks like this



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Route::post('/signup', function(){

	  //this would probably be abstracted away into
	  //a route filter or your form validation

	  //here we are setting our expiration timestamp
	  //to 1 day ago
	  $expiration = time() - 86400; //86400 is the number of seconds in 24 hours

	  if (
	  	  isset($_SESSION['csrfTokens'][$_POST['token']]) &&
	  	  $_SESSION['csrfTokens'][$_POST['token']] >=  $expiration
	  ) {

		//process the form

	  }


	  //like earlier, you should add a
	  //legit error message here
	  die('Invalid Form Data');

	});
	

This new function not only checks that the token exists but also checks that it hasn’t expired.




Garbage Collection

You really should use a Session class to wrap these calls to the $_SESSION data and then write a garage collection routine to clear expired tokens. Here is an example



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Session
{
		function validateCSRFToken($token) {

			$this->garbageCollectCsrfTokens();

			//if it is a validate token clear it
			//and then return true
			if (isset($_SESSION['csrfTokens'][$_POST['token']]) === TRUE) {
				unset($_SESSION['csrfTokens'][$_POST['token']]);
				return TRUE;
			}

			return FALSE;

		}

		function garbageCollectCsrfTokens() {

			//here we are setting our expiration timestamp
			//to 1 day ago
			//this has been hardcoded here for simplicity
			$expiration = time() - 86400; //86400 is the # of seconds in 24 hours

			foreach ($_SESSION['csrfTokens'] as $k => $time) {

				if ($time < $expiration) {
					//unset since it's expired
					unset($_SESSION['csrfTokens'][$k]);
				}

			}

		}
	}
	


Everytime the token is evaluated a garabage collection routine will be ran against all the tokens. The route logic to check the tokens has now been simplified to this



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Route::post('/signup', function(Session $session){

	  //this would probably be abstracted away into
	  //a route filter or your form validation

	  if ($session->validateCSRFToken($_POST['token']) === TRUE) {

		//process the form

	  }


	  //like earlier, you should add a
	  //legit error message here
	  die('Invalid Form Data');

	});
	



You will now be able to use multiple CSRF tokens at once in the same user session. Plus have the added protection of expiring tokens.




Want to Learn More about PHP Security?

This builds upon topics discussed in my book, Building Secure PHP Apps. If you want to learn a ton more, some might even call it a metric fuck-ton, about writing secure PHP code then stop what you're doing and
Buy the Book


Get In Touch.

If you'd like to get in touch, you can reach me at ben@benedmunds.com.


Find me on ...