Skip to content

Instantly share code, notes, and snippets.

@CMCDragonkai
Created January 6, 2014 07:07
Show Gist options
  • Save CMCDragonkai/8279280 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/8279280 to your computer and use it in GitHub Desktop.
PHP: Codeigniter CSRF functionality does not support putting the CSRF token in the HTTP headers for the purposes of the double submit cookie method. It also only runs the CSRF check on POST and not on PUT or DELETE. This drop in MY_Security.php makes sure CSRF runs on POST, PUT or DELETE and checks the HTTP headers for X-XSRF-TOKEN recommended b…
<?php
class MY_Security extends CI_Security{
//overriding the normal csrf_verify, this gets automatically called in the Input library's constructor
//verifying on POST and PUT and DELETE
public function csrf_verify(){
$request_method = strtoupper($_SERVER['REQUEST_METHOD']);
//If it is GET, ignore the rest
if($request_method == 'GET' OR $request_method == 'HEAD' OR $request_method == 'OPTIONS'){
return $this->csrf_set_cookie();
}
// Check if URI has been whitelisted from CSRF checks
if($exclude_uris = config_item('csrf_exclude_uris')){
$uri = load_class('URI', 'core');
if(in_array($uri->uri_string(), $exclude_uris)){
return $this;
}
}
//Double submit cookie method: COOKIE needs to exist and at least either POST or SERVER needs to exist and at least one of the POST or SERVER must match the COOKIE
if(
!isset($_COOKIE[$this->_csrf_cookie_name])
OR
(
!isset($_POST[$this->_csrf_cookie_name])
AND
!isset($_SERVER['HTTP_X_XSRF_TOKEN'])
)
){
$this->csrf_show_error();
}
//if CSRF token was in the POST, then it needs to match the cookie
if(isset($_POST[$this->_csrf_token_name])){
if($_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]){
$this->csrf_show_error();
}
}
//if CSRF token was in the SERVER (headers), then it needs to match the cookie
if(isset($_SERVER['HTTP_X_XSRF_TOKEN'])){
if($_SERVER['HTTP_X_XSRF_TOKEN'] !== $_COOKIE[$this->_csrf_cookie_name]){
$this->csrf_show_error();
}
}
// We kill this since we're done and we don't want to polute the _POST array
unset($_POST[$this->_csrf_token_name]);
if(config_item('csrf_regenerate')){
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_hash = '';
}
$this->_csrf_set_hash();
$this->csrf_set_cookie();
log_message('debug', 'CSRF token verified');
return $this;
}
}
@RastoStric
Copy link

Unbelievable! I was just thinking about the same CI modification as You have made here! I cannot believe that nobody cares about CI CSRF protection of PUT and DELETE. With your kind permission I will use this code in my app.
Just wondering about the URI whitelist - is it your idea or it is in CI 3.0? Can you give me an example where to use it?
Thank you

@brenjt
Copy link

brenjt commented Dec 9, 2014

This is awesome. Thank you so much for taking the time to create it. This should absolutely be pushed to the 3.0 branch.

@matbeard
Copy link

matbeard commented Oct 1, 2015

This looks great... I just can't get it to work. I've placed MY_Security in the application/core folder and I'm sending HTTP_X_XSRF_TOKEN with the token, which matches the CSFR cookie, but I'm still getting The action you have requested is not allowed.

Any ideas?

@MohannadNaj
Copy link

Thanks for this!

for the purpose of only make codeigniter recognize csrf in Headers, can anyone illuminate me why this simple override is not enough:

<?php
class MY_Security extends CI_Security{

    public function csrf_verify()
    {
        $headers = getallheaders();
        if(isset($headers[$this->_csrf_token_name])) {
            $_POST[$this->_csrf_token_name] = $headers[$this->_csrf_token_name];
        }
        parent::csrf_verify();
    }
}

I tried some tests and it's working well, but since I'm not expert enough I hope someone light me up on this. thanks.

@CMCDragonkai
Copy link
Author

I'm not working in codeigniter anymore, so if anyone has updates to it, feel free to post it and I'll update the gist.

@rkorebrits
Copy link

rkorebrits commented Dec 20, 2017

Edit: This is because Nginx apparently doesn't support this function. See the comments on http://php.net/manual/en/function.getallheaders.php for a solution.

@MohannadNaj
I get this error when I implement your code:

 
  Fatal error: Uncaught Error: Class 'CI_Controller' not found in /projects/ditto/www/system/core/CodeIgniter.php:366
  Stack trace:
  #0 /projects/ditto/www/system/core/Common.php(462): get_instance()
  #1 /projects/ditto/www/system/core/Exceptions.php(105): log_message('error', 'Severity: error...')
  #2 /projects/ditto/www/system/core/Common.php(727): CI_Exceptions->log_exception('error', 'Exception: Call...', '/projects/ditto...', 6)
  #3 [internal function]: _exception_handler(Object(Error))
  #4 {main}
  thrown in /projects/ditto/www/system/core/CodeIgniter.php on line 366
 
  Fatal error: Uncaught Error: Class 'CI_Controller' not found in /projects/ditto/www/system/core/CodeIgniter.php:366
  Stack trace:
  #0 /projects/ditto/www/system/core/Common.php(462): get_instance()
  #1 /projects/ditto/www/system/core/Exceptions.php(105): log_message('error', 'Severity: Error...')
  #2 /projects/ditto/www/system/core/Common.php(690): CI_Exceptions->log_exception('Error', 'Uncaught Error:...', '/projects/ditto...', 366)
  #3 /projects/ditto/www/system/core/Common.php(766): _error_handler(1, 'Uncaught Error:...', '/projects/ditto...', 366)
  #4 [internal function]: _shutdown_handler()
  #5 {main}
  thrown in /projects/ditto/www/system/core/CodeIgniter.php on line 366
   

Any idea what it could be?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment