Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Riimu committed Feb 4, 2015
1 parent 29326ac commit 2f458f9
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 14 deletions.
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog #

## v2.2.0 (2015-02-04) ##

* Token length is now stored in a constant CSRFHandler::TOKEN_LENGTH instead
of a protected member, as it should have been from the start.
* Use HMAC-SHA256 for generating the encrypted token instead of XOR cipher.
* CookieStorage now allows secure and httpOnly parameters in the constructor,
which default to false and true.
* Added NonceValidator class for using nonce tokens.

## v2.1.0 (2015-02-01) ##

* Improvements in code quality and documentation
Expand Down
85 changes: 71 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,25 +149,74 @@ try {
}
```

Note that if you are using ajax request on your website, you may also provide
Note that if you are using ajax requests on your website, you may also provide
the CSRF token using a `X-CSRF-Token` header. This is particularly useful, if
you are creating a RESTful api that takes advantage of PUT and DELETE requests.

### Using nonces ###

A nonce is a token that can be used only once. If you are *not* using SSL (i.e.
HTTPS) to secure the connection to your website, but you still need additional
security to protect you against [Replay Attacks](https://en.wikipedia.org/wiki/Replay_attack),
you may use [nonce behavior](http://blog.ircmaxell.com/2013/02/preventing-csrf-attacks.html)
to prevent these types of 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
<?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 and cannot be
used again. If you have a website that relies on a large number of form
submissions, this array of used up tokens can grow quite large. To clear the
array, simply regenerate the token using `regenerateToken()`. Note that the
name of the session variable can be passed to the constructor. For example:

```php
<?php

require 'vendor/autoload.php';

session_start();
$csrf = new \Riimu\Kit\CSRF\NonceValidator('csrf_nonces');
$csrf->validateRequest();

if (count($_SESSION['csrf_nonces']) > 100) {
$csrf->regenerateToken();
}

$token = $csrf->getToken();
```

### Securing Your Website ###

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.

In some rare cases, it may 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
avoid this, you should also regenerate the CSRF token upon authentication.
To regenerate the CSRF token, simply call the `regenerateToken()` method
provided by the `CSRFHandler` class. This invalidates all the previous CSRF
tokens.
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
Expand All @@ -176,6 +225,11 @@ 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 employ SSL
encryption, i.e. use a HTTPS connection. This is the only effective measure
against [Man-in-the-middle](https://www.owasp.org/index.php/Man-in-the-middle_attack)
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](https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29)
attacks. If the attacker is capable of running javascript on your website, the
Expand Down Expand Up @@ -222,11 +276,14 @@ tokens returned by `getToken()` are very different in length. This is because
they are base64 encoded strings that also contain an encryption key for the
token.

In order to prevent BREACH attacks, each token is encrypted using a simple XOR
encryption with another random 32 byte string which has also been generated
using the SecureRandom library. The base64 encoded string actually consists of
the encryption key and the encrypted CSRF token. Thus, the length of the
decoded token string is 64 bytes.
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
HTTPS connection. In order to achieve this, the returned token actually consists
of a random encryption key and a token that has been encrypted using HMAC-SHA256
(which also makes it infeasible to reverse the operation and find out the secret
CSRF 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 encryption key is generated every time `getToken()` is called.
Thus, each string returned by that method is different. If you have a large
Expand Down
25 changes: 25 additions & 0 deletions tests/tests/NonceValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,29 @@ public function testAllowSameTokenIfReturnedAgain()
$this->assertSame($tokenA, $tokenB);
$this->assertTrue($nonce->validateToken($tokenB));
}

public function testAllowSameTokenAfterRegeneration()
{
$nonce = new NonceValidator();
$random = $this->getMock('Riimu\Kit\SecureRandom\SecureRandom', ['getBytes']);

$random->expects($this->exactly(5))->method('getBytes')->will($this->onConsecutiveCalls(
str_repeat('A', CSRFHandler::TOKEN_LENGTH),
str_repeat('A', CSRFHandler::TOKEN_LENGTH),
str_repeat('B', CSRFHandler::TOKEN_LENGTH),
str_repeat('A', CSRFHandler::TOKEN_LENGTH),
str_repeat('A', CSRFHandler::TOKEN_LENGTH)
));

$nonce->setGenerator($random);

$tokenA = $nonce->getToken();
$this->assertTrue($nonce->validateToken($tokenA));
$nonce->regenerateToken();
$nonce->regenerateToken();
$tokenB = $nonce->getToken();

$this->assertSame($tokenA, $tokenB);
$this->assertTrue($nonce->validateToken($tokenB));
}
}

0 comments on commit 2f458f9

Please sign in to comment.