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.