PHP: Protecting forms using a CAPTCHAThe CAPTCHA approach to securing forms is not new - it first appeared in the late 90's for domain name submissions to search engines and the like - but with the exponential growth of scripted exploits it's coming to the fore once again. The main targets are Guestbook and Contact forms, but any website with a form could be vulnerable to misuse. The code presented here shows you how to create a simple CAPTCHA graphic with random lines and digits and how to incorporate it into an HTML form to prevent automated submission by malicious scripts. Creating a CAPTCHA graphic using PHPThe following code needs to be saved as a stand-along PHP file (we call it captcha.php). This file creates a PNG image containing a series of five digits. It also stores these digits in a session variable so that other scripts can know what the correct code is and validate that it's been entered correctly. <?PHP
// Adapted for The Art of Web: www.the-art-of-web.com
// Based on PHP code from: php.webmaster-kit.com
// Please acknowledge use of this code by including this header.
// initialise image with dimensions of 120 x 30 pixels
$image = @imagecreatetruecolor(120, 30) or die("Cannot Initialize new GD image stream");
// set background to white and allocate drawing colours
$background = imagecolorallocate($image, 0xFF, 0xFF, 0xFF);
imagefill($image, 0, 0, $background);
$linecolor = imagecolorallocate($image, 0xCC, 0xCC, 0xCC);
$textcolor = imagecolorallocate($image, 0x33, 0x33, 0x33);
// draw random lines on canvas
for($i=0; $i < 6; $i++) {
imagesetthickness($image, rand(1,3));
imageline($image, 0, rand(0,30), 120, rand(0,30), $linecolor);
}
session_start();
// add random digits to canvas
$digit = '';
for($x = 15; $x <= 95; $x += 20) {
$digit .= ($num = rand(0, 9));
imagechar($image, rand(3, 5), $x, rand(2, 14), $num, $textcolor);
}
// record digits in session variable
$_SESSION['digit'] = $digit;
// display image and clean up
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);
?>
The output of this script appears as follows (reload to see it change): This image is meant to be difficult for 'robots' to read, but simple for humans (the Turing test). You can make it more difficult for them by addition of colours or textures, or by using different fonts and a bit of rotation. We've simplified the script presented above as much as possible so that you can easily customise it for your site and add more complexity as necessary. Adding a CAPTCHA to your formIn your HTML form you need to make sure that the CAPTCHA image is displayed and that there's an input field for people to enter the CAPTCHA code for validation. Here's a 'skeleton' of how the HTML code for your form might appear: <form method="POST" action="form-handler" onsubmit="return checkForm(this);">
...
<p><img src="/captcha.php" width="120" height="30" border="1" alt="CAPTCHA"></p>
<p><input type="text" size="6" maxlength="5" name="captcha" value=""><br>
<small>copy the digits from the image into this box</small></p>
...
</form>
If you're using JavaScript form validation then you can test that a code has been entered in the CAPTCHA input box before the form is submitted. This will confirm that exactly five digits have been entered, but not say anything about whether they're the right digits as that information is only available on the server-side ($_SESSION) data. So again, here's a skeleton of how your JavaScript form validation script might appear: function checkForm(form)
{
...
if(!form.captcha.value.match(/^\d{5}$/)) {
alert('Please enter the CAPTCHA digits in the box provided');
form.captcha.focus();
return false;
}
...
return true;
}
Finally, in the server-side script that is the target of the form, you need to check that the code entered in the form by the user matches the session variable set by the captcha.php script: if($_POST && all required variables are present) {
...
session_start();
if($_POST['captcha'] != $_SESSION['digit']) die("Sorry, the CAPTCHA code entered was incorrect!");
session_destroy();
...
}
Note: It's important to call session_start both in the captcha.php script (when seting the session variable) and in the server-side validation script (in order to retrieve the value) as those files are processed separately and can't otherwise share information. You can see this code working in our Feedback form which appears as a link on every page. Putting it all togetherThere has been feedback sent by a number of people confused about which code to put where to get this working on their own website. To try and make it clearer I've put together a couple of diagrams which illustrate the two most common solutions. Here you can see illustrated the simplest and most common setup, but by no means the best solution. The form is checked using JavaScript and then POSTed to another page/script where the data is processed:
A more 'professional' solution involves a practice called Post/Redirect/Get (PRG) which means that the data is first processed and then the user is redirected to a landing page:
This avoids a number of issues including problems caused when someone reloads the landing page which in the first configuration would cause all the POST data to be re-submitted. This can also be implemented using three scripts where the form handler has it's own file and decides whether to redirect back to the FORM or forward to the landing page depending on whether the data validates. In any case the PHP form handler code needs to appear as the first item before any HTML code is generated. Upgrading the CAPTCHA to block new botsThe CAPTCHA image presented above was 'cracked' after a matter of months by one or two bots. Fortunately a few small changes to the code can send them packing at least for a while. Here's some code to 'jazz up' our CAPTCHA to give it a better chance of being bot-proof. The sections of code that have been changed are highlighted: <?PHP
// Adapted for The Art of Web: www.the-art-of-web.com
// Based on PHP code from: php.webmaster-kit.com
// Please acknowledge use of this code by including this header.
// initialise image with dimensions of 120 x 30 pixels
$image = @imagecreatetruecolor(120, 30) or die("Cannot Initialize new GD image stream");
// set background and allocate drawing colours
$background = imagecolorallocate($image, 0x66, 0x99, 0x66);
imagefill($image, 0, 0, $background);
$linecolor = imagecolorallocate($image, 0x99, 0xCC, 0x99);
$textcolor1 = imagecolorallocate($image, 0x00, 0x00, 0x00);
$textcolor2 = imagecolorallocate($image, 0xFF, 0xFF, 0xFF);
// draw random lines on canvas
for($i=0; $i < 6; $i++) {
imagesetthickness($image, rand(1,3));
imageline($image, 0, rand(0,30), 120, rand(0,30) , $linecolor);
}
session_start();
// add random digits to canvas using random black/white colour
$digit = '';
for($x = 15; $x <= 95; $x += 20) {
$textcolor = (rand() % 2) ? $textcolor1 : $textcolor2;
$digit .= ($num = rand(0, 9));
imagechar($image, rand(3, 5), $x, rand(2, 14), $num, $textcolor);
}
// record digits in session variable
$_SESSION['digit'] = $digit;
// display image and clean up
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);
?>
And here is the modified CAPTCHA graphic produced by the new code: All we've done here is changed the background colour from white to green, the lines from grey to light green and the font colour from black to a mixture of white and black. This method has now also been cracked by a small number of bots. In recent days we've seen 10-20 succesful exploits a day, but we're not going to give up. A new version of the CAPTCHA graphic is shown in the next section below. For information on how the CAPTCHA images can be cracked, read this article. Yet another CAPTCHASo here's the latest version that we're using on live websites. The main change from those presented above is that we now use a larger range of fonts to confuse the spambots. You can find a good resource for GDF fonts under References below. The positioning of the lines has also changed to make it more random. Once this one is cracked the options become more complex. We'll probably have to start rotating some of the digits or applying other kinds of distortion. Or Microsoft could invent a secure operating system and put an end to botnets once and for all! Usability improvementsIt's the little things that make your visitors more relaxed about
filling in forms. The code below has been modified to limit the input
to only numbers using the onkeyup event, and adding an option
to reload/refresh the CAPTCHA image in the case that it's not readable.
In your form, the CAPTCHA section will then appear something like the following: You can see this code in action on our Feedback page which is linked at the bottom of each page. ReferencesUser Comments and Notes3 February 2007: Lisa says: First of all, your captcha instructions are the best I've seen yet! I almost got it going but I'm confused about the last part where I need to add the session start variable. By definition PHP can validate only AFTER the form is submitted. In your PHP code you need to call session_start() before any $_SESSION variables become available, and then destroy the session. If you have problems try using the print_r function to display the $_POST and $_SESSION data to make sure they are populated. If your form uses GET instead of POST as the submission method then replace $_POST with $_GET. 9 March 2008: Paul Smith says: 1st of all thank you for a straightforward script that works well. You can find the code for the second CAPTCHA now on the page. We only make public new versions of the code as and when we upgrade our own 'live' version, which has now happened. 14 November 2008: Brian says: Thanks for a very informative and easy to follow tutorial! 3 August 2009: Mewp says: I have only one question: how is it better than ReCAPTCHA [recaptcha.net]? You only have one question?
14 October 2009: Gary says: HI, I am new to this, maybe you can help. I've tried to illustrate this in the section "Putting it all together". Look for where "PHP form handler" appears in the graphics. Send Feedback |
|
|
© Copyright 2010 Chirp Internet
- Page Last Modified: 30 December 2009
|
|