CSRF Token

Thor

Honorary Master
Joined
Jun 5, 2014
Messages
44,236
I need some peer review if this is good enough for my login and sign up form ( Specifically referring to the CSRF Token this is simply partial code for demonstration) Got inspired by laravel, but I do not need that whole framwork for small stuff.

token_functions.php

PHP:
<?php
// Generate a token for use with CSRF protection.
// Does not store the token.
function csrf_token() {
	return md5(uniqid(rand(), TRUE));
}

// Generate and store CSRF token in user session.
// Requires session to have been started already.
function create_csrf_token() {
	$token = csrf_token();
    $_SESSION['csrf_token'] = $token;
 	$_SESSION['csrf_token_time'] = time();
	return $token;
}

// Destroys a token by removing it from the session.
function destroy_csrf_token() {
    $_SESSION['csrf_token'] = null;
 	$_SESSION['csrf_token_time'] = null;
	return true;
}

// Return an HTML tag including the CSRF token 
// for use in a form.
// Usage: echo csrf_token_tag();
function csrf_token_tag() {
	$token = create_csrf_token();
	return "<input type=\"hidden\" name=\"csrf_token\" value=\"".$token."\">";
}

// Returns true if user-submitted POST token is
// identical to the previously stored SESSION token.
// Returns false otherwise.
function csrf_token_is_valid() {
	if(isset($_POST['csrf_token'])) {
		$user_token = $_POST['csrf_token'];
		$stored_token = $_SESSION['csrf_token'];
		return $user_token === $stored_token;
	} else {
		return false;
	}
}

// You can simply check the token validity and 
// handle the failure yourself, or you can use 
// this "stop-everything-on-failure" function. 
function die_on_csrf_token_failure() {
	if(!csrf_token_is_valid()) {
		die("CSRF token validation failed.");
	}
}

// Optional check to see if token is also recent
function csrf_token_is_recent() {
	$max_elapsed = 60 * 60 * 24; // 1 day
	if(isset($_SESSION['csrf_token_time'])) {
		$stored_time = $_SESSION['csrf_token_time'];
		return ($stored_time + $max_elapsed) >= time();
	} else {
		// Remove expired token
		destroy_csrf_token();
		return false;
	}
}

?>

token_request_type.php

PHP:
<?php
// GET requests should not make changes
// Only POST requests should make changes

function request_is_get() {
	return $_SERVER['REQUEST_METHOD'] === 'GET';
}

function request_is_post() {
	return $_SERVER['REQUEST_METHOD'] === 'POST';
}

// Usage:
// if(request_is_post()) {
//   ... process form, update database, etc.
// } else {
//   ... do something safe, redirect, error page, etc.
// }
?>

login.php

PHP:
<?php
session_start();
require_once 'csrf_request_type_functions.php';
require_once 'csrf_token_functions.php';

if(request_is_post()) {
	if(csrf_token_is_valid()) {
		$message = "It works, good!";
		if(csrf_token_is_recent()) {
			$message .= " Token is recent";
		} else {
			$message .= " Token is NOT recent";
		}
	} else {
		$message = "The CSRF Token is mission or mismatched.";
	}
} else {
	// form not submitted or was GET request
	$message = "Sneaky Putin, Please login.";
}

?>
<html>
	<head>
		<title>Login Screen</title>
	</head>
	<body>
		<?php echo $message; ?><br />
		<form action="" method="post">
			<?php echo csrf_token_tag(); ?>
			Username: <input type="text" name="username" /><br />
			Password: <input type="password" name="password"><br />
			<input type="submit" value="Submit" />
		</form>
	</body>
</html>
 
Last edited:

neo_

Well-Known Member
Joined
May 20, 2015
Messages
453
Use mtrand() instead of rand(). Although, you should use something with more entropy. OpenSSL or Linux /dev/urandom or PHPs hash_hmac (sha256). Also, don't die() without sending an HTTP response code. That just jumped out at me - I think 401 is the correct one, but not 100% sure.

On your note about Laravel: Perhaps you project would be more suited to a smaller framework like Lumen, Slim, or Silex?
 

Thor

Honorary Master
Joined
Jun 5, 2014
Messages
44,236
Use mtrand() instead of rand(). Although, you should use something with more entropy. OpenSSL or Linux /dev/urandom or PHPs hash_hmac (sha256). Also, don't die() without sending an HTTP response code. That just jumped out at me - I think 401 is the correct one, but not 100% sure.

I will definitly go read up I was simply not aware, as for straight die(), yea agreed that I will change.

On your note about Laravel: Perhaps you project would be more suited to a smaller framework like Lumen, Slim, or Silex?

Even that, to me is too much for this in particular - Scenario:

- Home page
- About page
- Contact Form

--
- protected Admin Dash, so the guy can read messages send via the contact form assuming he is not at his mails for some reason.

for simple stuff like that all I want is the CSRF token to make sure the submission is from my form and then as a added bonus I add rechapcha on every form now. I had no idea it's so damn easy in PHP.
 

neo_

Well-Known Member
Joined
May 20, 2015
Messages
453
Yeah it becomes easy when you understand the workflow. I would still recommend a micro-framework for this. Whilst it may seem that such frameworks are overkill, they aren't really, especially when you're only using a limited subset of the functionality. Tiny sites I've built in the past are all powered by Silex (thin with only the packages I need). I did use Lumen for one of them, but migrated it to Silex as Lumen became more of an API framework of sorts, not truly intended for sites. The other thing to consider is that if the client ever wants to add things, you might want to have the ability to add those things with ease.
 

Thor

Honorary Master
Joined
Jun 5, 2014
Messages
44,236
Yeah it becomes easy when you understand the workflow. I would still recommend a micro-framework for this. Whilst it may seem that such frameworks are overkill, they aren't really, especially when you're only using a limited subset of the functionality. Tiny sites I've built in the past are all powered by Silex (thin with only the packages I need). I did use Lumen for one of them, but migrated it to Silex as Lumen became more of an API framework of sorts, not truly intended for sites. The other thing to consider is that if the client ever wants to add things, you might want to have the ability to add those things with ease.

You are definitely right, I do like silex I have used it before, well I have tried it before, my problem is this is all self study i do not have mentors and people looking over my shoulder saying hey, do not do that, rather do this and here is why. ( hence why this section is flooded with my stuff as I trust the advice I get here will have more weight than that on a random blog since everyone is a PHP expert if you google for advise so I limit myself now to only official documentation and here is the issue sometimes I do not understand what I am reading and the examples are either not there or not easy enough for me, thus I end up asking for clarity on mybroadband. - That is why I went with laravel due to laracasts gorgeous little resource worth all the money. The problem thou is that although I can use laravel I still feel I need to learn more about the concepts ie the stuff you don't get paid to do. That is what makes frameworks nice you can focus on what brings in the money and then the framework takes care of the lower level stuff in my case, I want to understand all of it.
 

Gnome

Executive Member
Joined
Sep 19, 2005
Messages
7,208
IMHO, I would use this guys approach:

http://stackoverflow.com/questions/1805838/csrf-token-generation
base64_encode(openssl_random_pseudo_bytes(16))

Hash codes are typically used for data which is not random and you want to encode it one way for verification later.
You don't actually care about the underlying data you send, you just want it to be unique and unguessable (meaning someone couldn't reconstruct it reliable).

I certainly wouldn't use MD5
 

Thor

Honorary Master
Joined
Jun 5, 2014
Messages
44,236
IMHO, I would use this guys approach:

http://stackoverflow.com/questions/1805838/csrf-token-generation
base64_encode(openssl_random_pseudo_bytes(16))

Hash codes are typically used for data which is not random and you want to encode it one way for verification later.
You don't actually care about the underlying data you send, you just want it to be unique and unguessable (meaning someone couldn't reconstruct it reliable).

I certainly wouldn't use MD5

I won't.

as for md5, yea that will be changed to bcrypt.
 
Top