Created
July 20, 2017 08:44
-
-
Save nilesolutions/9916a2d1e17c74a2e9b4c131fe6464a3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
For security code, please don't generate your tokens this way: `$token = md5(uniqid(rand(), TRUE));` | |
* [`rand()` is predictable](https://jazzy.id.au/2010/09/20/cracking_random_number_generators_part_1.html) | |
* [`uniqid()` only adds up to 29 bits of entropy](http://securitymaverick.com/php-uniqid-entropy-analysis-and-potentially-vulnerable-apps) | |
* `md5()` doesn't add entropy, it just mixes it deterministically | |
Try this out: | |
## Generating a CSRF Token | |
### PHP 7 | |
session_start(); | |
if (empty($_SESSION['token'])) { | |
$_SESSION['token'] = bin2hex(random_bytes(32)); | |
} | |
$token = $_SESSION['token']; | |
Sidenote: One of [my employer's open source projects](https://paragonie.com/projects) is an initiative to backport `random_bytes()` and `random_int()` into PHP 5 projects. It's MIT licensed and available on Github and Composer as [paragonie/random_compat](https://github.com/paragonie/random_compat). | |
### PHP 5.3+ (or with ext-mcrypt) | |
session_start(); | |
if (empty($_SESSION['token'])) { | |
if (function_exists('mcrypt_create_iv')) { | |
$_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); | |
} else { | |
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32)); | |
} | |
} | |
$token = $_SESSION['token']; | |
## Verifying the CSRF Token | |
Don't just use `==` or even `===`, use [`hash_equals()`](https://secure.php.net/hash_equals) (PHP 5.6+ only, but available to earlier versions with the [hash-compat](https://github.com/indigophp/hash-compat) library). | |
if (!empty($_POST['token'])) { | |
if (hash_equals($_SESSION['token'], $_POST['token'])) { | |
// Proceed to process the form data | |
} else { | |
// Log this as a warning and keep an eye on these attempts | |
} | |
} | |
-------- | |
## Going Further with Per-Form Tokens | |
You can further restrict tokens to only be available for a particular form by using [`hash_hmac()`](https://secure.php.net/hash_hmac). HMAC is a particular keyed hash function that is safe to use, even with weaker hash functions (e.g. MD5). However, I recommend using the SHA-2 family of hash functions instead. | |
First, generate a second token for use as an HMAC key, then use logic like this to render it: | |
<input type="hidden" name="token" value="<?php | |
echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']); | |
?>" /> | |
And then using a congruent operation when verifying the token: | |
$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']); | |
if (hash_equals($calc, $_POST['token'])) { | |
// Continue... | |
} | |
The tokens generated for one form cannot be reused in another context without knowing `$_SESSION['second_token']`. **It is important that you use a separate token as an HMAC key than the one you just drop on the page.** | |
### Bonus: Hybrid Approach + Twig Integration | |
Anyone who uses the [Twig templating engine](http://twig.sensiolabs.org) can benefit from a simplified dual strategy by adding this filter to their Twig environment: | |
$twigEnv->addFunction( | |
new \Twig_SimpleFunction( | |
'form_token', | |
function($lock_to = null) { | |
if (empty($_SESSION['token'])) { | |
$_SESSION['token'] = bin2hex(random_bytes(32)); | |
} | |
if (empty($_SESSION['token2'])) { | |
$_SESSION['token2'] = random_bytes(32); | |
} | |
if (empty($lock_to)) { | |
return $_SESSION['token']; | |
} | |
return hash_hmac('sha256', $lock_to, $_SESSION['token2']); | |
} | |
) | |
); | |
With this Twig function, you can use both the general purpose tokens like so: | |
<input type="hidden" name="token" value="{{ form_token() }}" /> | |
Or the locked down variant: | |
<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" /> | |
Twig is only concerned with template rendering; you still must validate the tokens properly. In my opinion, the Twig strategy offers greater flexibility and simplicity, while maintaining the possibility for maximum security. | |
------- | |
## Single-Use CSRF Tokens | |
If you have a security requirement that each CSRF token is allowed to be usable exactly once, the simplest strategy regenerate it after each successful validation. However, doing so will invalidate every previous token which doesn't mix well with people who browse multiple tabs at once. | |
Paragon Initiative Enterprises maintains an [Anti-CSRF library](https://github.com/paragonie/anti-csrf) for these corner cases. It works with one-use per-form tokens, exclusively. When enough tokens are stored in the session data (default configuration: 65535), it will cycle out the oldest unredeemed tokens first. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment