Safer web forms with security tokens

Posted on November 15, 2009

A common issue with many web applications is their vulnerability for Cross-Site Request Forgery, or XSRF. It allows a hacker to send a malicious request to a website with an other user’s privileges. Here’s how it works:

  • A hacker creates a page with a form that submits data to example.com.
  • An administrator from example.com is tricked into visiting the page, the form is submitted using JavaScript.
  • The data is handled by example.com as if it came from the administrator (because it did).

This allows a hacker to perform administrative tasks on example.com like editing pages or deleting users.

Solution

Probably the best solution to this problem is using a security token. This is a code (usually a hash) that is send with the form in a hidden field and is only valid for a specific user and a certain period of time.

I recommend using a SHA1 hash created from these components:

  • Information about the user (IP address, user agent, username).
  • Information about the server (hostname, software version).
  • Information about the website (database name, table prefix).
  • Information that expires (user’s session id).

This will result in an unpredictable and seemingly random hash that is still verifiable by the server and difficult to fake.

After the form is submitted the hash is re-created and compared to the token that was send with the form. Only if the hashes match the form is processed, otherwise an error message is displayed. This way even if a hacker manages to find the user’s token the request will fail.

Example (PHP/HTML):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
$authToken = sha1(
  session_id() .
  phpversion() .
  $dbName .
  $_SERVER['REMOTE_ADDR'] .
  $_SERVER['HTTP_USER_AGENT']
  );
 
if ( isset($_POST['submit']) )
{
  if ( isset($_POST['auth_token']) && $_POST['auth_token'] == $authToken )
  {
    // Process form
  }
  else
  {
    // Display error message
  }
}
?>
 
<form id="form" method="post" action="./">
  <fieldset>
    <label for="name">Name:</label>
    <input type="text" name="name" id="name" value=""/>
  </fieldset>
  <fieldset>
    <input type="hidden" name="auth_token" value="<?php echo $authToken ?>"/>
 
    <input type="submit" name="submit" value="Submit"/>
  </fieldset>
</form>

Note that this only really works for POST requests. For this reason GET (regular links) should never be used to pass information that is used for administrative tasks. AJAX requests should also always use POST.

Scroll to top

Comments (5)

  • Excellent and usefull tip, I'll adapt this one for http://www.phpbbantispam.com ( on which I allready have a set of randomized (hidden and non hidden) fields ).

    Posted by Ramon Fincken on November 16, 2009 Reply

  • Excellent and usefull tip, I'll adapt this one for http://www.phpbbantispam.com ( on which I allready have a set of randomized (hidden and non hidden) fields ).

    Posted by Ramon Fincken on November 16, 2009 Reply

  • If you're building it on phpBB something similar should already be in there, they use what they call “form tokens” and “confirm boxes” to prevent XSRF.

    Posted by ElbertF on November 16, 2009 Reply

  • If you're building it on phpBB something similar should already be in there, they use what they call “form tokens” and “confirm boxes” to prevent XSRF.

    Posted by ElbertF on November 16, 2009 Reply

  • Cheers for this. I has set up a script where it create a random hash tag and put it into a session variable. It was all set up the same as yours but everytime I navigated to the page it would re-send the form. (I got the code from an O'Reilly book aswell). This one works all fine though.

    Thanks mate.

    Posted by reececropley on March 15, 2010 Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

Fork me on GitHub