CSRF.php
<?php
namespace Lia\Test;
use \Lia\Utility\CSRF;
/** For testing CSRF validation
*
*/
class CSRFTester extends \Tlf\Tester {
public function testCSRF(){
$_SERVER['REQUEST_URI'] = '/';
$_SERVER['HTTP_HOST'] = 'http://example.com';
$_SERVER['HTTP_REFERER'] = 'http://example.com';
echo "\n\n#### CUSTOM/ONE-OFF TESTS ####";
$this->test("One token per form id during the same request");
$token1 = CSRF::get_csrf_value('one_token', '', 10);
$token2 = CSRF::get_csrf_value('one_token', '', 10);
$token3 = CSRF::get_csrf_value('one_token', '', 10);
$this->is_true($token1 == $token2 && $token2 == $token3);
$this->test("Gets token, submits form, and generates new form for that same token. Tokens should be different");
$token1 = ResettableCSRF::get_csrf_value('sameid', '', 10);
ResettableCSRF::reset();
$valid1 = ResettableCSRF::is_request_valid('sameid', ['csrf-sameid'=>$token1]);
$token2 = ResettableCSRF::get_csrf_value('sameid', '', 10);
ResettableCSRF::reset();
$valid2 = ResettableCSRF::is_request_valid('sameid', ['csrf-sameid'=>$token2]);
$this->is_true($valid1 && $valid2);
$this->is_true($token1 != $token2);
$this->test("Cannot validate same token on different requests");
$token1 = ResettableCSRF::get_csrf_value('diffreqs', '', 10);
ResettableCSRF::reset();
$valid1 = ResettableCSRF::is_request_valid('diffreqs', ['csrf-diffreqs'=>$token1]);
ResettableCSRF::reset();
$invalid1 = ResettableCSRF::is_request_valid('diffreqs', ['csrf-diffreqs'=>$token1]);
$this->is_true($valid1);
$this->is_false($invalid1);
$this->test("Multiple forms, different IDs");
$token1 = CSRF::get_csrf_value('id1', '', 10);
$token2 = CSRF::get_csrf_value('id2', '', 10);
$token3 = CSRF::get_csrf_value('id3', '', 10);
$valid1 = CSRF::is_request_valid('id1', ['csrf-id1'=>$token1]);
$valid2 = CSRF::is_request_valid('id2', ['csrf-id2'=>$token2]);
$valid3 = CSRF::is_request_valid('id3', ['csrf-id3'=>$token3]);
$this->is_true($valid1 && $valid2 && $valid3);
$this->test("Multiple forms, same IDs, different requests for setup");
ResettableCSRF::reset();
$token1 = ResettableCSRF::get_csrf_value('id1', '', 10);
ResettableCSRF::reset();
$token2 = ResettableCSRF::get_csrf_value('id1', '', 10);
ResettableCSRF::reset();
$token3 = ResettableCSRF::get_csrf_value('id1', '', 10);
ResettableCSRF::reset();
$this->is_true($token1!=$token2 && $token2 != $token3);
$valid1 = ResettableCSRF::is_request_valid('id1', ['csrf-id1'=>$token1]);
$valid2 = ResettableCSRF::is_request_valid('id1', ['csrf-id1'=>$token2]);
$valid3 = ResettableCSRF::is_request_valid('id1', ['csrf-id1'=>$token3]);
$this->is_true($valid1 && $valid2 && $valid3);
$this->test("Multiple forms, same IDs, different requests for setup and validation");
ResettableCSRF::reset();
$token1 = ResettableCSRF::get_csrf_value('id1', '', 10);
ResettableCSRF::reset();
$token2 = ResettableCSRF::get_csrf_value('id1', '', 10);
ResettableCSRF::reset();
$token3 = ResettableCSRF::get_csrf_value('id1', '', 10);
ResettableCSRF::reset();
$this->is_true($token1!=$token2 && $token2 != $token3);
$valid1 = ResettableCSRF::is_request_valid('id1', ['csrf-id1'=>$token1]);
ResettableCSRF::reset();
$valid2 = ResettableCSRF::is_request_valid('id1', ['csrf-id1'=>$token2]);
ResettableCSRF::reset();
$valid3 = ResettableCSRF::is_request_valid('id1', ['csrf-id1'=>$token3]);
ResettableCSRF::reset();
$this->is_true($valid1 && $valid2 && $valid3);
$pass = [
'path_specified'=>[
'form_id'=>'pass1',
'request_uri'=>'/test1/',
'target_uri'=>'/test1/',
'host'=>'http://test1.com',
'referer'=>'http://test1.com',
],
'form_id_already_used'=>[
'form_id'=>'pass1',
'request_uri'=>'/test1/',
'target_uri'=>'/test1/',
'host'=>'http://test1.com',
'referer'=>'http://test1.com',
],
'any_path'=>[
'form_id'=>'pass2',
'request_uri'=>'/some-path/',
'target_uri'=>'',
'host'=>'http://test-path.com',
'referer'=>'http://test-path.com',
],
];
echo "\n\n#### TESTS THAT HAVE VALID CSRF CHECKS ####";
foreach ($pass as $test_name => $test){
$this->test($test_name);
$_SERVER['REQUEST_URI'] = $test['request_uri'];
$_SERVER['HTTP_HOST'] = $test['host'];
$_SERVER['HTTP_REFERER'] = $test['referer'];
$key = 'csrf-'.$test['form_id'];
$value = CSRF::get_csrf_value($test['form_id'], $test['target_uri'], 10);
$is_valid1 = CSRF::is_request_valid($test['form_id'], [$key=>$value]);
$is_valid2 = CSRF::is_request_valid($test['form_id'], [$key=>$value]);
$is_valid3 = CSRF::is_request_valid($test['form_id'], [$key=>$value]);
$this->is_true($is_valid1&&$is_valid2&&$is_valid3);
}
$fail = [
'target_uri_wrong'=>[
'form_id'=>'fail1',
'request_uri'=>'/wrong-uri/',
'target_uri'=>'/test1/',
'host'=>'http://test1.com',
'referer'=>'http://test1.com',
],
'test_no_path'=>[
'form_id'=>'fail2',
'request_uri'=>'',
'target_uri'=>'/some-path/',
'host'=>'http://test-path.com',
'referer'=>'http://test-path.com',
],
'test_no_referer'=>[
'form_id'=>'fail3',
'request_uri'=>'/fail1/',
'target_uri'=>'/fail1/',
'host'=>'http://test1.com',
'referer'=>'',
],
'test_no_referer_or_host'=>[
'form_id'=>'fail4',
'request_uri'=>'/fail1/',
'target_uri'=>'/fail1/',
'host'=>'',
'referer'=>'',
],
'test_wrong_key'=>[
'form_id'=>'fail6',
'request_uri'=>'/fail1/',
'target_uri'=>'/fail1/',
'host'=>'http://test1.com',
'referer'=>'http://test1.com',
'key'=>'csrf-wrongformid',
],
'test_wrong_value'=>[
'form_id'=>'fail7',
'request_uri'=>'/fail1/',
'target_uri'=>'/fail1/',
'host'=>'http://test1.com',
'referer'=>'http://test1.com',
'value'=>'not-a-real-token',
],
'test_wrong_formid'=>[
'form_id'=>'fail8',
'request_uri'=>'/fail1/',
'target_uri'=>'/fail1/',
'host'=>'http://test1.com',
'referer'=>'http://test1.com',
'receipt_form_id'=>'wrong-form-id',
],
'test_expired'=>[
'form_id'=>'fail9',
'request_uri'=>'/fail1/',
'target_uri'=>'/fail1/',
'host'=>'http://test1.com',
'referer'=>'http://test1.com',
'expiry'=>-10
],
];
echo "\n\n#### TESTS THAT FAIL CSRF CHECKS ####";
foreach ($fail as $test_name => $test){
$this->test($test_name);
$_SERVER['REQUEST_URI'] = $test['request_uri'];
$_SERVER['HTTP_HOST'] = $test['host'];
$_SERVER['HTTP_REFERER'] = $test['referer'];
$key = 'csrf-'.($test['key'] ?? $test['form_id']);
$value = $test['value'] ?? CSRF::get_csrf_value($test['form_id'], $test['target_uri'],
$test['expiry'] ?? 10);
$is_valid = CSRF::is_request_valid($test['receipt_form_id'] ?? $test['form_id'], [$key=>$value]);
$this->is_false($is_valid);
}
$exceptions = [
'test_no_host'=>[
'form_id'=>'fail5',
'request_uri'=>'/fail1/',
'target_uri'=>'/fail1/',
'host'=>'',
'referer'=>'http://test1.com',
],
// I cannot test CSRF_TOKEN_MISMATCH (*which should NEVER happen*) or CSRF_SESSION_NOT_STARTED
];
echo "\n\n#### TESTS THAT THROW EXCEPTIONS ####";
foreach ($exceptions as $test_name => $test){
$this->test($test_name);
$_SERVER['REQUEST_URI'] = $test['request_uri'];
$_SERVER['HTTP_HOST'] = $test['host'];
$_SERVER['HTTP_REFERER'] = $test['referer'];
$key = 'csrf-'.($test['key'] ?? $test['form_id']);
$value = $test['value'] ?? CSRF::get_csrf_value($test['form_id'], $test['target_uri'], 10);
$did_throw = false;
try {
$is_valid = CSRF::is_request_valid($test['receipt_form_id'] ?? $test['form_id'], [$key=>$value]);
} catch (\Exception $e){
$did_throw = true;
}
$this->is_true($did_throw);
}
}
}
/** allows us to pretend that a new request has started by resetting CSRF's static class variables to default state */
class ResettableCSRF extends CSRF {
static public function reset(){
static::$valid_requests = [];
static::$generated_csrf = [];
}
}