Secure Your Forms With Form Keys
Security is a hot topic. Ensuring that your websites are secure is extremely important for any web application. In fact, I spend 70% of my time securing my applications. One of the most important things we must secure are forms. Today, we are going to review a method to prevent XSS (Cross-site scripting) and Cross-site request forgery on forms.
Why?
POST data can be sent from one website to another. Why is this bad? A simple scenario...
A user, logged into your website, visits another website during his session. This website will be able to send POST data to your website -- for example, with AJAX. Because the user is logged in on your site, the other website will also be able to send post data to secured forms that are only accessible after a login.
We also must protect our pages against attacks using cURL
How Do We Fix This?
With form keys! We'll add a special hash (a form key) to every form to make sure that the data will only be processed when it has been sent from your website. After a form submit, our PHP script will validate the submitted form key against the form key we've set in a session.
What We Must Do:
- Add a form key to every form.
- Store the form key in a session.
- Validate the form key after a form submit.
Step 1: A Simple Form
First we need a simple form for demonstration purposes. One of the most important forms we have to secure is the login form. The login form is vulnerable tobrute force attacks. Create a new file, and save it as index.php in your web-root. Add the following code within the body:
1 |
|
2 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3 |
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
4 |
<head>
|
5 |
<meta http-equiv="content-type" content="text/html;charset=UTF-8" /> |
6 |
<title>Securing forms with form keys</title> |
7 |
</head>
|
8 |
<body>
|
9 |
<form action="" method="post"> |
10 |
<dl>
|
11 |
<dt><label for="username">Username:</label></dt> |
12 |
<dd><input type="text" name="username" id="username" /></dd> |
13 |
<dt><label for="username">Password:</label></dt> |
14 |
<dd><input type="password" name="password" id="password" /></dd> |
15 |
<dt></dt>
|
16 |
<dd><input type="submit" value="Login" /></dd> |
17 |
</dl>
|
18 |
</form>
|
19 |
</body>
|
20 |
</html>
|
Now we have a simple XHTML page with a login form. If you want to use form keys on your website, you can replace the script above with your own login page. Now, let's continue to the real action.
Step 2: Creating a Class
We are going to create a PHP class for our form keys. Because every page can contain only one form key, we could make a singleton of our class to make sure that our class is used correctly. Because creating singletons is a more advanced OOP topic, we will skip that part. Create a new file called formkey.class.php and place it in your web-root. Now we have to think about the functions we need. First, we need a function to generate a form key so we can place it in our form. In your PHP file place the following code:
1 |
|
2 |
<?php
|
3 |
|
4 |
//You can of course choose any name for your class or integrate it in something like a functions or base class
|
5 |
class formKey |
6 |
{
|
7 |
//Here we store the generated form key
|
8 |
private $formKey; |
9 |
|
10 |
//Here we store the old form key (more info at step 4)
|
11 |
private $old_formKey; |
12 |
|
13 |
//Function to generate the form key
|
14 |
private function generateKey() |
15 |
{
|
16 |
|
17 |
}
|
18 |
}
|
19 |
?>
|
Above, you see a class with three parts: two variables and a function. We make the function private because this function will only be used by our outputfunctions, which we will create later. In the two variables, we will store the form keys. These are also private because they may only be used by functions inside our class.
Now, we have to think of a way to generate our form key. Because our form key must be unique (otherwise we don't have any security), we use a combination of the users IP-address to bind the key to a user, mt_rand() to make it unique, and the uniqid() function to make it even more unique. We also encrypt this information with md5() to create a unique hash which we can then insert into our pages. Because we used md5(), a user cannot see what we used to generate the key. The whole function:
1 |
|
2 |
//Function to generate the form key
|
3 |
private function generateKey() |
4 |
{
|
5 |
//Get the IP-address of the user
|
6 |
$ip = $_SERVER['REMOTE_ADDR']; |
7 |
|
8 |
//We use mt_rand() instead of rand() because it is better for generating random numbers.
|
9 |
//We use 'true' to get a longer string.
|
10 |
//See http://www.php.net/mt_rand for a precise description of the function and more examples.
|
11 |
$uniqid = uniqid(mt_rand(), true); |
12 |
|
13 |
//Return the hash
|
14 |
return md5($ip . $uniqid); |
15 |
}
|
Insert the code above into your formkey.class.php file. Replace the function with the new function.
Step 3: Inserting a Form Key into Our Form
For this step, we create a new function that outputs a hidden HTML field with our form key. The function consists of three steps:
- Generate a form key with our generateKey() function.
- Store the form key in our $formKey variable and in a session.
- Output the HTML field.
We name our function outputKey() and make it public, because we have to use it outside of our class. Our function will call the private function generateKey() to generate a new form key and save it locally in a session. Lastly, we create the XHTML code. Now add the following code inside our PHP class:
1 |
|
2 |
//Function to output the form key
|
3 |
public function outputKey() |
4 |
{
|
5 |
//Generate the key and store it inside the class
|
6 |
$this->formKey = $this->generateKey(); |
7 |
//Store the form key in the session
|
8 |
$_SESSION['form_key'] = $this->formKey; |
9 |
|
10 |
//Output the form key
|
11 |
echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />"; |
12 |
}
|
Now, we are going to add the form key to our login form to secure it. We have to include the class in our index.php file. We also have to start the session because our class uses sessions to store the generated key. For this, we add the following code above the doctype and head tag:
1 |
|
2 |
<?php
|
3 |
//Start the session
|
4 |
session_start(); |
5 |
//Require the class
|
6 |
require('formkey.class.php'); |
7 |
//Start the class
|
8 |
$formKey = new formKey(); |
9 |
?>
|
The code above is pretty self-explanatory. We start the session (because we store the form key) and load the PHP class file. After that, we start the class with new formKey(), this will create our class and store it in $formKey. Now we only have to edit our form so that it contains the form key:
1 |
|
2 |
<form action="" method="post"> |
3 |
<dl>
|
4 |
<?php $formKey->outputKey(); ?>
|
5 |
<dt><label for="username">Username:</label></dt> |
6 |
<dd><input type="text" name="username" id="username" /></dd> |
7 |
<dt><label for="username">Password:</label></dt> |
8 |
<dd>input type="password" name="password" id="password" /></dd> |
9 |
<dl>
|
10 |
</form>
|
And that's all! Because we created the function outputKey(), we only have to include it in our form. We can use form keys in every form by just adding <?php $formKey->outputKey(); ?> Now just review the source of your webpage and you can see that there is a form key attached to the form. The only remaining step is to validate requests.
Step 4: Validating
We won't validate the whole form; only the form key. Validating the form is basic PHP and tutorials can be found all over the web. Let's validate the form key. Because our "generateKey" function overwrites the session value, we add a constructor to our PHP class. A constructor will be called when our class is created (or constructed). The constructor will store the previous key inside the class before we create a new one; so we'll always have the previous form key for validating our form. If we didn't do this, we wouldn't be able to validate the form key. Add the following PHP function to your class:
1 |
|
2 |
//The constructor stores the form key (if one exists) in our class variable.
|
3 |
function __construct() |
4 |
{
|
5 |
//We need the previous key so we store it
|
6 |
if(isset($_SESSION['form_key'])) |
7 |
{
|
8 |
$this->old_formKey = $_SESSION['form_key']; |
9 |
}
|
10 |
}
|
A constructor should always be named __construct(). When the constructor is called we check if a session is set, and if so, we store it locally in our old_formKey variable.
Now we are able to validate our form key. We create a basic function inside our class which validates the form key. This function should also be public because we are going to use it outside our class. The function will validate the POST value of the form key against the stored value of the form key. Add this function to the PHP class:
1 |
|
2 |
//Function that validated the form key POST data
|
3 |
public function validate() |
4 |
{
|
5 |
//We use the old formKey and not the new generated version
|
6 |
if($_POST['form_key'] == $this->old_formKey) |
7 |
{
|
8 |
//The key is valid, return true.
|
9 |
return true; |
10 |
}
|
11 |
else
|
12 |
{
|
13 |
//The key is invalid, return false.
|
14 |
return false; |
15 |
}
|
16 |
}
|
Within index.php, we validate the form key by using the function we just created in our class. Of course, we only validate after a POST request. Add the following code after $formKey = new formKey();
1 |
|
2 |
$error = 'No error'; |
3 |
|
4 |
//Is request?
|
5 |
if($_SERVER['REQUEST_METHOD'] == 'post') |
6 |
{
|
7 |
//Validate the form key
|
8 |
if(!isset($_POST['form_key']) || !$formKey->validate()) |
9 |
{
|
10 |
//Form key is invalid, show an error
|
11 |
$error = 'Form key error!'; |
12 |
}
|
13 |
else
|
14 |
{
|
15 |
//Do the rest of your validation here
|
16 |
$error = 'No form key error!'; |
17 |
}
|
18 |
}
|
We created a variable $error which stores our error message. If a POST request has been sent we validate our formkey with $formKey->validate(). If this returns false, the form key is invalid and we display an error. Note that we only validate the form key -- you're expected to validate the rest of the form yourself.
In your HTML, you can place the following code to show the error message:
1 |
|
2 |
<div><?php if($error) { echo($error); } ?></div> |
This will echo the $error variable if it is set.



If you start your server and go to index.php, you will see our form and the message 'No error'. When you submit a form you will see the message 'No form key error' because it is an valid POST request. Now try to reload the page and accept when your browser requests that the POST data be sent again. You will see that our script triggers an error message: 'Form key error!' Your form is now protected against input from other websites and errors with page reloads! The error is also shown after a refresh because a new form key was generated after we submitted the form. This is good because, now, user can't accidentally post a form twice.
Full Code
Here is the whole PHP and HTML code:
index.php
1 |
|
2 |
<?php
|
3 |
//Start the session
|
4 |
session_start(); |
5 |
//Require the class
|
6 |
require('formkey.class.php'); |
7 |
//Start the class
|
8 |
$formKey = new formKey(); |
9 |
|
10 |
$error = 'No error'; |
11 |
|
12 |
//Is request?
|
13 |
if($_SERVER['REQUEST_METHOD'] == 'post') |
14 |
{
|
15 |
//Validate the form key
|
16 |
if(!isset($_POST['form_key']) || !$formKey->validate()) |
17 |
{
|
18 |
//Form key is invalid, show an error
|
19 |
$error = 'Form key error!'; |
20 |
}
|
21 |
else
|
22 |
{
|
23 |
//Do the rest of your validation here
|
24 |
$error = 'No form key error!'; |
25 |
}
|
26 |
}
|
27 |
?>
|
28 |
|
29 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
30 |
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
31 |
<head>
|
32 |
<meta http-equiv="content-type" content="text/html;charset=UTF-8" /> |
33 |
<title>Securing forms with form keys</title> |
34 |
</head>
|
35 |
<body>
|
36 |
<div><?php if($error) { echo($error); } ?> |
37 |
<form action="" method="post"> |
38 |
<dl>
|
39 |
<?php $formKey->outputKey(); ?> |
40 |
|
41 |
<dt><label for="username">Username:</label></dt> |
42 |
<dd><input type="text" name="username" id="username" /></dd> |
43 |
<dt><label for="username">Password:</label></dt> |
44 |
<dd><input type="password" name="password" id="password" /></dd> |
45 |
<dt></dt>
|
46 |
<dd><input type="submit" value="Submit" /></dd> |
47 |
<dl>
|
48 |
</form>
|
49 |
</body>
|
50 |
</html>
|
fomrkey.class.php
1 |
|
2 |
<?php
|
3 |
|
4 |
//You can of course choose any name for your class or integrate it in something like a functions or base class
|
5 |
class formKey |
6 |
{
|
7 |
//Here we store the generated form key
|
8 |
private $formKey; |
9 |
|
10 |
//Here we store the old form key (more info at step 4)
|
11 |
private $old_formKey; |
12 |
|
13 |
//The constructor stores the form key (if one excists) in our class variable
|
14 |
function __construct() |
15 |
{
|
16 |
//We need the previous key so we store it
|
17 |
if(isset($_SESSION['form_key'])) |
18 |
{
|
19 |
$this->old_formKey = $_SESSION['form_key']; |
20 |
}
|
21 |
}
|
22 |
|
23 |
//Function to generate the form key
|
24 |
private function generateKey() |
25 |
{
|
26 |
//Get the IP-address of the user
|
27 |
$ip = $_SERVER['REMOTE_ADDR']; |
28 |
|
29 |
//We use mt_rand() instead of rand() because it is better for generating random numbers.
|
30 |
//We use 'true' to get a longer string.
|
31 |
//See http://www.php.net/mt_rand for a precise description of the function and more examples.
|
32 |
$uniqid = uniqid(mt_rand(), true); |
33 |
|
34 |
//Return the hash
|
35 |
return md5($ip . $uniqid); |
36 |
}
|
37 |
|
38 |
|
39 |
//Function to output the form key
|
40 |
public function outputKey() |
41 |
{
|
42 |
//Generate the key and store it inside the class
|
43 |
$this->formKey = $this->generateKey(); |
44 |
//Store the form key in the session
|
45 |
$_SESSION['form_key'] = $this->formKey; |
46 |
|
47 |
//Output the form key
|
48 |
echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />"; |
49 |
}
|
50 |
|
51 |
|
52 |
//Function that validated the form key POST data
|
53 |
public function validate() |
54 |
{
|
55 |
//We use the old formKey and not the new generated version
|
56 |
if($_POST['form_key'] == $this->old_formKey) |
57 |
{
|
58 |
//The key is valid, return true.
|
59 |
return true; |
60 |
}
|
61 |
else
|
62 |
{
|
63 |
//The key is invalid, return false.
|
64 |
return false; |
65 |
}
|
66 |
}
|
67 |
}
|
68 |
?>
|
Conclusion
Adding this code to every important form on your website will increase your form's security dramatically. It even stops refreshing issues, as we saw in step 4. Because the form key is only valid for one request, a double post is not possible.
This was my first tutorial, I hope you like it and use it to improve your security! Please let me know your thoughts, via the comments. Have a better method? Let us know.
Further Reading
- WordPress also uses form keys (naming it Nonces): Wordpress Nonces
- Seven habits for writing secure PHP applications
- Follow us on Twitter, or subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.