CSRF is a PHP library for preventing [Cross-Site Request Forgery] (https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29) attacks. A CSRF attack takes advantage of authenticated users by sending them to a malicious website that sends carefully crafted requests to the targeted website in order to modify content on that website. The attack uses the authenticated user's browser to send the request to bypass any authentication. This library prevents these attacks by requiring a CSRF token in each POST, PUT and DELETE request. These tokens are not known by the attacker, which prevents them from sending malicious requests.
This library supports storing the CSRF tokens using either cookies or sessions. The token can also be submitted using either a hidden form field in POST requests or using a HTTP header, which makes it easier to pass the token in ajax requests.
In order to provide additional security against different forms of attacks against the CSRF tokens, this library uses constant time string comparisons to prevent timing attacks and generates random encrypted tokens in each request to prevent BREACH attacks. On top of that, all tokens are generated using a secure random byte generator.
The API documentation, which can be generated using Apigen, can be read online at: http://kit.riimu.net/api/csrf/
In order to use this library, the following requirements must be met:
- PHP version 5.4
- Kit-SecureRandom library is required
This library can be installed by using Composer. In
order to do this, you must download the latest Composer version and run the
require
command to add this library as a dependency to your project. The
easiest way to complete these two tasks is to run the following two commands
in your terminal:
php -r "readfile('https://getcomposer.org/installer');" | php
php composer.phar require "riimu/kit-csrf:2.*"
If you already have Composer installed on your system and you know how to use
it, you can also install this library by adding it as a dependency to your
composer.json
file and running the composer install
command. Here is an
example of what your composer.json
file could look like:
{
"require": {
"riimu/kit-csrf": "2.*"
}
}
After installing this library via Composer, you can load the library by
including the vendor/autoload.php
file that was generated by Composer during
the installation.
You can also install this library manually without using Composer. In order to
do this, you must download the latest release
and extract the src
folder from the archive to your project folder. To load
the library, you can simply include the src/autoload.php
file that was
provided in the archive.
Note that if you install this library manually, you must also install the dependencies by yourself. Installing the library via Composer also installs the dependencies for you.
The idea of this library is to make security as convenient as possible. You only
really need two methods provided by the CSRFHandler
class. The method
validateRequest()
should be called at the very beginning of each request. This
method will only validate POST, PUT and DELETE requests so you can safely call
it on every request. The method getToken()
can be used to retrieve the token
that should be included in each submitted form using a hidden field named
csrf_token
.
If the submitted token does not match against the secret token stored in the
cookie or session, the validateRequest()
method will send a HTTP 400 (bad
request) header and kill the script execution. This should not affect the normal
usage of your website, but it will prevent any CSRF attack attempts against
your website.
As an example, here is a simple web page that has one form that can be submitted:
<?php
require 'vendor/autoload.php';
$csrf = new \Riimu\Kit\CSRF\CSRFHandler();
$csrf->validateRequest();
$token = $csrf->getToken();
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Simple Form</title>
</head>
<body>
<?php
if (!empty($_POST['my_name'])) {
printf(" <p>Hello <strong>%s</strong>!</p>" . PHP_EOL, htmlspecialchars($_POST['my_name'], ENT_QUOTES | ENT_HTML5, 'UTF-8'));
}
?>
<form method="post"><div>
<input type="hidden" name="csrf_token" value="<?=htmlspecialchars($token, ENT_QUOTES | ENT_HTML5, 'UTF-8')?>" />
What is your name?
<input type="text" name="my_name" />
<input type="submit" />
</div></form>
</body>
</html>
By default, the library will save the secret token to a cookie. If you prefer
to save the token to a session instead, you can initialize the CSRFHandler
by
setting the constructor parameter to false
. For example:
<?php
session_start();
require 'vendor/autoload.php';
$csrf = new \Riimu\Kit\CSRF\CSRFHandler(false);
If you wish to have more control over what happens when the request sends an
invalid csrf token, you can set the parameter passed to validateRequest()
to
true
. This will cause the method to throw an InvalidCSRFTokenException
instead of killing the script. For example:
<?php
require 'vendor/autoload.php';
$csrf = new \Riimu\Kit\CSRF\CSRFHandler();
try {
$csrf->validateRequest(true);
} catch (\Riimu\Kit\CSRF\InvalidCSRFTokenException $ex) {
header('HTTP/1.0 400 Bad Request');
exit('Bad CSRF Token!');
}
Note that if you are building a REST api to your website or you are using ajax requests to send POST, PUT or DELETE requests, you may also provide the csrf token using a header.
To provide the token using a header, simply include a header named X-CSRF-Token
which contains the same value you would include in the csrf_token
form field.
A nonce is a token that can be used only once. Turning CSRF tokens into nonces provides protection against Replay Attacks. It is important to note, however, that the best defense against such attacks is using a secure HTTPS connection. However, if you do not have the luxury of an encrypted connection at your disposal, it may be possible to [use nonces] (http://blog.ircmaxell.com/2013/02/preventing-csrf-attacks.html) to prevent these attacks.
This library provides a way to implement nonces by using the NonceValidator
class. This class works exactly the same as CSRFHandler
except that it accepts
each token generated by getToken()
only once. Even if the attacker can spy
on the connection, they cannot resend the http request because the token only
works once.
You can use the NonceValidator
the same way as you would use the CSRFHandler
,
for example:
<?php
require 'vendor/autoload.php';
session_start();
$csrf = new \Riimu\Kit\CSRF\NonceValidator();
$csrf->validateRequest();
$token = $csrf->getToken();
Note that NonceValidator
always uses sessions to store the CSRF token. In
addition to that, it will also store which tokens have been used up and cannot
be used again. If you have a website that relies on a large number of form
submissions, this array of invalidated tokens can grow quite large. To clear this
array, simply regenerate the token using regenerateToken()
. For example:
<?php
require 'vendor/autoload.php';
session_start();
$csrf = new \Riimu\Kit\CSRF\NonceValidator();
$csrf->validateRequest();
if ($csrf->getNonceCount() > 100) {
$csrf->regenerateToken();
}
$token = $csrf->getToken();
If you wish to have more control over the token validation, this library
provides several methods that allows you to manually manage several aspects of
the library. For your convenience, the CSRFHandler
provides the following
methods:
-
isValidatedRequest()
tells if the current request is a POST, PUT or DELETE request which should be validated. -
validateRequest($throw = false)
validates the request and kills the script or throws an exception if the token is invalid. The token is only validated on POST, PUT and DELETE requests. -
validateRequestToken()
validates the token sent in the request. True is returned if the token exists and it matches against the secret token. -
validateToken($token)
can be used to validate tokens manually. The token passed to the method should be the one that has been returned bygetToken()
-
getToken()
returns a valid base64 encoded token. -
regenerateToken()
regenerates the secret CSRF token and invalidates all the tokens returned previously bygetToken()
-
getTrueToken()
returns the stored secret CSRF token that is used to validate the tokens submitted by the user. -
getRequestToken()
returns the token sent in the request.
Even this library does not prevent CSRF attacks if you fail to utilize the tokens correctly. It is very important that each request is properly validated and that the token is sent with each submitted form. However, there are still couple of pitfalls that you should be aware of.
If you're not using nonces, in some rare cases it may also be possible to use
[Session Fixation] (https://www.owasp.org/index.php/Session_fixation) attack to
determine the CSRF token used by the authenticated user. Even if the session ID
is regenerated upon login, the attacker may still take advantage of the known
CSRF token. To prevent this, it is simply advisable to regenerate the token
upon authentication by calling regenerateToken()
.
In order to create a website that is impervious to CSRF attacks, you must also remember that only POST, PUT and DELETE requests should change the state of the website. A CSRF token should be never be supplied in a GET parameter, because this can be leaked using various different attacks. Thus, GET requests should never affect the state. For example, allowing users to be deleted using a simple GET request would make your website vulnerable to CSRF attacks.
If you truly want to create a secure site, however, you must also only use encrypted connections, i.e. you must use HTTPS. This is the only effective measure against Man-in-the-middle attacks, but it also helps in preventing replay attacks.
Finally, remember that CSRF tokens only protect you from external requests. They offer no protection against Cross-site Scripting attacks. If the attacker is capable of running javascript on your website, the CSRF tokens offer no additional protection. Using a XSS attack, the attacker is always capable of finding out the CSRF token. The security of your website only as strong as the weakest link.
All the tokens generated by this library are random 32 byte strings. These
strings have been generated by using the SecureRandom library in order to ensure
that they have been generated using a secure random source. However, the tokens
returned by getToken()
are more than double that in length. This is because
they are base64 encoded strings that also contain a hashed version of the token
using the secret token as a salt.
In order to prevent BREACH attacks, each token returned by getToken()
is
different, because a static token can be used to break the encryption used by a
HTTPS connection. In order to achieve this, the returned token actually consists
of a random generated token and an encrypted version of that token that has been
encrypted using HMAC-SHA256 using the secret token as the key. (which also makes
it infeasible to reverse the operation to find out the secret token). This
allows each token to be different, but still valid until regenerateToken()
is
called. Thus, the actual length of the returned decoded string is 64 bytes.
Note that a new random token is generated every time getToken()
is called.
Thus, each string returned by that method is different. If you have a large
number of forms on your web page, it may be more efficient to use SingleToken
class, which loads the token only once and it can be casted to a string.
This library is copyright 2014 - 2015 to Riikka Kalliomäki.
See LICENSE for license and copying information.
Implementation of this library is based on ideas from Go library nosurf by Justinas Stankevicius