Protect a CodeIgniter Application Against CSRF

Protect a CodeIgniter Application Against CSRF

Tutorial Details
  • Framework: CodeIgniter
  • Version: 1.7.2
  • Difficulty: Medium
  • Estimated Completion Time: 1 hour

In today’s tutorial, we will learn how to painlessly protect your CodeIgniter (pre 2.0) application against Cross-Site Request Forgery attacks. The library we’ll be creating today will automate all of the protection mechanisms, making your site stronger and more secure.


Step 1 - Understanding the Attack Vector

Cross-Site Request Forgery attacks are based on unprotected forms on your sites.

An attacker could create a bogus form on his site – for example a search form. This form could have hidden inputs that contain malicious data. Now the form isn’t actually sent to the attacker’s site to perform the search; in reality, the form points to your site! Since your website will trust that the form is genuine, it goes through and executes the requested (and perhaps malicious) actions.

Imagine that a user is logged into your site, and is redirected to the attacker’s site for some reason (phishing, XSS, you name it). The attacker’s form could point to your account deletion form on your site. If the user performs a “search” on the attackers site, his account will then be deleted without him knowing!

There are numerous ways to prevent these sorts of attacks.

  • Check the HTTP Referer header and see if it belongs to your site. The problem with this method is that not all browsers submit this header (I personally had this problem once with IE7); it could be forged anyway.
  • Another method (the one we will use), is to include a random string (a “token”) on each form, and store that token on the user’s session. On each POST request, compare the submitted token to the one on store, and, if they differ, deny the request. Your site still needs to be protected against XSS though, because if it’s not, this method becomes useless.

Step 2 - Planning

We’ll need to do three things for each request:

  • If the request is a POST request, validate that the submitted token.
  • Generate a token in case there isn’t one.
  • Inject the token into all forms. This makes the method seamless and painless, since no modification is needed on your views.

To do this automatically, we’ll use CodeIgniter hooks. Hooks allow us to execute all actions on different parts of the request. We’ll need three:

  • post_controller_constructor – To check the submitted token, we’ll need a post_controller_constructor hook. Hooking this action before this event doesn’t give us access to CodeIgniter’s instance correctly.
  • Generate the Token – To generate the token, we’ll use the same hook as before. This allows us to have access to it in case we needed to print it manually in our views.
  • display_override – To inject the token automatically in our views, we’ll need to use the display_override hook. This is a tricky hook though, since, as its name implies, it overrides the display of the views. We need to output the content ourselves if we use this hook (check the CodeIgniter documentation for more info).

Step 3 - Token Generation

Let’s get started. We’ll go step by step in order to explain everything as thoroughly as possible. We’ll create the method that generates the token first, so we can test everything correctly afterwards. Create a file in your system/application/hooks folder called “csrf.php“, and paste the following code:

<?php
/**
 * CSRF Protection Class
 */
class CSRF_Protection
{
  /**
   * Holds CI instance
   *
   * @var CI instance
   */
  private $CI;

  /**
   * Name used to store token on session
   *
   * @var string
   */
  private static $token_name = 'li_token';

  /**
   * Stores the token
   *
   * @var string
   */
  private static $token;

  // -----------------------------------------------------------------------------------

  public function __construct()
  {
    $this->CI =& get_instance();
  }
}

Hopefully, what we’ve added above should look rather basic to you. We’re creating a class, called CSRF_Protection, an instance variable to hold the CodeIgniter instance, two static variables to hold the name of the parameter that will store the token, and one to store the token itself for easy access throughout the class. Within the class constructor (__construct), we simply retrieve the CodeIgniter instance, and store it in our corresponding instance variable.

Note: The “name of the parameter” is the name of the field that holds the token. So on the forms, it’s the name of the hidden input.

After the class constructor, paste the following code:

/**
 * Generates a CSRF token and stores it on session. Only one token per session is generated.
 * This must be tied to a post-controller hook, and before the hook
 * that calls the inject_tokens method().
 *
 * @return void
 * @author Ian Murray
 */
public function generate_token()
{
  // Load session library if not loaded
  $this->CI->load->library('session');

  if ($this->CI->session->userdata(self::$token_name) === FALSE)
  {
    // Generate a token and store it on session, since old one appears to have expired.
    self::$token = md5(uniqid() . microtime() . rand());

    $this->CI->session->set_userdata(self::$token_name, self::$token);
  }
  else
  {
    // Set it to local variable for easy access
    self::$token = $this->CI->session->userdata(self::$token_name);
  }
}

Step by step:

  • We load the session library, in case it’s not being loaded automatically. We need this to store the token.
  • We determine if the token was already generated. If the userdata method returns FALSE, then the token is not yet present.
  • We generate a token and store it in the class variable for easy access. The token could be almost anything really. I used those three functions to ensure that it’s very random.
  • We then store it in the session variable with the name configured previously.
  • If the token was already present, we don’t generate it and instead store it in the class variable for easy access.

Step 4 - Token Validation

We need to ensure that the token was submitted, and is valid in case the request is a POST request. Go ahead and paste the following code into your csrf.php file:

/**
 * Validates a submitted token when POST request is made.
 *
 * @return void
 * @author Ian Murray
 */
public function validate_tokens()
{
  // Is this a post request?
  if ($_SERVER['REQUEST_METHOD'] == 'POST')
  {
    // Is the token field set and valid?
    $posted_token = $this->CI->input->post(self::$token_name);
    if ($posted_token === FALSE || $posted_token != $this->CI->session->userdata(self::$token_name))
    {
      // Invalid request, send error 400.
      show_error('Request was invalid. Tokens did not match.', 400);
    }
  }
}
  • Above, we validate the request, but only if it’s a POST request, which means that a form was, in fact, submitted. We check this by taking a look at the REQUEST_METHOD within the $_SERVER super global.
  • Check if the token was actually posted. If the output of $this->CI->input->post(self::$token_name) is FALSE, then the token was never posted.
  • If the token wasn’t posted or if it’s not equal to the one we generated, then deny the request with a “Bad Request” error.

Step 5 - Inject Tokens into the Views

This is the fun part! We need to inject the tokens in all forms. To make life easier for ourselves, we are going to place two meta tags within our <head> (Rails-like). That way, we can include the token in AJAX requests as well.

Append the following code to your csrf.php file:

/**
 * This injects hidden tags on all POST forms with the csrf token.
 * Also injects meta headers in <head> of output (if exists) for easy access
 * from JS frameworks.
 *
 * @return void
 * @author Ian Murray
 */
public function inject_tokens()
{
  $output = $this->CI->output->get_output();

  // Inject into form
  $output = preg_replace('/(<(form|FORM)[^>]*(method|METHOD)="(post|POST)"[^>]*>)/',
                         '$0<input type="hidden" name="' . self::$token_name . '" value="' . self::$token . '">', 
                         $output);

  // Inject into <head>
  $output = preg_replace('/(<\/head>)/',
                         '<meta name="csrf-name" content="' . self::$token_name . '">' . "\n" . '<meta name="csrf-token" content="' . self::$token . '">' . "\n" . '$0', 
                         $output);

  $this->CI->output->_display($output);
}
  • Since this is a display_override hook, we need to retrieve the generated output from CodeIgniter. We do this by using the $this->CI->output->get_output() method.
  • To inject the tags in our forms, we’ll use regular expressions. The expression ensures that we inject a hidden input tag (which contains our generated token) only into forms with a method of type POST.
  • We also need to inject our meta tags into the header (if present). This is simple, since the closing head tag should only be present once per file.
  • Lastly, since we’re using the display_override hook, the default method to display your view will not be called. This method includes all sorts of things, which we should not – just for the purposes of injecting some code. Calling it ourselves solves this.

Step 6 - Hooks

Last, but not least, we need to create the hooks themselves – so our methods get called. Paste the following code into your system/application/config/hooks.php file:

//
// CSRF Protection hooks, don't touch these unless you know what you're
// doing.
//
// THE ORDER OF THESE HOOKS IS EXTREMELY IMPORTANT!!
//

// THIS HAS TO GO FIRST IN THE post_controller_constructor HOOK LIST.
$hook['post_controller_constructor'][] = array( // Mind the "[]", this is not the only post_controller_constructor hook
  'class'    => 'CSRF_Protection',
  'function' => 'validate_tokens',
  'filename' => 'csrf.php',
  'filepath' => 'hooks'
);

// Generates the token (MUST HAPPEN AFTER THE VALIDATION HAS BEEN MADE, BUT BEFORE THE CONTROLLER
// IS EXECUTED, OTHERWISE USER HAS NO ACCESS TO A VALID TOKEN FOR CUSTOM FORMS).
$hook['post_controller_constructor'][] = array( // Mind the "[]", this is not the only post_controller_constructor hook
  'class'    => 'CSRF_Protection',
  'function' => 'generate_token',
  'filename' => 'csrf.php',
  'filepath' => 'hooks'
);

// This injects tokens on all forms
$hook['display_override'] = array(
  'class'    => 'CSRF_Protection',
  'function' => 'inject_tokens',
  'filename' => 'csrf.php',
  'filepath' => 'hooks'
);
  • The first hook calls the validate_tokens method. This is not the only post_controller_constructor hook, so we need to add those brackets (“[]“). Refer to the documentation on CodeIgniter hooks for more info.
  • The second hook, which is also a post_controller_constructor, generates the token in case it hasn’t been generated yet.
  • The third one is the display override. This hook will inject our tokens into the form and the header.

Wrapping Up

With minimal effort, we’ve built quite a nice library for ourselves.

You can use this library in any project, and it will automagically protect your site against CSRF.

If you’d like to contribute to this little project, please leave a comment below, or fork the project on GitHub. Alternatively, as of CodeIgniter v2.0, protection against CSRF attacks is now built into the framework!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Lucky Sebastian

    I think this is available in CI 2.0

  • http://ericlbarnes.com Eric Barnes

    I think it should be noted that CodeIgniter since v2 includes this already:
    http://codeigniter.com/user_guide/libraries/security.html

    • Joakim

      Thanks for pointing this out Eric. Saved me the trouble to doublecheck ;-)

    • http://radumicu.info/ RaduM

      My thoughts exactly, but nice article anyway, explaining the concept for the rest of us.

  • http://imetstuart.com Stuart Gibson

    You do realise the current version of CodeIgniter (2.0 and up) has native support for CSRF protection and is as simple as enabling the config variable and using the standard form_open() method to transparently enable it for all forms on the site?

    I think that is worth mentioning for anyone running the current (and recommended) version of the framework.

  • http://imetstuart.com Stuart Gibson

    You do realise the current version of CodeIgniter (2.0 and up) has native support for CSRF protection and is as simple as enabling the config variable and using the standard form_open() method to transparently enable it for all forms on the site?

    I think that is worth mentioning for anyone running the current (and recommended) version of the framework.

  • jas

    Nice article but I am not 100% certain pushing the token back to the client is a good idea.

    I would like to suggest and alternate solution and/or additional vector to help protect authenticated users from CSRF attacks.

    When generating your token include client specifics. Browser agent string, remote address and even page referrer data.

    These three identifiers will assist the server in validation of the authenticated user. For instance once the user has been authenticated create sha1 hashes of the remote ip, browser agent string, then create a base64 encoding of the referrer data. (We will need a way to get this later for comparison).

    Each time the user visits a page which should be protected and accessible by authenticated users you should compare the $_SERVER['REMOTE_ADDR'], $_SERVER['BROWSER_AGENT'] with the sha1 sha1 hashes stored in your authentication session token. Even going as far as ensuring the $_SERVER['HTTP_REFERRER'] closely matches (using regex patterns) the base64_decoded referrer data you stored previously.

    Have three points of client validation per page request will ensure the authenticated session token (which should match the PHPSESSID on the client) will remain valid for the original authenticated suer.

    Moving the default session handler away from the default temporary files will provide even more protection of the session tokens.

    • Fraz Ahmed

      When reading the article, I was thinking same that there should be more checks. If a hacker is intended to send malicious data to our website, it should not be difficult for him to take this tiny token from our website.

      But anyhow nice explanation of hooks. And anyone who is new to CI must read it for the better understanding of Hooks in CI.

  • Rafa

    Simpler: Update to Codeigniter 2.0

  • http://www.BlaineSch.com BlaineSch

    Great tutorial! However, were using two regular expressions to “inject” the token into the website. Since the controller has to validate the token anyways, the controller should also be giving the view the token as a variable, and the view just putting it in those two places. This way you don’t slow it down with two regular expressions on your entire source.

  • http://www.linkworks.cl Ian
    Author

    I should note that at the time of writing I wasn’t aware nor using CI 2.0, thus I created this library that automagically injects tokens. Given CI 2.0 has this protection already, this library loses some of its goodness.

    Cheers!

  • http://itcutives.com Jatin

    Nice Tutorial. I know that CSRF protection is already built-in since CI 2.0, but I would suggest PHP developers, who are not using CI or not familiar with CI should look at the code.

  • erminio ottone

    btw great tut :) thanks!
    Please more CI :)

  • http://www.paulund.co.uk Paul

    Hi thanks for the tutorial found it very interesting, just started using codeigniter so this information is very useful. Thanks.

  • http://developermadan.blogspot.com Madan Sapkota

    It is already implemented in CodeIgniter V2.0 or more on V2.0.2, No need to write custom code.

    // ./application/config/config.php
    $config['csrf_protection'] = TRUE;

    For V2.0 and V2.0.1

    e=”code” class=”php”>

    // ./application/views/any_form.php

    <input type="hidden" name="<?php echo $this->security->csrf_token_name; ?>" value="<?php echo $this->security->csrf_hash; ?>" />

    For V2.0.2 (changed)

    // ./application/views/any_form.php

    <input type="hidden" name="<?php echo $this->security->get_csrf_token_name(); ?>" value="<?php echo $this->security->get_csrf_hash(); ?>" />

    I don’t know this is secure or not. Hope this will help for lazy coders like me.

    Thanks,
    Madan Sapkota

  • Hosein

    What is it to stop a hacker to simply run the validate tokens function and then run the inject tokens one, and then finally run the validate tokens once more? This is useless then, no?

  • William Cosulich

    Hey, great tutorial. I was wondering if maybe for the next codeigniter tutorial you do a tutorial about using other databases other than Mysql. I am finding that they are many people who are having trouble with connecting to MS SQL Server or Oracle. There does not seem to be a lot of information on this topic. I have a PHP project I am working that will at some point require me to use a MS Sql Server database and I love the ease of use of codeigniter. I would hate to have to try another framework simply for the lack of information on this topic.

  • Mark

    Instead of this:
    <?php

    $output = preg_replace('/(]*(method|METHOD)=”(post|POST)”[^>]*>)/’,
    ‘$0′,
    $output);

    ?>

    Why not use this?
    <?php
    $output = preg_replace('/(]*method=”post”[^>]*>)/i’,
    ‘$0′,
    $output);

    ?>

    http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php
    “Pattern Modifiers

    The current possible PCRE modifiers are listed below. The names in parentheses refer to internal PCRE names for these modifiers. Spaces and newlines are ignored in modifiers, other characters cause error.

    i (PCRE_CASELESS)
    If this modifier is set, letters in the pattern match both upper and lower case letters. “

  • Mark

    Well, ignore my comment. It seems it thought I was putting some HTML tags into my post…

  • geo

    Thank you for the article! For older big CI projects, this really is awsome.

  • http://detik.hotthread.net DHT

    Nice libraries, now with CI 2 .. in built in..only change in config.php like this

    $config['csrf_protection'] = TRUE;

    Anyway thanks :)

  • http://www.facebook.com/luckymahrus Lucky Mahrus

    Hi,

    I use CodeIgniter v2.1.3 + IonAuth.. I set $config['csrf_protection'] = TRUE, and I use $this->data['csrf'] = $this->_get_csrf_nonce(); too..

    in auth/create_user controller, I create an ajax validation for email & username input.. after some modification, it successful to check email & username is already exist or not.. but after I submit the form, it show an error “This form post did not pass our security checks”..

    I found an article in http://www.web-and-development.com/5-reasons-to-not-use-csrf_protection-in-codeigniter/ which tell about “It is unstable for AJAX forms”..

    Is there any solution to use CI CSRF protection in ajax forms?

    Thanks,
    Lucky Mahrus