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:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>Securing forms with form keys</title> </head> <body> <form action="" method="post"> <dl> <dt><label for="username">Username:</label></dt> <dd><input type="text" name="username" id="username" /></dd> <dt><label for="username">Password:</label></dt> <dd><input type="password" name="password" id="password" /></dd> <dt></dt> <dd><input type="submit" value="Login" /></dd> </dl> </form> </body> </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:
<?php
//You can of course choose any name for your class or integrate it in something like a functions or base class
class formKey
{
//Here we store the generated form key
private $formKey;
//Here we store the old form key (more info at step 4)
private $old_formKey;
//Function to generate the form key
private function generateKey()
{
}
}
?>
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:
//Function to generate the form key
private function generateKey()
{
//Get the IP-address of the user
$ip = $_SERVER['REMOTE_ADDR'];
//We use mt_rand() instead of rand() because it is better for generating random numbers.
//We use 'true' to get a longer string.
//See http://www.php.net/mt_rand for a precise description of the function and more examples.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
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:
//Function to output the form key
public function outputKey()
{
//Generate the key and store it inside the class
$this->formKey = $this->generateKey();
//Store the form key in the session
$_SESSION['form_key'] = $this->formKey;
//Output the form key
echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
}
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:
<?php
//Start the session
session_start();
//Require the class
require('formkey.class.php');
//Start the class
$formKey = new formKey();
?>
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:
<form action="" method="post"> <dl> <?php $formKey->outputKey(); ?> <dt><label for="username">Username:</label></dt> <dd><input type="text" name="username" id="username" /></dd> <dt><label for="username">Password:</label></dt> <dd>input type="password" name="password" id="password" /></dd> <dl> </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:
//The constructor stores the form key (if one exists) in our class variable.
function __construct()
{
//We need the previous key so we store it
if(isset($_SESSION['form_key']))
{
$this->old_formKey = $_SESSION['form_key'];
}
}
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:
//Function that validated the form key POST data
public function validate()
{
//We use the old formKey and not the new generated version
if($_POST['form_key'] == $this->old_formKey)
{
//The key is valid, return true.
return true;
}
else
{
//The key is invalid, return false.
return false;
}
}
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();
$error = 'No error';
//Is request?
if($_SERVER['REQUEST_METHOD'] == 'post')
{
//Validate the form key
if(!isset($_POST['form_key']) || !$formKey->validate())
{
//Form key is invalid, show an error
$error = 'Form key error!';
}
else
{
//Do the rest of your validation here
$error = 'No form key error!';
}
}
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:
<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
<?php
//Start the session
session_start();
//Require the class
require('formkey.class.php');
//Start the class
$formKey = new formKey();
$error = 'No error';
//Is request?
if($_SERVER['REQUEST_METHOD'] == 'post')
{
//Validate the form key
if(!isset($_POST['form_key']) || !$formKey->validate())
{
//Form key is invalid, show an error
$error = 'Form key error!';
}
else
{
//Do the rest of your validation here
$error = 'No form key error!';
}
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Securing forms with form keys</title>
</head>
<body>
<div><?php if($error) { echo($error); } ?>
<form action="" method="post">
<dl>
<?php $formKey->outputKey(); ?>
<dt><label for="username">Username:</label></dt>
<dd><input type="text" name="username" id="username" /></dd>
<dt><label for="username">Password:</label></dt>
<dd><input type="password" name="password" id="password" /></dd>
<dt></dt>
<dd><input type="submit" value="Submit" /></dd>
<dl>
</form>
</body>
</html>
fomrkey.class.php
<?php
//You can of course choose any name for your class or integrate it in something like a functions or base class
class formKey
{
//Here we store the generated form key
private $formKey;
//Here we store the old form key (more info at step 4)
private $old_formKey;
//The constructor stores the form key (if one excists) in our class variable
function __construct()
{
//We need the previous key so we store it
if(isset($_SESSION['form_key']))
{
$this->old_formKey = $_SESSION['form_key'];
}
}
//Function to generate the form key
private function generateKey()
{
//Get the IP-address of the user
$ip = $_SERVER['REMOTE_ADDR'];
//We use mt_rand() instead of rand() because it is better for generating random numbers.
//We use 'true' to get a longer string.
//See http://www.php.net/mt_rand for a precise description of the function and more examples.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
//Function to output the form key
public function outputKey()
{
//Generate the key and store it inside the class
$this->formKey = $this->generateKey();
//Store the form key in the session
$_SESSION['form_key'] = $this->formKey;
//Output the form key
echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
}
//Function that validated the form key POST data
public function validate()
{
//We use the old formKey and not the new generated version
if($_POST['form_key'] == $this->old_formKey)
{
//The key is valid, return true.
return true;
}
else
{
//The key is invalid, return false.
return false;
}
}
}
?>
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.
Related Posts
Check out some more great tutorials and articles that you might like
Plus Members
Source Files, Bonus Tutorials and
More for $9 a month for all TUTS+
sites in one subscription.










User Comments
( ADD YOURS )Myfacefriends June 12th
Thanks for another wonderful tuts!
( )Jeffrey Way June 12th
Thanks for reading — after thirty seconds of being posted.
( )Merxhan June 12th
hahahah I wanna read that fast, and digest it
Kiran June 12th
Another great article from nettuts!
( )Myfacefriends June 12th
I’m so much addicted to your site Jeff, your site is great. hope someday theres a smarty tutorial here. thanks
( )Morten Najbjerg June 13th
You mean like this
http://blog.themeforest.net/tutorials/smarty/
Not from Nettuts though. The tutorial is from Envatos ThemeForest…
( )Jehnee June 13th
Thanks Morten for this link..
Rene June 12th
great tutorial, really awesome and helpful
( )James June 12th
“This website will be able to send POST data to your website — for example, with AJAX” – how, exactly? I’m pretty sure the same-domain-policy would forbid this.
( )Mikael June 12th
True.
( )But all it takes is a regular form on the foreign website to POST to yours.
As long as the user is logged into your website, bam, vulnerable !
Tom June 12th
doesn’t authentication via ajax always somewhere include a hash of some sort? How else would you authenticate the user when he/she submits data via ajax?
Adam June 12th
Seems to me this is a good example about OO PHP. But for security, what does ‘POST data can be sent from one website to another’ really mean? Can anyone have a better explanation?
Adam June 12th
Never mind. Maybe the author was just talking about the a regular form (non-ajax).
Thomas Milburn June 13th
I think the author was talking about how you can send POST data cross domain using an iframe and javascript. Of course this is not Ajax but probably the classic case of mixing up Ajax and Javascript. You can create a hidden iframe which has a form on it with action=”www.otherdomain.com/post.php” and use javascript to submit that POST form.
The user will be unaware that data has been submitted to another site. Of course in keeping with the cross domain policy, javascript won’t be able to read the result of this form submission.
Randy June 12th
This is so great to see so much about php security here! People talk about how “easy” php is to learn, but many fail to focus on securing their site properly. There can not be too many articles on this IMO. Nice job Wouter.
( )Bryan P June 12th
A great post to increase my form security. I think its interesting because I have recently been examining my security processes.
( )keithnorm June 12th
Thank you for an explanation of a nice clean way to handle this. Very helpful.
( )Daniel June 12th
I can’t understand where is the security point, sorry.
( )Why attackers couldn’t read generated HTML code for form, copy the key value, and send it from another server with all the other variables on a POST?
Gerry June 14th
Exactly what I was thinking
( )Gerry June 14th
Actually this won’t work as the attacker can only send data to the logged in session, it can’t receive it. Or I think so anyway.
Ely June 28th
Because the ip address would be different, in addition this could be hardened with an appened timestamp. Remeber this is all stored in the session variable,
( )which if secured correctly would not allow some to copy paste the values and run from another server (the receiving script would either redirect or throw an error upon receipt of invalid key/token/capthca etc..
Seed June 12th
I always have “No errors” message, even if I refresh the site :/
( )ananda rudra September 13th
//Is request?
if($_SERVER['REQUEST_METHOD'] == ‘post’)
CHANGE it to
//Is request?
( )if($_SERVER['REQUEST_METHOD'] == ‘POST’)
Shaun June 12th
Awesome Tutorial! Would be great to get more tutorials on Secuirty!
( )Greg June 12th
This is great stuff for a php newbie like me. Very much appreciated.
( )cheers
Bennie Mosher June 12th
Awesome post Jeff. This will come in handy one day.
One question I have though, does anyone know if Wordpress is using some kind of secure form publishing technique for their log-in forms? I would think that they are, but can’t seem to find where it would be doing it at. Thanks for the heads up in advance!
( )Wouter Bulten June 12th
See the end of my tut:
Further Reading
( )- WordPress also uses form keys (naming it Nonces): Wordpress Nonces
- Seven habits for writing secure PHP applications
John June 12th
great tutorial…
one small typo – in the Full Code section you have fomrkey.class.php – it should be formkey.class.php
Again… GREAT STUFF… look forward to your future posts
( )B. Ackles June 12th
I saw that to. I also noticed that the input tag on Step 3, snipit 3 is missing an opening bracket. Simple typo.
Great tutorial. Extremely valuable to any beginners to intermediates php developers.
( )Ben June 12th
This is a great write up. Thanks. It’s going to be a great help with some of the projects I’m currently working on.
( )Nathan June 12th
Great post, but one warning. You’ll run into problems if you use a user’s ip address to uniquely identify them. When a user’s request originates behind a corporate proxy or a proxy farm, their ip address is sure to change between requests (thus their token).
This technique is fine for simple forms, but if you were to roll this out into a large registration system, you’d be hitting your head against the wall trying to find out why 5% of your user’s cannot use your application.
( )Thomas Milburn June 13th
Actually, this script doesn’t check the IP address again when validating so this wouldn’t be an issue. Which of course raises the point of why put the IP address into the hash?
Quote: “we use a combination of the users IP-address to bind the key to a user” is blatantly not true.
( )Nathan June 16th
Gotcha, I didn’t verify that it was used on form validation, only the line you quoted.
Philo June 12th
Great tutorial Wouter! ( Goede tutorial!
)
( )Crysfel June 12th
Thank you! i enjoy this one
( )Wouter Bulten June 12th
Thanks for your great responses!
@James
“This website will be able to send POST data to your website — for example, with AJAX”
It is possible if you use iframes and send POST data with AJAX to the iframe. (I read an article about it, not tested it myself)
( )Santana June 12th
Nice!!! Thank u!! Great Stuff
( )John Girvin June 12th
Good tutorial on defending against some cross site request forgery (CSRF) attacks. Chris Shifflet wrote about this back in 2004 here: http://shiflett.org/articles/cross-site-request-forgeries
For the “form key” – aka “nonce” or “number used once” – a random string works just as well as anything else you can hash (hash, not encrypt!) together with md5(). The important points are that the value is unique and non-predictable.
You should also consider checking IP address and user agent strings to see if they have changed from previous requests. This isn’t foolproof as these values can change anyway, but taken together they can mark a request as “suspicious” and prompt a graduated response from your application if the form is sensitive. For example, you can re-ask for the user’s password or CAPTCHA.
( )Ange Chierchia June 12th
Great tuts, your post is very interesting I think I should use your method in my on going project security in e-commerce websites is the most important thing.
Thanks!
( )Matthijn June 12th
Well, you say it is ‘not wanted’ being able to post from other sites with functions like curl, but if you just have good validations and filtering on all the values, what can go wrong then?
( )Matthijn June 12th
Can’t seem to edit my last post.
But even with this method you can’t be secure from ‘posting from another script’. Because you could get all the values needed via Zend_Http_Client, read that out, and repost those (and own) values via Zend_Http_Client back, and your script wouldn’t know the difference.
( )mefisto June 12th
The result is NOT a valid XHTML !
( )Its just wrong.
OSX Boy June 12th
Nice tutorial now all we need is to extend the class to eiminate non-human form fillers! We get lots of bots and other than adding a capatcha type test it’s tough to avoid automated form submissions.
( )Vitaly Babiy June 12th
Great tip, alot of frameworks do this for your like, Rails or Django. Not sure in the PHP world.
( )Merxhan June 12th
Great tutorial, easy to understand and not so much code.
( )I don’t have so much knowledge on security issues but these seems inspiring
Thanks
Csaba June 12th
Well written tutorial Wouter. Easy to follow.
( )Jobe June 12th
Nice work, this is very helpful.
( )OSX Boy June 12th
One thing I noticed is that:
if($_SERVER['REQUEST_METHOD'] == ‘post’) {
needs to be:
if($_SERVER['REQUEST_METHOD'] == ‘POST’) {
This may be our server setup or a genic thing i’m not sure as I user
( )‘ method=”post” ‘ in my form tag
Nuclear Gorilla August 11th
Thanks, this was my issue
( )joe June 12th
Already do it. Thanks, keeping going!
( )Alejandro June 12th
Great tutorial! A short question.. would this solution work with two (protected) forms on the same page?
( )jonathan June 25th
It would only work with two forms on the same page if they both use the same form key. It also wouldn’t work if the user has two forms loaded up in two different tabs or windows and the user tries to submit the older form. Since $_SESSION['form_key'] is overwritten every time a new form key is generated, it is good for one form or page only. A way around this would be to insert the form_key into a database and look it up based on the user’s session id. Or you could set $_SESSION['form_key'] = array(form_key1,form_key2….), adding each form_key to the array as it is generated and search the array when a form is posted for the submitted form_key.
( )Ryan S June 12th
For years I have used ASP. Over the past few months of reading Nettuts, I think I’m finding PHP to be much easier and more available code and information. Is there more information and users coding with PHP, over ASP and others?
Thanks
( )Jeffrey Way June 12th
Are you referring to ASP or ASP.NET 3 — big difference between the two. ASP.NET is very, very powerful.
( )Andy H June 12th
I thought the tutorial was well written and easy to follow. Good use of OO concepts and a very great security layer to implement.
( )Arne June 12th
This article is nice and really shows what popular CMS do nowadys to secure their forms.
But in fact this measure of security is just to protect your form against real cheapstakes.
You can’t kill a real cURL form spoofer with this one, cause cURL is capable of sending and parsing cookies.
Infact you have two do 2 requests with cURL, but if you really want to do this it is not much of a problem.
A better way would be using captures or generate the form-key via Ajax and put it in the value field.
( )cURL is not capable of rendering JS, so if mystify your JS code so much that is is to hard to read no one will be willing to hijack the form.
K. N. June 12th
This method actually helps you against CSRF, but I dont get the point how it helps against XSS flaws. By the way: If your site has XSS holes you dont need to secure you against CSRF, because then these protections are pretty pointless because you could grab the generated hash via javascript
( )Thomas Milburn June 13th
You are absolutely right. I’m not sure the author really understands the security issue or at least hasn’t explained it very well.
( )Some Macedonian August 20th
you will need to do input filtering in order to protect against XSS attacks, while you are right about this tutorial, it only helps against CSRF attacks. CodeIgniter has a good class for input filtering, the xss_clean method does lots of checking before it returns the valid input.
( )Lau June 12th
Useful info.
( )redwall_hp June 12th
Very nice tutorial.
I think it would be nice to have a series of tutorials showing the basics of how certain exploits work, and how to counter them. Just a thought.
( )Thomas June 15th
What would you like to see covered? I might write one…
( )Les June 12th
I have found that with a single key preserved for verification of the key held in the form is not enough in the event of a redirect, where you submit a form and redirect to a new page and you still require to validate a key.
In such a situ your key won’t validate, so what I do is to keep the last three generated keys in an array instead, and compare a key coming in from a form submission, against those three held in the array.
This works better and it doesn’t impede any security measures.
( )Les June 12th
> because you could grab the generated hash via javascript
Doesn’t matter, and that is the point really…
No matter what you do with it on the client side, if it ain’t on the server side your goose is well cooked; this approach is basically to prevent a) automated machines taking advantage, and b) to prevent a previously saved page on someone’s desktop being used instead of the live page, for two examples.
If you are going to make a wide, sweeping statement such as this, at least know a bit more about what you are talking about aye?
( )K. N. June 13th
Automated machines could not open the page, generate a hash and submit them?
( )Ruben June 13th
I can see problems with tabbed browsing: imagine a user needs to use a form (not a login form obviously) multiple times and opens it in multiple tabs.
Only the last form will be submitted successfully, and only if the user submits it first.
So you are assuming here that the user will use your site the way you intended, which is not that realistic.
Solutions:
( )* Store last N keys instead of just 1
* Encrypt the request time + a session specific value and use that value as a key. On submit, decrypt the key and check the difference between request time and current time.
Arun June 13th
Hi
May I know how different is this approach from the captcha?
with captcha the generate code / key is checked with the user input. Here instead of the user input, we have it in the hidden element.
Please correct if I’m wrong.
( )david June 13th
I don’t see why you should make a class for this? The workhorses of the code are the formfield and the session key/value pair. So all you have to do is create the formkey, add it to the session and a hidden input field.
Most of the class code just restricts developers.
( )Steven June 13th
Usually I don’t comment, but this is a really wonderful high quality tut. Wonderful job! More php tuts about security would be wonderful.
( )Brad June 13th
This is an excellent turtorial, perhaps one of the most pertinent I have read on form security. Thank you!
( )Polo June 13th
Hey Thomas Milburn and K.N, why do you write a better tutorial on security since you guys seems to be very knowledgeable.
This tutorial covers the basic security concerns for newbies but you keep bashing on the author, i mean everybody has the right to raise their opinions but at least be cool enough to provide better information if you feel that this tutorial is missing something, just my 2 cents.
Cool tutorial by the way.
( )James Hogan June 13th
synchronized token pattern. i love this site. thank you
( )iPad June 13th
Look at this form http://www.romancortes.com/contact/ I just love it, this is Roman Cortes the ‘Homer CSS’ Author
( )Hash June 14th
Good
( )João Pedro Pereira June 14th
Good tutorial
. That’s an easy way that I’ve already used in two projects to not use captcha’s.
( )samiul June 14th
Hi I want to see a scrrencasts of how to create a login form using facebox?
( )Thomas June 15th
Not sure this would quite make a tutorial….
( )Tanax June 14th
Why did you use visibility scopes for all functions except the constructor?
( )Wouter Bulten June 14th
First answer (and most simple): I forgot it.
Second: It is not really needed. Only when I create singletons or private classes I make them private.
( )Tanax June 15th
Point 1: Understandable!
Point 2: You do know that there is a public visibility scope aswell? And since __construct is a PHP5 method, why not use the rest of the PHP5 syntax – such as the visibility scopes. Especially since the rest of the methods use it in the class?
Sorry for criticising xD
Marcin June 14th
Simply awesome! thank you v. much!
( )Greg Dougherty June 14th
I was looking for something like this the other day! Glad I found it!!!
- Greg
( )MoldyMagnet June 15th
Terrible captcha, so hackable. How about a tutorial on how to implement a re-captcha? Or at least something isn’t just unobfacusated text on a plain background.
( )Ramon June 15th
Nice! Thank you. Gonna use it in my control panel.
( )Faifas June 15th
I still don’t get a idea, why a hacker could not read the HTML source which already has your generated unique key and then submit it with all other form values?
( )Rob June 15th
Just curious not convinced!
Is the aim to make sure that the POST data is coming from your site and not from some script on another server?
This method also could apply to making images also enforced to your site only and deny hotlinking BUT…..
Wouldn’t also a Captcha prevent massive submits?
Additionally, maybe I missed it but since you output a “hidden” field with the value in it, would I not be able to use CURL get the form from you site, read in the hidden value, and re submit?
Again I think a good captcha would be 1000 x more effective.
As someone said numerous times before here, use recaptcha or something similar. This method here is more work and not safer.
( )Thomas June 15th
This is to prevent the issues related to catchpa. And unless you had curl cookies that wouldn’t work. This makes it a bit harder for hackers.
( )Wouter Bulten June 15th
What do you think about login forms?
I would not think about using a captcha for a login form.
( )Wesley van Opdorp June 15th
I like the tutorial, great job! As a fellow dutch programmer I think its great to see some good php tutorials from the Netherlands!
One thing I’d like to comment on is leaving action empty. Certain browsers (including Google’s chrome) use the base href or domain name as action address when you do not provide one. This can cause you to miss out on important business leads at a contact form for example.
Keep up the good work!
( )Benjamin Reid June 15th
I’ll be implementing this from now, it makes me feel stupid not knowing about if beforehand though, haha.
( )Lucho June 15th
Good tut but using plain md5 is considered vulnerable these days. Recommendations are using sha256 or combination of hash algorithms(md5+hmac) instead.
( )Wouter Bulten June 15th
I know, now, but I wrote this tutorial about 6 months ago..
( )Tom June 15th
It hardly matters that md5 is insecure, because the point of the article was not hiding a password or other data.
Ramon June 16th
There is an error
index.php, 12 line
if($_SERVER['REQUEST_METHOD'] == ‘post’)
it must be
( )if($_SERVER['REQUEST_METHOD'] == ‘POST’)
Ersin Demirtas June 17th
Thanks very helpgiuful article…
( )Dwayne Clarke June 17th
This is not good for XSS or CSRF, since i can view the html in the broswer create the same page and submit it or use telnet.
here is what u need to know http://phpsec.org/projects/guide/
The corner stone of web security is filtering all user input u get from the user especially when your client is interacting with a database database or you allow your client to post links which other users can click on.
a quick note:
(Problem – Counter measure)
Spoofed Form Submissions/XSS/CSRF/Spoofed HTTP Requests – 1. Filter data from users (using regular expression, htmlentities, etc).
Exposed Access Credentials – 1. use .htaccess files if you are not using a dedicated server other wise edit your php.ini file to allow/deny access to intended folders
SQL Injection – 1. prepared SQL statements 2. mysql_real_escape_string() function 3. filter data from form thus data that will be placed in SQL statements)
Session Hijacking/Session Fixation – 1. disable PHPSESSID in URLs 2. regenerate session id when user access level changes (after logging into website)
( )Dwayne Clarke June 17th
quick note:
1. If you are saving a session id it should also be placed in a database preferably.
2. When storing a user table for logging into web apps password hashing should be used rather than having plain-text passwords exposed.
http://phpsec.org/articles/2005/password-hashing.html
( )Jeff June 18th
In step 2, you briefly mention that a singlton form key would be the best thing to do since each page can have only one key. That isn’t a singleton at all. If one would turn that class into a true singleton, you would never be able to generate more than 1 form key. So every form would have to use the same key. If you want to be able to have a different key per page/form you would be better with some sort od factory class that would be responsible for handing out the right key to the right page/form as well as lifetime managent. You would probably want keys that have been issued to expire with sessions or possibly time intervals.
Just some thoughts anyway.
( )Stefan Ashwell June 19th
Really nice tutorial!
( )bybye June 21st
hmm, im wondering if this helps…
if you can post data via ajax, you can also get data via ajax.
( )if you get the form via ajax then you have the formKey and you can do a post with this formKey… in my opinion this only helps to prevent simple XSS attacks, but if someone wants to – he can..
Jomaguir June 22nd
Thanks man, that’s so helpful, right now I’m working in a website and it’s neccessary to improve the security. That helps a lot and I liked the tutorial, keep on so…
( )SX June 23rd
I learn from the tutorials and the comments. Keep those criticism coming. This is the wild wild web.
( )Zoran July 14th
About more PHP security check this website: http://shiflett.org/articles
( )Articles there are amazing
damulag84 July 29th
I used Zend Captcha and Zend Session in securing all my forms. Zend Captcha generates an alphanumeric string, I put the value in a session captcha using Zend Session and put the the value in a hidden captcha field in all my forms.
Once the form is submitted, I validate the hidden POST captcha by comparing it to the captcha session value. If match I process the transaction, if not I put an error message. Once this validation is done, I destroyed the session captcha and generate a new one. In this way the hidden captcha value cannot be used again.
( )ilyasishak July 29th
great tuts..
( )& great comments too!
good job Wouter!
billy August 30th
thanks. very good tutorial.
( )chad September 4th
this is really helpful. thanks for sharing.
( )Jaspal Singh September 6th
Excellent tutorial for creating secure php forms, Thanks for sharing.
( )Mori September 13th
Nice tutorials
( )เพชร September 22nd
thanks, I’ve learned something new.
( )Davi October 7th
if($_POST['form_key'] == $this->old_formKey)
If both the session and post variable are empty, they’re also equal. So if you’re attacking the form you simply omit the post variable.
( )Yemmy October 13th
How can I get it in PDF
( )Yemmy October 13th
How can I get the lattest version of PHP Tutorial in PDF
( )K. Mirchev October 31st
The improved solution is to generate field key name (i.e not only hidden field value but hidden field name) also and holds it in the session.
( )Apolo Castro November 13th
Congratulation and thanks for sharing
( )