Simple Accessible CAPTCHA in PHP

Simple Accessible CAPTCHA in PHP

In just a few lines of PHP code, this CAPTCHA will allow you to block most spambots. I developed this after some spammers discovered this web site’s contact form. There already were protections to avoid certain exploits (e. g., hijacking the form to send spam to third parties), but they still were able to send me spam emails.

This one is also accessible—instead of showing a difficult-to-read word, this one gives some instructions that must be followed (and thus requires a human) to succeed. Because there is a vulnerability in this code (a spammer could just use a human to answer correctly once, then send the page repeatedly with the correct answer and hash), this is not suitable for major spammer targets, but should work well on small sites.

On the form page, this will show up as instructions (“Enter the first three letters…”) and a string of six capital letters with spaces between so they are read as individual letters by a screen reader. A hidden field is filled with the hash of the correct three letters. If the hash were only made from the three letters, a spammer could figure out the hash algorithm, then fill the two fields correctly without having to follow the instructions. By adding a secret string to the three letters, the hash function is extremely hard to decode. The secret string must be duplicated on the receiving page in order for it to calculate the same hash.

In your user form, add the following:

  1. <label>Please enter the first three letters of the following:
  2. <?php
  3. $ranstr = chr(mt_rand(65,90)) . chr(mt_rand(65,90)) . chr(mt_rand(65,90)) . chr(mt_rand(65,90)) . chr(mt_rand(65,90)) . chr(mt_rand(65,90));
  4. echo chunk_split($ranstr,1,' ');
  5. ?>
  6. <input name=“ranstr” type=“text” size=“90” maxlength=“75” />
  7. </label>
  8. <input name=“checkran” type=“hidden” id=“checkran” value=<?php echo hash('sha1',substr($ranstr,0,3).'hard to guess the hash function');?> />

Line 3 forms a string of six random upper-case letters. Line 4 prints them out with a space between each. Line 6 (with the label from line 7 and 1) is where the human enters the requested substring. Line 8 provides a hashed version of the substring (plus some ‘salt’ to make guessing the has function more difficult). The first digit in the substr function provides the start character (0 = the first character in the string) and the second digit specifies the number of characters to use — each of those can be changed, but make sure you change the instructions to correspond. To best protect your site, you should change the string in this example — line 8 above and line 2 below (hard to guess the hash function) to something of your own choice.

On the back end, the following PHP code (line 2) cleans up the user input (removing spaces; capitalizing all letters), and calculates the hash with the same appended string as in line 8 of the form page (above). If you submit your form with a GET instead of a POST, change $_POST to $_GET.

If the hashes agree, the user has entered the correct letters, and lines 3 through 17 are skipped. Lines 4 through 15 is the html that is emitted when the hashes fail to agree, and line 17 terminates the program. Normal form processing starts at line 18 or 19.

  1. <?php
  2. if ($_POST['checkran']!=hash('sha1',strtoupper(preg_replace('/\s+/','',$_POST['ranstr'])).'hard to guess the hash function'))
  3. {?>
  4. <html>
  5. <head>
  6. <title>Error</title>
  7. </head>
  8. <body>
  9. <h1>Error</h1>
  10. <p>Oops, it appears that you didn't type the correct three letters in the last box. Please go back to the form and try again.</p>
  11. <form>
  12. <input type=“button” value=“Back” onClick=history.go(-1);return true; />
  13. </form>
  14. </body>
  15. </html>
  16. <?php
  17. die(); }
  18. ?>
  19. <!— The rest of your email form handling goes after this —>

Creative Commons License This work by Rory Jaffe is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.