No-nonsense protection using a nonce

Protect your forms
UPDATE: A new and improved version of this extension can be found in my post Form protection revisited!

In a web application for record collectors I wrote a few years ago I recently had to deal with users who added the same record hundreds of times. I did use the PRG, or Post/Redirect/Get, pattern which means that you always redirect the user after a post request so that a simple reload won’t resubmit the same data. This prevents users from mistakenly resubmitting, but by pressing the back-button the problem re-emerges. The solution is called a nonce.

Actually, the use of a nonce also prevents an attack called CSRF, Cross-Site Request Forgery. Without going into detail, this means a malicious site can potentially perform CRUD operations with your credentials on a site you are currently logged in to. The idea to prevent this is to generate a nonce word that is sent with the form and stored at the server, making sure that the request is made using our form. This is basically what we wish to do:

  1. Generate nonce
  2. Save nonce in a session variable
  3. Add a hidden form field containing the nonce
  4. Validate data and nonce
  5. If nonce is valid, handle data and mark nonce as inactive

The key here is the validation of the nonce. There are two things to consider, one is that we should not allow the same form to be posted twice and the other is that the form must know what the current nonce is. This means for every request we need to have the current and, if it exists, old nonce saved in the user’s session. Also note I am assuming the sessions are stored in a database, since if they are not you probably have bigger problems.

To solve this problem I have extended the CodeIgniter Form Validation library so that it can handle this extra layer of security.

class MY_Form_Validation extends CI_Form_Validation {

	function __construct()
	{
		parent::__construct();
	}

    /**
     * Create a new unique nonce, save it to the current session and return it.
     *
     * @return string
     */
    function create_nonce()
    {
        $nonce = md5('nonce' . $this->CI->input->ip_address() . microtime());
        $this->CI->session->set_userdata('nonce', $nonce);
        return $nonce;
    }

    /**
     * Mark the nonce sent from the form as already used.
     */
    function save_nonce()
    {
        $this->CI->session->set_userdata('old_nonce', $this->set_value('nonce'));
    }

    /**
     * Set form validation rules for the nonce.
     */
    function nonce()
    {
        $this->set_rules('nonce', 'Nonce', 'required|check_nonce');
    }

 	/**
	 * Validation rule for making sure the nonce is valid.
	 *
	 * @access	public
	 * @param	string
     * @param	last used nonce
	 * @return	bool
	 */
	function check_nonce($str)
	{
        return ($str == $this->CI->session->userdata('nonce') &&
                $str != $this->CI->session->userdata('old_nonce'));
	}

}

Also, I extended the form helper to include a function for generating a new hidden form field with a unique nonce:

/**
 * Generates a hidden field containing a nonce.
 *
 * @access	public
 * @return	string
 */
if ( ! function_exists('form_nonce'))
{
	function form_nonce()
	{
        $CI =& get_instance();
        $CI->load->library('form_validation');
		$field = '<input type="hidden" name="nonce" value="'
            . $CI->form_validation->set_value('nonce', $CI->form_validation->create_nonce())
            . '" />';
        return $field;
	}
}

Last but not least I’ll show you how to use it in a controller method, in this case called add.

	function add()
	{
		$this->load->library('form_validation');
		// Set validation rules here..
        $this->form_validation->nonce();

		if ($this->form_validation->run() !== FALSE) { // If validation has completed
            $this->form_validation->save_nonce();
			// Handle the validated post request
			redirect('controller/method');
		}
	}

This is not a subject I’d consider myself to be an expert in, so any input on the validity of this library is greatly appreciated. This is something that might very well get included in future versions of CodeIgniter, and it should, but until then I hope this is a good alternative.

Image from the Open Clip Art Library.

  • http://zackhovatter.com Zack Hovatter

    Seems useful, but couldn't the website that is trying to perform the CSRF just scrape the nonce from the hidden form input and use it in their own request? Sure it would be a deterrent, but not a complete prevention. However, it does seem very great for making sure things aren't re-posted.

  • erikbrannstrom

    That's a good idea, but to scrape the form they would need to make a GET request for it, and since a new nonce word is generated upon each new request the malicious site would receive a different nonce. Since the scraped nonce word is not stored in the user's session, any form requests using this will be invalid.

  • http://real-url.org/twitted.php?id=16973645874 Twitted by codeignitee

    [...] This post was Twitted by codeignitee [...]

  • Anonymous

    Hey Phil,

    Yup, I was just in a hurry to get it done so it didn’t occur to me at the time. I actually meant to do a follow-up post on that, and the idea is exactly as you say; running the parent method and save the nonce if all goes well.

    I’ve noticed CI 2.0 have added some CSRF protection into the Security library which could probably be used if one wishes, but it still seems there’s no native integration with the form validation unfortunately.

  • http://twitter.com/philsturgeon Philip Sturgeon

    Hey, this looks great and is exactly the sort of thing we should add into PyroCMS for extra security against CSRF and generally to stop fools reposting.

    Could this be made even easier to implement by creating the nonce in the Form_validation constructor and saving in run if it's true (or saving either way)? Maybe I am missing something.

  • erikbrannstrom

    Hey Phil,

    Yup, I was just in a hurry to get it done so it didn't occur to me at the time. I actually meant to do a follow-up post on that, and the idea is exactly as you say; running the parent method and save the nonce if all goes well.

    I've noticed CI 2.0 have added some CSRF protection into the Security library which could probably be used if one wishes, but it still seems there's no native integration with the form validation unfortunately.

blog comments powered by Disqus