# CSRF Protection in PHP

I'm currently working on CSRF prevention and I would love to hear your feedback for my code. I assume that there are no XSS vulnerabilities in order for the CSRF protection to work. Furthermore, I assume that all state-changing/critical actions are done via post-requests. One token is generated per session.

Here is the code:

<?php

/*
* CSRF prevention module
*/

class CSRF {

private static $key = "token"; public static function init() { if (session_status() === PHP_SESSION_NONE) die("[CSRF::init] PHP session engine is not started"); if (!self::has_tok()) self::set_tok(self::gen_tok()); } public static function inject_token() { if (!self::has_tok()) die("[CSRF::secure_form] There is no token"); echo "<input type='hidden' name='" . self::$key
. "' value='" . self::tok() . "'>\n";
}

public static function valid_post() {
$src = parse_url(self::source_origin(), PHP_URL_HOST); return$src === $_SERVER['SERVER_NAME'] && isset($_POST[self::$key]) && self::match_tok($_POST[self::$key]); } private static function gen_tok() { return base64_encode(random_bytes(64)); } private static function has_tok() { return isset($_SESSION[self::$key]); } private static function set_tok($tok) {
$_SESSION[self::$key] = $tok; } private static function match_tok($tok) {
return self::has_tok() && $tok === self::tok(); } private static function tok() { return$_SESSION[self::$key]; } private static function source_origin() { if (isset($_SERVER['HTTP_ORIGIN']))
return $_SERVER['HTTP_ORIGIN']; if (isset($_SERVER['HTTP_REFERER']))
return \$_SERVER['HTTP_REFERER'];
return "";
}
}

?>


Usage example:

 <form action="foo.php" method="post">
<?php CSRF::inject_token(); ?>
...
</form>


And then in foo.php:

<?php
...
if (!CSRF::valid_post())
die("CSRF");
...
?>


Any suggestions for improvements? Did I miss anything? I know adding the tokens to forms could be automated with javascript (there is an OWASP CSRF Protection module), but I prefer to have a site that can run without javascript. I don't plan on having too many forms, so I think I can manage to not forget securing one.

I recently added checking the origin/referer after reading OWASP CSRF Prevention Cheat Sheet, but I'm not sure I did it properly.

EDIT: I just realized from the code example at OWASP that

(1) I should compare protocol and port, too, not just the host.

• What I do is regenerate a CSRF token once it is used. Not once per session, Reason - It can be extracted by the user from inspect element and can be misused. – Rajendran Nadar Nov 4 '17 at 8:42

This seems quite reasonable as a baseline for CSRF protection. Critically, you must ensure that you do not set the Access-Control-Allow-Origin (CORS) header to a wildcard, otherwise an attacker can just perform an Ajax request to your site and gain access to the CSRF token.