I've created a simple captcha script. Very basic. I'm just wondering if this is the way it is typically done (probably not) or if there is a better, more secure way to do it. What could I do to improve this script? All suggestions are welcome. Thanks in advance. 😃

<?php
session_start();
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
	"http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head>
		<title>Login Required</title>
	</head>

<body bgcolor="black" text="white">
	<center>
	<h1>Only The Worthy Gain Entry Into The Halls Beyond</h1>
	<br />
	<br />
	<br />
	<form action="worthy.php" method="POST">
	<table border="0" cellpadding="5">
	<tr>
	<td><img src="torch.gif" /></td>
	<td>
	<input type="text" name="name" size="10" />
	</td>
	<td>
	<input type="password" name="pass" size="10" />
	</td>
	<td>
	<input type="text" name="captcha" size="10" />
	<td>
	<input type="submit" value="  Test Your Worthiness  " />
	</td>
	<td><img src="torch.gif" /></td>
	</tr>
	</table>
	</form>
	<?php
	echo $_SESSION['msg'];
	unset($_SESSION['msg']);
	?>
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
	<?php		
	$letter = array();
	$again = array();
	$letter[1] = mt_rand(1, 26);
	$letter[2] = mt_rand(1, 26);
	$letter[3] = mt_rand(1, 26);
	$letter[4] = mt_rand(1, 26);
	$letter[5] = mt_rand(1, 26);
	$letter[6] = mt_rand(1, 26);
	for ($x = 1; $x <= 6; $x++)
	{
		switch ($letter[$x])
		{
		case 1:
			$letter[$x] = 'captcha/1/a.jpg';
			$again[$x] = 'a';
			break;
		case 2:
			$letter[$x] = 'captcha/1/b.jpg';
			$again[$x] = 'b';
			break;
		case 3:
			$letter[$x] = 'captcha/1/c.jpg';
			$again[$x] = 'c';
			break;
		case 4:
			$letter[$x] = 'captcha/1/d.jpg';
			$again[$x] = 'd';
			break;
		case 5:
			$letter[$x] = 'captcha/1/e.jpg';
			$again[$x] = 'e';
			break;
		case 6:
			$letter[$x] = 'captcha/1/f.jpg';
			$again[$x] = 'f';
			break;
		case 7:
			$letter[$x] = 'captcha/1/g.jpg';
			$again[$x] = 'g';
			break;
		case 8:
			$letter[$x] = 'captcha/1/h.jpg';
			$again[$x] = 'h';
			break;
		case 9:
			$letter[$x] = 'captcha/1/i.jpg';
			$again[$x] = 'i';
			break;
		case 10:
			$letter[$x] = 'captcha/1/j.jpg';
			$again[$x] = 'j';
			break;
		case 11:
			$letter[$x] = 'captcha/1/k.jpg';
			$again[$x] = 'k';
			break;
		case 12:
			$letter[$x] = 'captcha/1/l.jpg';
			$again[$x] = 'l';
			break;
		case 13:
			$letter[$x] = 'captcha/1/m.jpg';
			$again[$x] = 'm';
			break;
		case 14:
			$letter[$x] = 'captcha/1/n.jpg';
			$again[$x] = 'n';
			break;
		case 15:
			$letter[$x] = 'captcha/1/o.jpg';
			$again[$x] = 'o';
			break;
		case 16:
			$letter[$x] = 'captcha/1/p.jpg';
			$again[$x] = 'p';
			break;
		case 17:
			$letter[$x] = 'captcha/1/q.jpg';
			$again[$x] = 'q';
			break;
		case 18:
			$letter[$x] = 'captcha/1/r.jpg';
			$again[$x] = 'r';
			break;
		case 19:
			$letter[$x] = 'captcha/1/s.jpg';
			$again[$x] = 's';
			break;
		case 20:
			$letter[$x] = 'captcha/1/t.jpg';
			$again[$x] = 't';
			break;
		case 21:
			$letter[$x] = 'captcha/1/u.jpg';
			$again[$x] = 'u';
			break;
		case 22:
			$letter[$x] = 'captcha/1/v.jpg';
			$again[$x] = 'v';
			break;
		case 23:
			$letter[$x] = 'captcha/1/w.jpg';
			$again[$x] = 'w';
			break;
		case 24:
			$letter[$x] = 'captcha/1/x.jpg';
			$again[$x] = 'x';
			break;
		case 25:
			$letter[$x] = 'captcha/1/y.jpg';
			$again[$x] = 'y';
			break;
		case 26:
			$letter[$x] = 'captcha/1/z.jpg';
			$again[$x] = 'z';
			break;
		}
	}
	foreach ($letter as $l)
	{
		echo "<img src=$l />";
	}
	$_SESSION['phrase'] = $again[1] . $again[2] . $again[3] . $again[4] . $again[5] . $again[6];
	?>
	</center>
</body>
</html>
    <?php
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_HEADER, 0);
    curl_setopt($curl, CURLOPT_URL, "http://127.0.0.1/sandbox/3.php");
    curl_setopt($curl, CURLOPT_POST, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_COOKIEFILE, "cookiefile");
    curl_setopt($curl, CURLOPT_COOKIEJAR, "cookiefile"); # SAME cookiefile
    $xxx = curl_exec($curl);
    
    preg_match_all("#<img src=captcha/1/([a-z]{1}).jpg />#is", $xxx, $matches);
    $key = '';
    foreach ($matches[1] as $v) { $key .= $v; }
    // Post to second page:
    curl_setopt($curl, CURLOPT_URL, "http://127.0.0.1/sandbox/worthy.php"); 
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, "name=SPAMSPAMSPAM&pass=SPAMSPAMSPAM&captcha=" . $key);
    $xxx = curl_exec($curl);
    echo $xxx; // Should show whatever the worthy page is, as well as post the SPAM data to the page
    
    curl_close($curl);
    ?>
    

    ^ How to crack it

    <?php     
    for ($i = 0; $i < 6; $i++) { $tmp = chr(mt_rand(97,122)); $_SESSION['phrase'] .= $tmp; echo '<img src=img.php?a=' . $tmp . ' />'; } ?>

    ^ Equivalent code-block for your switch statement

    So, in summary, as a captcha script, it doesn't work very well, and only keeps the very simple bots out. As a script generating a random letter image, a switch statement is not ideal, as the characters all lie between ascii character codes 97 and 122 inclusive, so we can deal with them as numbers and then turn them into letters ([man]chr/man does that.) Also, one loop is always better than three loops that loop over the same data.

    There are many captcha script tutorials [on the internet that may be able to give you some ideas. Most deal with GD image generation, but are not too hard to get your head around.

    HTH

      Thanks for replying madwormer. It's humbling to see someone rip apart your script, but I should expect that as I'm still learning the vastness that is PHP, but seriously, I do appreciate your response. 😃

      I'm not sure exactly what curl is, but I'm going to look it up. I wrote this script in about half an hour or so, just to see what I could come up with. I'm really striving to constantly better myself with PHP, but it seems like when I think I might have something good, I only come to realize that I'm still years behind the rest of the world.

      Is the key to being good at it just a whole lot of reading and writing code?

        Is the key to being good at it just a whole lot of reading and writing code?

        The more you do of each, the more experience you'll have, and that's the key to getting better - making mistakes and learning from them.

        As madwormer mentioned, the more-secure way of doing this would be to use GD (http://php.net/gd) to generate the image on the fly so that he can't just match against your <img> tags and see what characters you're putting in.

          Magfersile wrote:

          I'm still learning the vastness that is PHP, but seriously, I do appreciate your response.

          Well, that wasn't really anything to do with PHP per se, just defensive programming.

          Is the key to being good at it just a whole lot of reading and writing code?

          Not just, but it's a vital part. It's also necessary to learn from the experience 🙂. I think it's safe to say that it's impossible to be a really good PHP programmer (or a really good programmer in any language) without being able to think in terms divorced from the particular syntax or paradigm of a particular language, without having an intuitive grasp of programming. That's what you should be trying to develop when reading/writing/running lots of code; it's healthy to be a language slut and mess around with as many different programming languages as you can get your hands on. You're spoiled for choice: once upon a time there weren't any programming languages and people had write them the hard way.

            Thanks guys for helping me understand a little better. Are there any good articles or training resources that you could point me to to maybe help me develop a better 'intuitive grasp of programming'?

              Well, a classic is Structure and Interpretation of Computer Programs. A search for "How to Think Like a Computer Scientist" will turn up several titles based around different languages (Python, Java, C++ are the canonical set, and it seems there are Ruby and Logo versions as well).

              See Free Tech Books

              'Cos this is the web and you're using an app that effectively provides an entire programming platform in its own right, books don't have to remain static: Eloquent JavaScript. Actually, that's quite profound: a machine that is simulating a machine that simulates providing a simulated machine (see Bornat's essay below). In fact, I'll let you in on a very deep secret of programming: programs are fundamentally meaningless. Now that I've told you, I'm going to have to kill you.

              And it's fun to go wandering through the original Wiki.

              Richard Bornat wrote:

              ....Programmers do real magic by making working machinery out of mathematical abstractions. Somehow or other, useful things are made out of a material that is less than thin air, less even than a vacuum.
              http://www.cs.mdx.ac.uk/staffpages/r_bornat/lectures/revisedinauguraltext.pdf

                Thanks Weedpacket. I'm gonna have a field day with all those links. 🙂 Can you elaborate a bit on what you mean by meaningless?

                  Magfersile wrote:

                  Can you elaborate a bit on what you mean by meaningless?

                  It's something you have to realise for yourself for it to be effective (although it's covered in Bornat's treatise). As a novel that I've just finished reading (for the nth time) puts it: "The only way to grasp a ... concept was to see it in a multitude of different contexts, think through dozens of specific examples, and find at least two or three metaphors to power intuitive speculations."

                  Bornat also notes that "...the experience over decades, across all universities and all countries, [is] that programming is so hard it's almost impossible to teach. Something between 30% and 50% of computing students never really learn to program and never have."

                    4 days later

                    So, as much as half of the programmers out there learn only how to go through the motions of getting the syntax right, so to speak, and the rest actually have an innate feel for how programs should be written, intelligently and well structured? (If that's what you meant) That's an interesting statistic.

                      4 days later

                      A few months ago, I scrounged around looking for a decent CAPTCHA script in PHP. I found several that worked OK, but none had all of the features I deemed necessary. In particular: (1) the size of the image should be variable; (2) the character set should be variable, and, if desired, include both upper and lower case characters; (2) the size of the characters within the image should be variable; and, (3) most importantly, each character should be randomly rotated AND offset (in both X and Y dimensions) within the image. The challenge, of course, is to stay ahead of the image recognition guys ... and they are pretty danged good! These days, for a few $$, you can clip out a CAPTCHA image and send it to, e.g., a certain website in Russia; usually, within a very few seconds, you will receive the code in clear text! Nasty business model, but for some, $$ are $$.

                      In any event, here's my recomposition of the best ideas out of what I downloaded:

                      Here's the entry point module:

                      <?php 
                      
                      // File Location: /tmp/CAPTCHA_1/form.php
                      	// derived from the following:
                      		// @author Edward Elliot
                      		// @source http://www.sitepoint.com/article/toughen-forms-security-image
                      	// @author Jeffrey Van Myers
                      	// @copyright 2004-8 Blue By-ways, LLC
                      	// @version 1.0
                      	// @since 1.0
                      	// @access public
                      
                      session_start();
                      if (isset($_POST['submit']))
                      {
                         if ($_SESSION['security_code'] == $_POST['security_code'] && !empty($_SESSION['security_code']))
                         {
                      		// Insert your code for processing the form here, e.g., emailing the submission, entering it into a database, etc. 
                      		echo 'You have successfully created an account';
                      		unset($_SESSION['security_code']);
                         }
                         else
                         {
                      		// Insert your code for showing an error message here
                      		echo 'Sorry, you have provided an invalid security code';
                         }
                      }
                      else
                      {
                      ?>
                      <!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xHTML1/DTD/xHTML1-Transitional.DTD">
                      <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
                      <head>
                      	<title>CAPTCHA_1 Test</title>
                      </head>
                      <body>
                      	<form action="form.php" method="post">
                      		<img src="images.php" /><br />
                      		<label for="security_code">Security Code: </label><input id="security_code" name="security_code" type="text" /><br />
                      		<input type="submit" name="submit" value="Submit" />
                      	</form>
                      </body>
                      </html>
                      <?php
                      }
                      ?>

                      As you can see, it dynamically calls the main Class module:

                      <?php
                      
                      // File Location: /tmp/CAPTCHA_1/images.php
                      	// derived from the following:
                      		// @author Edward Elliot
                      		// @source http://www.sitepoint.com/article/toughen-forms-security-image
                      	// @author Jeffrey Van Myers
                      	// @copyright 2004-8 Blue By-ways, LLC
                      	// @version 1.0
                      	// @since 1.0
                      	// @access public
                      
                      session_start();
                      $oImage = new Images(); // instantiate object
                      if ($oImage->Create())
                      {
                      	$sCode = $oImage->GetCode();
                      }
                      else
                      {
                      	echo 'Image GIF library is not installed.';
                      }
                      exit; // end images.php
                      
                      // Class to create CAPTCHA images
                      	// @access public
                      class Images
                      {
                      	var $_oImage; // image object
                      	var $_oChar; // character object
                      	var $_font = 'monofont.ttf'; // character font
                      	var $_sLower = '23456789bcdfghjkmnpqrstvwxyz'; // lower case character set
                      	var $_sUpper = '23456789bBcCdDfFgGhHjJkKmMnNpPqQrRsStTvVwWxXyYzZ'; // upper/lower case character set
                      	var $_sPossible = ''; // set of possible characters, selected by _iCase
                      	var $_iCase = 1; // _iCase: 0 => lower case only; 1 => upper and lower case
                      	var $_iWidth = 150; // image width, in pxs
                      	var $_iHeight = 40; // image heigth, in pxs
                      	var $_iNumChars = 5; // number of characters in image
                      	var $_iSize; // calculated image size
                      	var $_iSpacing; // calculated character spacing
                      	var $_sCode; // randomly generated CAPTCHA character string
                      	var $_iColor = 1; // _iColor: 0 => black & white; 1 => RGB
                      
                      // constructor: class Images
                      	// @access public
                      function Images()
                      {
                      	$this->_sPossible = $this->_iCase ? $this->_sUpper : $this->_sLower;
                      	$this->_iSize = $this->_iHeight * 0.75; // font size will be 75% of the image height
                      	$this->_oImage = imagecreate($this->_iWidth, $this->_iHeight);// create new image
                      	$cBackground = imagecolorallocate($this->_oImage, 255, 255, 255); // allocate white background colour
                      	$this->_iSpacing = (int)($this->_iWidth / $this->_iNumChars); // calculate spacing between characters based on width of image
                      } // end function Images
                      
                      // function: create CAPTCHA image
                      	// @access public
                      function Create()
                      {
                      	if (!function_exists('imagegif')) // check for existence of GD GIF library
                      	{
                      		return false;
                      	}
                      	$this->_DrawNoise(); // draw random noise in image box
                      	$this->_DrawCharacters(); // draw randomly selected/oriented CAPTCHA character string in image box
                      	header('Content-type: image/gif'); // tell browser that data is gif
                      	imagegif($this->_oImage); // write stream to browser
                      	imagedestroy($this->_oImage); // free memory used in creating image
                      	$_SESSION['security_code'] = $this->_sCode; // set CAPTCHA code
                      	return true;
                      } // end function Create
                      
                      // function: draw random noise in image box
                      	// @access private
                      function _DrawNoise()
                      {
                      	$iNoiseColor = imagecolorallocate($this->_oImage, 100, 120, 180);
                      	for ($i = 0; $i < ($this->_iWidth * $this->_iHeight) / 3; $i++) // generate random dots in background
                      	{
                      		imagefilledellipse($this->_oImage, mt_rand(0, $this->_iWidth), mt_rand(0, $this->_iHeight), 1, 1, $iNoiseColor);
                      	}
                      	for ($i = 0; $i < ($this->_iWidth * $this->_iHeight) / 150; $i++) // generate random lines in background
                      	{
                      		imageline($this->_oImage, mt_rand(0, $this->_iWidth), mt_rand(0, $this->_iHeight), mt_rand(0, $this->_iWidth), mt_rand(0, $this->_iHeight), $iNoiseColor);
                      	}
                      } // end function _DrawLines
                      
                      // function: draw randomly selected/oriented CAPTCHA character string in image box
                      	// @access private
                      function _DrawCharacters()
                      {
                      	$this->_sCode = ''; // reset code
                      	$iX = (int) $this->_iSpacing / 4; // calculate spacing in X dimension for 1st character
                      	$iMidY = (int) $this->_iHeight / 2; // calculate mid-line of image box
                      	for ($i = 0; $i < $this->_iNumChars; $i++) // loop through and write out selected number of characters
                      	{
                      		$sChar= substr($this->_sPossible, mt_rand(0, strlen($this->_sPossible)-1), 1); // select random character
                      		$this->_sCode .= $sChar; // add to code string
                      		if ($this->_iColor) // create RGB image
                      		{
                      			$iRandRed = (int) rand(0, 128); // select random red saturation
                      			$iRandGreen = (int) rand(0, 128); // select random green saturation
                      			$iRandBlue = (int) rand(0, 128); // select random blue saturation
                      			$cTextColour = imagecolorallocate($this->_oImage, $iRandRed, $iRandGreen, $iRandBlue);
                      		}
                      		else // create gray scale image
                      		{
                      			$iRandGray = (int) rand(0, 128); // select random saturation
                      			$cTextColour = imagecolorallocate($this->_oChar, $iRandGray, $iRandGray, $iRandGray);
                      		}
                      		$fRandRotate = (float) rand(0, 25); // select random positive rotation
                      		$iRandDirection = (int) rand(0, 10); // select random rotation direction
                      		if ($iRandDirection > 5)
                      		{
                      			$fRandRotate = (float) -$fRandRotate; // change to random negative rotation
                      		}
                      		$textbox = imagettfbbox($this->_iSize, $fRandRotate, $this->_font, $sChar) or die('Error in imagettfbbox function'); // create textbox and add text
                      		$iY = ($this->_iHeight - $textbox[5]) / 2; // calculate spacing in Y dimension for this character
                      		imagettftext($this->_oImage, $this->_iSize, $fRandRotate, $iX, $iY, $cTextColour, $this->_font , $sChar) or die('Error in imagettftext function');
                      		$iX += $this->_iSpacing; // calculate spacing in X dimension for next character
                      	}
                      } // end function _DrawCharacters
                      
                      // function: return CAPTCHA character string
                      	// @access public
                      function GetCode()
                      {
                      	return $this->_sCode;
                      } // end function GetCode
                      } // end Class Images
                      ?>

                      I've included extensive (for me) comments for ease of understanding & modification. Note that the code does check to make sure your server has the GD image-handling library installed; otherwise it pukes.

                      Even though this is set up to take the default values from the Class setup, it would, of course, be possible to pass them from the set-up code in the form.php module, or, if you want to play with fire, pass them as vars in the <img> URL (line 38 in form.php). If you do decide to use both upper and lower case characters, be sure to add an advisory line to the output (e.g., immediately after line 40 in form.php) to warn the client that they must enter the code using the appropriate case.

                      One enhancement I considered, but have not yet tackled, is to have multiple font sets and randomly select which set on a character-by-character basis. Should be relatively easy, but will require a bit of work.

                      I attached the monofont.ttf file as monofont.ttf.txt, because the uploader refused the file as .ttf. Hopefully, it will be OK when you strip off the ".txt" suffix.

                      My thanks to Edward Elliot (http://www.sitepoint.com/article/toughen-forms-security-image) for giving me such a good place to start.

                        Thanks madwormer for ripping my script apart. It's always good to know that whatever you can program, someone else can do it better and I'll try to make my code smarter.
                        Thanks for posting Weedpacket, interesting stuff and you've got me going in a new direction now and I appreciate it. 🙂

                        Thanks for the code icehose, makes it a bit easier to understand to see several various approaches. The ttf file worked fine after renaming it.

                        I'm going to go ahead and mark this topic resolved, and I look forward to talking with you guys again. You guys are awesome.

                          Write a Reply...