Skip to content

Commit 46ac2c7

Browse files
committed
Merge pull request #30 from mebjas/simultaneous-tokens
Simultaneous tokens
2 parents a1d28d3 + 483fe00 commit 46ac2c7

File tree

2 files changed

+64
-21
lines changed

2 files changed

+64
-21
lines changed

libs/csrf/csrfprotector.php

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ public static function init($length = null, $action = null)
136136
ob_start('csrfProtector::ob_handler');
137137

138138
if (!isset($_COOKIE[self::$config['CSRFP_TOKEN']])
139-
|| !isset($_SESSION[self::$config['CSRFP_TOKEN']]))
139+
|| !isset($_SESSION[self::$config['CSRFP_TOKEN']])
140+
|| !is_array($_SESSION[self::$config['CSRFP_TOKEN']]))
140141
self::refreshToken();
141142

142143
// Set protected by CSRF Protector header
@@ -169,7 +170,7 @@ public static function authorizePost()
169170
//currently for same origin only
170171
if (!(isset($_POST[self::$config['CSRFP_TOKEN']])
171172
&& isset($_SESSION[self::$config['CSRFP_TOKEN']])
172-
&& ($_POST[self::$config['CSRFP_TOKEN']] === $_SESSION[self::$config['CSRFP_TOKEN']])
173+
&& (self::isValidToken($_POST[self::$config['CSRFP_TOKEN']]))
173174
)) {
174175

175176
//action in case of failed validation
@@ -182,7 +183,7 @@ public static function authorizePost()
182183
//currently for same origin only
183184
if (!(isset($_GET[self::$config['CSRFP_TOKEN']])
184185
&& isset($_SESSION[self::$config['CSRFP_TOKEN']])
185-
&& ($_GET[self::$config['CSRFP_TOKEN']] === $_SESSION[self::$config['CSRFP_TOKEN']])
186+
&& (self::isValidToken($_GET[self::$config['CSRFP_TOKEN']]))
186187
)) {
187188

188189
//action in case of failed validation
@@ -193,6 +194,35 @@ public static function authorizePost()
193194
}
194195
}
195196

197+
/*
198+
* Function: isValidToken
199+
* function to check the validity of token in session array
200+
* Function also clears all tokens older than latest one
201+
*
202+
* Parameters:
203+
* $token - the token sent with GET or POST payload
204+
*
205+
* Returns:
206+
* bool - true if its valid else false
207+
*/
208+
private static function isValidToken($token) {
209+
if (!isset($_SESSION[self::$config['CSRFP_TOKEN']])) return false;
210+
if (!is_array($_SESSION[self::$config['CSRFP_TOKEN']])) return false;
211+
foreach ($_SESSION[self::$config['CSRFP_TOKEN']] as $key => $value) {
212+
if ($value == $token) {
213+
214+
// Clear all older tokens assuming they have been consumed
215+
foreach ($_SESSION[self::$config['CSRFP_TOKEN']] as $_key => $_value) {
216+
if ($_value == $token) break;
217+
array_shift($_SESSION[self::$config['CSRFP_TOKEN']]);
218+
}
219+
return true;
220+
}
221+
}
222+
223+
return false;
224+
}
225+
196226
/*
197227
* Function: failedValidationAction
198228
* function to be called in case of failed validation
@@ -266,8 +296,12 @@ public static function refreshToken()
266296
{
267297
$token = self::generateAuthToken();
268298

299+
if (!isset($_SESSION[self::$config['CSRFP_TOKEN']])
300+
|| !is_array($_SESSION[self::$config['CSRFP_TOKEN']]))
301+
$_SESSION[self::$config['CSRFP_TOKEN']] = array();
302+
269303
//set token to session for server side validation
270-
$_SESSION[self::$config['CSRFP_TOKEN']] = $token;
304+
array_push($_SESSION[self::$config['CSRFP_TOKEN']], $token);
271305

272306
//set token to cookie for client side processing
273307
setcookie(self::$config['CSRFP_TOKEN'],
@@ -338,6 +372,10 @@ public static function ob_handler($buffer, $flags)
338372
}
339373
}
340374

375+
// TODO: statically rewrite all forms as well so that if a form is submitted
376+
// before the js has worked on, it will still have token to send
377+
// @priority: medium @labels: important
378+
341379
//add a <noscript> message to outgoing HTML output,
342380
//informing the user to enable js for CSRFProtector to work
343381
//best section to add, after <body> tag

test/csrfprotector_test.php

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
2+
date_default_timezone_set('UTC');
33
require_once __DIR__ .'/../libs/csrf/csrfprotector.php';
44

55
/**
@@ -52,14 +52,16 @@ public function setUp()
5252
$_SERVER['HTTP_HOST'] = 'test'; // For isUrlAllowed
5353
$_SERVER['PHP_SELF'] = '/index.php'; // For authorizePost
5454
$_POST[csrfprotector::$config['CSRFP_TOKEN']] = $_GET[csrfprotector::$config['CSRFP_TOKEN']] = '123';
55-
$_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = 'abc'; //token mismatch - leading to failed validation
55+
$_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('abc'); //token mismatch - leading to failed validation
5656
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
5757

5858
$this->config = include(__DIR__ .'/../libs/config.sample.php');
5959

6060
// Create an instance of config file -- for testing
6161
$data = file_get_contents(__DIR__ .'/../libs/config.sample.php');
62-
file_put_contents(__DIR__ .'/../libs/config.php', $data);
62+
file_put_contents(__DIR__ .'/../libs/config.php', $data);
63+
64+
if (!defined('__TESTING_CSRFP__')) define('__TESTING_CSRFP__', true);
6365
}
6466

6567
/**
@@ -76,17 +78,16 @@ public function tearDown()
7678
public function testRefreshToken()
7779
{
7880

79-
$val = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = $_COOKIE[csrfprotector::$config['CSRFP_TOKEN']] = '123abcd';
80-
81-
81+
$val = $_COOKIE[csrfprotector::$config['CSRFP_TOKEN']] = '123abcd';
82+
$_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('123abcd');
8283
csrfProtector::$config['tokenLength'] = 20;
8384
csrfProtector::refreshToken();
8485

85-
$this->assertTrue(strcmp($val, $_SESSION[csrfprotector::$config['CSRFP_TOKEN']]) != 0);
86+
$this->assertTrue(strcmp($val, $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][1]) != 0);
8687

8788
$this->assertTrue(csrfP_wrapper::checkHeader('Set-Cookie'));
8889
$this->assertTrue(csrfP_wrapper::checkHeader('csrfp_token'));
89-
$this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']]));
90+
$this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']][1]));
9091
}
9192

9293

@@ -247,29 +248,32 @@ public function testAuthorisePost_failedAction_6()
247248
*/
248249
public function testAuthorisePost_success()
249250
{
251+
250252
$_SERVER['REQUEST_METHOD'] = 'POST';
251-
$_POST[csrfprotector::$config['CSRFP_TOKEN']] = $_GET[csrfprotector::$config['CSRFP_TOKEN']] = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']];
253+
$_POST[csrfprotector::$config['CSRFP_TOKEN']]
254+
= $_GET[csrfprotector::$config['CSRFP_TOKEN']]
255+
= $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0];
252256
$temp = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']];
253257

254258
csrfprotector::authorizePost(); //will create new session and cookies
255-
256-
$this->assertFalse($temp == $_SESSION[csrfprotector::$config['CSRFP_TOKEN']]);
259+
$this->assertFalse($temp == $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0]);
257260
$this->assertTrue(csrfp_wrapper::checkHeader('Set-Cookie'));
258261
$this->assertTrue(csrfp_wrapper::checkHeader('csrfp_token'));
259-
$this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']])); // Combine these 3 later
262+
// $this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0])); // Combine these 3 later
260263

261264
// For get method
262265
$_SERVER['REQUEST_METHOD'] = 'GET';
263266
csrfp_wrapper::changeRequestType('GET');
264-
$_POST[csrfprotector::$config['CSRFP_TOKEN']] = $_GET[csrfprotector::$config['CSRFP_TOKEN']] = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']];
267+
$_POST[csrfprotector::$config['CSRFP_TOKEN']]
268+
= $_GET[csrfprotector::$config['CSRFP_TOKEN']]
269+
= $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0];
265270
$temp = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']];
266271

267272
csrfprotector::authorizePost(); //will create new session and cookies
268-
269273
$this->assertFalse($temp == $_SESSION[csrfprotector::$config['CSRFP_TOKEN']]);
270274
$this->assertTrue(csrfp_wrapper::checkHeader('Set-Cookie'));
271275
$this->assertTrue(csrfp_wrapper::checkHeader('csrfp_token'));
272-
$this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']])); // Combine these 3 later
276+
// $this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0])); // Combine these 3 later
273277
}
274278

275279
/**
@@ -388,11 +392,12 @@ public function testisURLallowed()
388392
public function testModCSRFPEnabledException()
389393
{
390394
putenv('mod_csrfp_enabled=true');
391-
$temp = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = $_COOKIE[csrfprotector::$config['CSRFP_TOKEN']] = 'abc';
395+
$temp = $_COOKIE[csrfprotector::$config['CSRFP_TOKEN']] = 'abc';
396+
$_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('abc');
392397
csrfProtector::init();
393398

394399
// Assuming no cookie change
395-
$this->assertTrue($temp == $_SESSION[csrfprotector::$config['CSRFP_TOKEN']]);
400+
$this->assertTrue($temp == $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0]);
396401
$this->assertTrue($temp == $_COOKIE[csrfprotector::$config['CSRFP_TOKEN']]);
397402
}
398403
}

0 commit comments

Comments
 (0)