Browse files

modify random value generation for csrf token to be crypto-secure (i.…

…e., no more mt_rand() usage)
  • Loading branch information...
1 parent 2b12446 commit 7e3c8360ff4f5f34d09d87a599a1ac527dbb054a @pmjones pmjones committed Mar 17, 2013
View
19 README.md
@@ -197,8 +197,9 @@ $segment->clearFlash();
```
-CSRF Token
-----------
+Cross-Site Request Forgery
+==========================
+
A "cross-site request forgery" is a security issue where the attacker, via
malicious JavaScript or other means, issues a request in-the-blind from a
@@ -208,6 +209,9 @@ did not actually make the request (the malicious JavaScript did).
<http://en.wikipedia.org/wiki/Cross-site_request_forgery>
+Defending Against CSRF
+----------------------
+
To defend against CSRF attacks, server-side logic should:
1. Place a token value unique to each authenticated user session in each form;
@@ -271,3 +275,14 @@ if ($unsafe && $user->isAuthenticated()) {
echo "CSRF attacks only affect unsafe requests by authenticated users.";
}
```
+
+CSRF Value Generation
+---------------------
+
+For a CSRF token to be useful, its random value must be cryptographically
+secure. Using things like `mt_rand()` is insufficient. Aura.Session comes with
+a `Randval` class that implements a `RandvalInterface`, and uses either the
+`openssl` or the `mcrypt` extension to generate a random value. If you do not
+have one of these extensions installed, you will need your own random-value
+implementation of the `RandvalInterface`. We suggest a wrapper around
+[RandomLib](https://github.com/ircmaxell/RandomLib).
View
10 config/default.php
@@ -10,6 +10,11 @@
$di->set('session_manager', $di->lazyNew('Aura\Session\Manager'));
/**
+ * Aura\Session\CsrfTokenFactory
+ */
+$di->params['Aura\Session\CsrfTokenFactory']['randval'] = $di->lazyNew('Aura\Session\Randval');
+
+/**
* Aura\Session\Manager
*/
$di->params['Aura\Session\Manager'] = [
@@ -19,6 +24,11 @@
];
/**
+ * Aura\Session\Randval
+ */
+$di->params['Aura\Session\Randval']['phpfunc'] = $di->lazyNew('Aura\Session\Phpfunc');
+
+/**
* Aura\Session\Segment
*/
$di->params['Aura\Session\Segment'] = [
View
6 scripts/instance.php
@@ -2,6 +2,10 @@
require dirname(__DIR__) . '/src.php';
return new \Aura\Session\Manager(
new \Aura\Session\SegmentFactory,
- new \Aura\Session\CsrfTokenFactory,
+ new \Aura\Session\CsrfTokenFactory(
+ new \Aura\Session\Randval(
+ new \Aura\Session\Phpfunc
+ )
+ ),
$_COOKIE
);
View
4 src.php
@@ -1,7 +1,11 @@
<?php
require_once __DIR__ . '/src/Aura/Session/CsrfToken.php';
require_once __DIR__ . '/src/Aura/Session/CsrfTokenFactory.php';
+require_once __DIR__ . '/src/Aura/Session/Exception.php';
require_once __DIR__ . '/src/Aura/Session/Manager.php';
+require_once __DIR__ . '/src/Aura/Session/Phpfunc.php';
+require_once __DIR__ . '/src/Aura/Session/RandvalInterface.php';
+require_once __DIR__ . '/src/Aura/Session/Randval.php';
require_once __DIR__ . '/src/Aura/Session/SegmentInterface.php';
require_once __DIR__ . '/src/Aura/Session/Segment.php';
require_once __DIR__ . '/src/Aura/Session/SegmentFactory.php';
View
39 src/Aura/Session/CsrfToken.php
@@ -22,6 +22,15 @@
class CsrfToken
{
/**
+ *
+ * A cryptographically-secure random value generator.
+ *
+ * @var RandvalInterface
+ *
+ */
+ protected $randval;
+
+ /**
*
* Session segment for values in this class.
*
@@ -36,10 +45,14 @@ class CsrfToken
*
* @param Segment $segment A segment for values in this class.
*
+ * @param RandvalInterface $randval A cryptographically-secure random
+ * value generator.
+ *
*/
- public function __construct(Segment $segment)
+ public function __construct(Segment $segment, RandvalInterface $randval)
{
$this->segment = $segment;
+ $this->randval = $randval;
if (! isset($this->segment->value)) {
$this->regenerateValue();
}
@@ -80,28 +93,6 @@ public function getValue()
*/
public function regenerateValue()
{
- // number of bytes
- $len = 32;
-
- // eventual value
- $value = false;
-
- // best
- if (extension_loaded('openssl')) {
- $value = openssl_random_pseudo_bytes($len, $strong);
- }
-
- // good
- if (! $value && extension_loaded('mcrypt')) {
- $value = mcrypt_create_iv($len, MCRYPT_DEV_URANDOM);
- }
-
- // merely ok
- if (! $value) {
- $value = uniqid(mt_rand(), true);
- }
-
- // set the value (hash helps hide some info w/ uniqid)
- $this->segment->value = hash('sha512', $value);
+ $this->segment->value = hash('sha512', $this->randval->generate());
}
}
View
24 src/Aura/Session/CsrfTokenFactory.php
@@ -21,6 +21,28 @@ class CsrfTokenFactory
{
/**
*
+ * A cryptographically-secure random value generator.
+ *
+ * @var RandvalInterface
+ *
+ */
+ protected $randval;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param RandvalInterface $randval A cryptographically-secure random
+ * value generator.
+ *
+ */
+ public function __construct(RandvalInterface $randval)
+ {
+ $this->randval = $randval;
+ }
+
+ /**
+ *
* Creates a CsrfToken object.
*
* @param Manager $manager The session manager.
@@ -31,6 +53,6 @@ class CsrfTokenFactory
public function newInstance(Manager $manager)
{
$segment = $manager->newSegment('Aura\Session\CsrfToken');
- return new CsrfToken($segment);
+ return new CsrfToken($segment, $this->randval);
}
}
View
7 src/Aura/Session/Exception.php
@@ -0,0 +1,7 @@
+<?php
+namespace Aura\Session;
+
+class Exception extends \Exception
+{
+
+}
View
10 src/Aura/Session/Phpfunc.php
@@ -0,0 +1,10 @@
+<?php
+namespace Aura\Session;
+
+class Phpfunc
+{
+ public function __call($func, $args)
+ {
+ return call_user_func_array($func, $args);
+ }
+}
View
31 src/Aura/Session/Randval.php
@@ -0,0 +1,31 @@
+<?php
+namespace Aura\Session;
+
+use Aura\Session\Exception;
+
+class Randval implements RandvalInterface
+{
+ public function __construct(Phpfunc $phpfunc)
+ {
+ $this->phpfunc = $phpfunc;
+ }
+
+ public function generate()
+ {
+ $bytes = 32;
+
+ if ($this->phpfunc->extension_loaded('openssl')) {
+ return $this->phpfunc->openssl_random_pseudo_bytes($bytes);
+ }
+
+ if ($this->phpfunc->extension_loaded('mcrypt')) {
+ return $this->phpfunc->mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
+ }
+
+ $message = "Cannot generate cryptographically secure random values. "
+ . "Please install extension 'openssl' or 'mcrypt', or use "
+ . "another cryptographically secure implementation.";
+
+ throw new Exception($message);
+ }
+}
View
7 src/Aura/Session/RandvalInterface.php
@@ -0,0 +1,7 @@
+<?php
+namespace Aura\Session;
+
+interface RandvalInterface
+{
+ public function generate();
+}
View
23 tests/Aura/Session/CsrfTokenTest.php
@@ -9,11 +9,15 @@ class CsrfTokenTest extends \PHPUnit_Framework_TestCase
protected $name = __CLASS__;
+ protected $phpfunc;
+
protected function setUp()
{
+ $this->phpfunc = new MockPhpfunc;
+
$this->session = new Manager(
new SegmentFactory,
- new CsrfTokenFactory,
+ new CsrfTokenFactory(new Randval($this->phpfunc)),
$_COOKIE
);
}
@@ -40,9 +44,22 @@ public function testGetAndRegenerateValue()
$old = $token->getValue();
$this->assertTrue($old != '');
+ // with openssl
+ $this->phpfunc->setExtensions(['openssl']);
+ $token->regenerateValue();
+ $openssl = $token->getValue();
+ $this->assertTrue($old != $openssl);
+
+ // with mcrypt
+ $this->phpfunc->setExtensions(['mcrypt']);
+ $token->regenerateValue();
+ $mcrypt = $token->getValue();
+ $this->assertTrue($old != $openssl && $old != $mcrypt);
+
+ // with nothing
+ $this->phpfunc->setExtensions([]);
+ $this->setExpectedException('Aura\Session\Exception');
$token->regenerateValue();
- $new = $token->getValue();
- $this->assertTrue($old != $new);
}
public function testIsValid()
View
2 tests/Aura/Session/ManagerTest.php
@@ -16,7 +16,7 @@ protected function newSession(array $cookies = [])
{
return new Manager(
new SegmentFactory,
- new CsrfTokenFactory,
+ new CsrfTokenFactory(new Randval(new Phpfunc)),
$cookies
);
}
View
26 tests/Aura/Session/MockPhpfunc.php
@@ -0,0 +1,26 @@
+<?php
+namespace Aura\Session;
+
+class MockPhpfunc extends Phpfunc
+{
+ protected $extensions = [];
+
+ public function __construct()
+ {
+ $this->setExtensions(get_loaded_extensions());
+ }
+
+ public function setExtensions(array $extensions)
+ {
+ $this->extensions = $extensions;
+ }
+
+ public function extension_loaded($name)
+ {
+ // for parent coverage
+ $this->__call('extension_loaded', [$name]);
+
+ // for testing
+ return in_array($name, $this->extensions);
+ }
+}
View
2 tests/Aura/Session/SegmentTest.php
@@ -19,7 +19,7 @@ protected function newSession(array $cookies = [])
{
return new Manager(
new SegmentFactory,
- new CsrfTokenFactory,
+ new CsrfTokenFactory(new Randval(new Phpfunc)),
$cookies
);
}
View
1 tests/WiringTest.php
@@ -19,6 +19,7 @@ public function testServices()
public function testInstances()
{
+ $this->assertNewInstance('Aura\Session\CsrfTokenFactory');
$this->assertNewInstance('Aura\Session\Manager');
$this->assertNewInstance('Aura\Session\Segment');
}

0 comments on commit 7e3c836

Please sign in to comment.