Problems using "preg_replace" in a CAPTCHA routine

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
myron_b
Forum Newbie
Posts: 4
Joined: Sun Aug 26, 2007 8:21 am

Problems using "preg_replace" in a CAPTCHA routine

Post by myron_b »

I am a novice when it comes to programming in php. I'm attempting to set up CAPTCHA on my contact page and I'm using the some code found here: http://www.fuzzyopinions.com/article/te ... hp-captcha

I have two problems with the code: First the preg_replace routine does not appear to be work as intended. It is not removing the "hard-to-distinguish" letters.

Second, if the page with the CAPTCHA image is reloaded it does not correctly truncate the CAPTCHA text to seven characters. In fact, even if the user leaves the site (but keeps the browser window open) then returns later and loads the page again, it still doesn't correctly truncate the captcha image.

The code for generating the CAPTCHA is below. I've implementd it on my site without any changes here: http://www.libraryspaceplanning.com/captcha-test.html

Have I done something wrong in the way I've implemented it? Or is there something wrong with the code?

Thanks for any and all assistance!

Myron.

Code: Select all

<?php

session_start( );

$captchaTextSize = 7;

do {

    // Generate a random string and encrypt it with md5

    $md5Hash = md5( microtime( ) * mktime( ) );

    // Remove any hard to distinguish characters from our hash

    preg_replace( '([1aeilou0])', "", $md5Hash );

} while( strlen( $md5Hash ) < $captchaTextSize );

// we need only 7 characters for this captcha
$key = substr( $md5Hash, 0, $captchaTextSize );

// Add the newly generated key to the session. Note, it is encrypted.
$_SESSION['key'] = md5( $key );

// grab the base image from our pre-generated captcha image background
$captchaImage = imagecreatefrompng( "images/captcha.png" );

/* 

Select a color for the text. Since our background is an aqua/greenish color, we choose a text color that will stand out, but not completely. A slightly darker green in our case.
*/ 
$textColor = imagecolorallocate( $captchaImage, 31, 118, 92 );

/* 
Select a color for the random lines we want to draw on top of the image, in this case, we are going to use another shade of green/blue
*/
$lineColor = imagecolorallocate( $captchaImage, 15, 103, 103 );

// get the size parameters of our image
$imageInfo = getimagesize( "images/captcha.png" );

// decide how many lines you want to draw
$linesToDraw = 10;

// Add the lines randomly to the image
for( $i = 0; $i < $linesToDraw; $i++ )  {

	// generate random start spots and end spots
    $xStart = mt_rand( 0, $imageInfo[ 0 ] );
    $xEnd = mt_rand( 0, $imageInfo[ 0 ] );

    // Draw the line to the captcha
    imageline( $captchaImage, $xStart, 0, $xEnd, $imageInfo[1], $lineColor );

}

/* 
Draw our randomly generated string to our captcha using the given true type font. In this case, I am using BitStream Vera Sans Bold, but you could modify it to any other font you wanted to use.
*/
imagettftext( $captchaImage, 20, 0, 35, 35, $textColor, "fonts/VeraBd.ttf", $key );

// Output the image to the browser, header settings prevent caching
header ( "Content-type: image/png" );

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Fri, 19 Jan 1994 05:00:00 GMT");
header("Pragma: no-cache");

imagepng( $captchaImage );

?>
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Post by VladSun »

preg_replace doesn't modify any of its arguments. It returns the result instead:

Code: Select all

$md5Hash = preg_replace( '([1aeilou0])', '', $md5Hash );
There are 10 types of people in this world, those who understand binary and those who don't
myron_b
Forum Newbie
Posts: 4
Joined: Sun Aug 26, 2007 8:21 am

Post by myron_b »

Thanks! Now can anyone explain why, on reloading the page, the routine that controls the string length doesn't truncate $key to 7 characters?

Code: Select all

while( strlen( $md5Hash ) < $captchaTextSize );
$key = substr( $md5Hash, 0, $captchaTextSize );
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Post by VladSun »

Please, give us an example result and the code you are using now.
There are 10 types of people in this world, those who understand binary and those who don't
myron_b
Forum Newbie
Posts: 4
Joined: Sun Aug 26, 2007 8:21 am

Post by myron_b »

Here is the CAPTCHA code:

Code: Select all

<?php

// Start the session so we can store our generated key inside it for later retrieval
session_start( );

// Set to whatever size you want, or randomize for more security
$captchaTextSize = 7;

do {

    // Generate a random string and encrypt it with md5
    $md5Hash = md5( microtime( ) * mktime( ) );

    // Remove any hard to distinguish characters from our hash
    $md5Hash = preg_replace( '([1aeilou0])', '', $md5Hash );

} while( strlen( $md5Hash ) < $captchaTextSize );

// we need only 7 characters for this captcha
$key = substr( $md5Hash, 0, $captchaTextSize );

// Add the newly generated key to the session. Note, it is encrypted.
$_SESSION['key'] = md5( $key );

// grab the base image from our pre-generated captcha image background
$captchaImage = imagecreatefrompng( "images/captcha.png" );

/* 

Select a color for the text. Since our background is an aqua/greenish color, we choose a text color that will stand out, but not completely. A slightly darker green in our case.

*/ 
$textColor = imagecolorallocate( $captchaImage, 31, 118, 92 );
/* 

Select a color for the random lines we want to draw on top of the image, in this case, we are going to use another shade of green/blue

*/
$lineColor = imagecolorallocate( $captchaImage, 15, 103, 103 );

// get the size parameters of our image
$imageInfo = getimagesize( "images/captcha.png" );

// decide how many lines you want to draw
$linesToDraw = 10;

// Add the lines randomly to the image
for( $i = 0; $i < $linesToDraw; $i++ )  {

	// generate random start spots and end spots
    $xStart = mt_rand( 0, $imageInfo[ 0 ] );
    $xEnd = mt_rand( 0, $imageInfo[ 0 ] );

    // Draw the line to the captcha
    imageline( $captchaImage, $xStart, 0, $xEnd, $imageInfo[1], $lineColor );

}

/* 

Draw our randomly generated string to our captcha using the given true type font. In this case, I am using BitStream Vera Sans Bold, but you could modify it to any other font you wanted to use.

*/
imagettftext( $captchaImage, 20, 0, 35, 35, $textColor, "fonts/VeraBd.ttf", $key );

// Output the image to the browser, header settings prevent caching
header ( "Content-type: image/png" );

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Fri, 19 Jan 1994 05:00:00 GMT");
header("Pragma: no-cache");

imagepng( $captchaImage );

?>

Here is the HTML that calls up the CAPTCH code in a form.

Code: Select all

<!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">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>FuzzyOpinions.com | Captcha Test</title>
</head>

<body>

<img src="captcha.php" border="0" />

<form name="captcha-form" method="POST" action="captcha-verify.php">

         <input type="text" name="code" width="25" />

         <input type="submit" name="submit" value="submit" />

</form>

</body>
</html>


And here is the verify routine that checks the string entered in the form against the CAPTCHA string.

Code: Select all

<?php

session_start( ); // allows us to retrieve our key form the session

/* 
First encrypt the key passed by the form, then compare it to the already encrypted key we have stored inside our session variable
*/

if( md5( $_POST[ 'code' ] ) != $_SESSION[ 'key' ] ) {

       echo "You ented the wrong code, please try again!";

} else {

       echo "Success, you ented the correct code, rock and roll...";

}

?>

You can see the error at the link I posted earlier: http://www.libraryspaceplanning.com/captcha-test.html. Click on the link. Then hit refresh. The new CAPTCHA images will contain too many characters.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Post by VladSun »

I've tried using the problematic part of your code:

Code: Select all

<?php

// Set to whatever size you want, or randomize for more security
$captchaTextSize = 7;

do {

    // Generate a random string and encrypt it with md5
    $md5Hash = md5( microtime( ) * mktime( ) );

    // Remove any hard to distinguish characters from our hash
    $md5Hash = preg_replace( '([1aeilou0])', '', $md5Hash );

} while( strlen( $md5Hash ) < $captchaTextSize );

// we need only 7 characters for this captcha
$key = substr( $md5Hash, 0, $captchaTextSize );

// Output $key and its length
echo $key." ".strlen($key);

?>
No problems found ... works perfect ...
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Post by VladSun »

Now I've tried it with "register_globals On" ini php.ini (veryyy bad idea). It works in the way you describe. Two solutions:
1. turn off this option "register_globals Off" - very good idea;
2. rename $key to $skey for example.

PS: Don't forget to destroy session before working it on.
There are 10 types of people in this world, those who understand binary and those who don't
myron_b
Forum Newbie
Posts: 4
Joined: Sun Aug 26, 2007 8:21 am

Post by myron_b »

Vlad,

Thanks for you continued help.

I'm under an Apache environment that has the register_globals option enabled, and since I'm on shared hosting I have no access to php.ini. However, I believe I've succeeded in turning off register_globals by adding an .htaccess file to my root directory with the following lines:

Code: Select all

php_flag register_globals 0


<Files ".ht*">
deny from all
</Files>
The result seems to have resolved my problem. If there's anything "bad" about this solution (i.e. not secure) I'd appreciate any further advice you'd care to share.


Myron.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Post by VladSun »

There shouldn't be any security issues due to suing "register_globals off".

Try to avoid code which requires "register_globals On". Use this .htaccess file for every root directory of your sites hosted by this provider. Amen! :)
There are 10 types of people in this world, those who understand binary and those who don't
Post Reply