Sehr simpler Captcha in PHP – Captcha.class.php

Dieser Beitrag ist Teil meiner Sourecode a Day-Aktion.

Heute möchte ich eine Klasse vorstellen, mit der man sehr einfache Captchas generieren und prüfen kann. Man sollte diese Klasse so jedoch nicht direkt verwenden, sondern zumindest statt imagestring imagettftext mit einer eigenen Schriftart verwenden.

<?php
/**
 * a simple captcha
 *
 * @author Alexander Thiemann 
 */
class Captcha {
	/**
	 * generate new captcha code
	 *
	 */
	public static function reinit() {
		$_SESSION["xcaptcha"] = rand(10000, 99999);
	}
	
	/**
	 * check the userinput + captcha
	 *
	 * @param string $code
	 * @return bool
	 */
	public static function check($code) {
		if (isset($_SESSION["xcaptcha"]) && !empty($code) && $code == $_SESSION["xcaptcha"]) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * generate captcha image
	 *
	 */
	public static function generate() {
		$im = ImageCreate(55, 20);
		$white = ImageColorAllocate($im, 0xFF, 0xFF, 0xFF);
		$black = ImageColorAllocate($im, 0x00, 0x00, 0x00);
		ImageString($im, 4, 3, 3, $_SESSION["xcaptcha"], $black);
		
		header("Content-Type: image/png");
		ImagePNG($im);
		ImageDestroy($im);
	}
}
?>
 

Mit PHP einen simplen Captcha cracken

Solange ein Captcha nicht sehr aufwändig generiert wurde, ist er auch entsprechend leicht zu knacken. Ich habe hier mal ein Beispiel für einen schlechten Captcha:

Ein Beispiel für einen schlechten Captcha

Was ist an diesem Captcha schlecht? Der auszulesende Inhalt ist nicht abwechselnd genug (immer nur Zahlenfolgen), nicht verzerrt, nicht farblich variierend und hebt sich zu gut vom Hintergrund ab. Das alles werden wir nun ausnutzen um den Captcha zu knacken.

Zuerst müssen wir „Zahlendefinitionen“ schreiben. Das heißt wir legen für jede Zahl einige eindeutige Fixpunkte fest, mit denen die Zahl eindeutig identifizierbar ist.

Beispiel an der Zahl „1“:

     OO             
    OOO             
   OOOO             
     OO             
     OO             
     OO             
     OO             
     OO             
     OO             
   OOOOOO           

Die Definition:

function is_one($pix, $x, $y) {
	$c = 0;
	$c += check($pix, $x, $y, 2, -2); // 5 / 5
	$c += check($pix, $x, $y, 3, -2); // 6 / 5
	$c += check($pix, $x, $y, 1, -1); // 4 / 6
	$c += check($pix, $x, $y, 2, -1); // 5 / 6
	$c += check($pix, $x, $y, 3, -1); // 6 / 6
	$c += check($pix, $x, $y, 0, 0); // 3 / 7
	$c += check($pix, $x, $y, 1, 0); // 4 / 7
	$c += check($pix, $x, $y, 2, 0); // 5 / 7
	$c += check($pix, $x, $y, 3, 0); // 6 / 7
	$c += check($pix, $x, $y, 2, 1); // 5 / 8
	$c += check($pix, $x, $y, 3, 1); // 6 / 8
	$c += check($pix, $x, $y, 2, 2); // 5 / 9
	$c += check($pix, $x, $y, 3, 2); // 6 / 9
	$c += check($pix, $x, $y, 2, 3); // 5 / 10
	$c += check($pix, $x, $y, 3, 3); // 6 / 10
	$c += check($pix, $x, $y, 2, 4); // 5 / 11
	$c += check($pix, $x, $y, 3, 4); // 6 / 11
	$c += check($pix, $x, $y, 2, 5); // 5 / 12
	$c += check($pix, $x, $y, 3, 5); // 6 / 12
	$c += check($pix, $x, $y, 2, 6); // 5 / 13
	$c += check($pix, $x, $y, 3, 6); // 6 / 13
	$c += check($pix, $x, $y, 0, 7); // 3 / 14
	$c += check($pix, $x, $y, 1, 7); // 4 / 14
	$c += check($pix, $x, $y, 2, 7); // 5 / 14
	$c += check($pix, $x, $y, 3, 7); // 6 / 14
	$c += check($pix, $x, $y, 4, 7); // 7 / 14
	$c += check($pix, $x, $y, 5, 7); // 8 / 14
	if ($c == 27) {
		return true;
	}
	return false;
}

Dort sind nun alle Fixpunkte relativ zum „ersten“ Fixpunkt gespeichert. Der „erste“ Fixpunkt ist bei der eins der Punkt ganz unten rechts. Nachdem wir solche Definitionen für alle Zahlen von 0-9 erstellt haben geht’s nun an die Wiedererkennung der Zahlen. Dazu muss zuerst der Hintergrund herausgefiltert werden und die Grafik in ein besser (für unser Programm) lesbares Format gebracht werden. Dies mache ich wie folgt:

function read($path) {
	$im = ImageCreateFromPNG($path);
	$pix = array();
	$sy = ImageSy($im);
	$sx = ImageSx($im);

	for ($y = 0;$y<$sy;$y++) {
		for ($x = 0;$x<$sx;$x++) {
			$col = imagecolorat($im, $x, $y);
			$rgb = imagecolorsforindex($im, $col);

			if ($rgb["red"] <= 150) {
				$pix[$x][$y] = "O";
			}
			else {
				$pix[$x][$y] = "W";
			}
		}

	}

	// ...
}

Nun haben wir den Array $pix, der bei [x][y] entweder W (für weiß) oder O (für schwarz) enthält. Jetzt müssen wir nur noch durch den Array durchgehen und nach unseren Fixpunkt-Mustern suchen:

        // ....

        $no = 0;
	$complete_string = "";
	$found_at = array();
	
	for ($x = 0;$x<$sx;$x++) {
		for ($y = 0;$y<$sy;$y++) {
			if ($pix[$x][$y] == "O" && !in_array("$x|$y", $found_at)) {
				$no = 0;
				
				if (is_one($pix, $x, $y)) {
					$no = 1;
				}
				if (is_two($pix, $x, $y)) {
					$no = 2;
				}
				if (is_three($pix, $x, $y)) {
					$no = 3;
				}

				if (is_four($pix, $x, $y)) {
					$no = 4;
				}

				if (is_five($pix, $x, $y)) {
					$no = 5;
				}

				if (is_six($pix, $x, $y)) {
					$no = 6;
				}

				if (is_seven($pix, $x, $y)) {
					$no = 7;
				}

				if (is_eight($pix, $x, $y)) {
					$no = 8;
				}

				if (is_nine($pix, $x, $y)) {
					$no = 9;
				}
				
				if ($no != 0) {
					$found_at[] = "$x|$y";
					$complete_string .= $no;
				}
			}
		}
	}

	//...

Die Variable $complete_string enthält nun den ausgelesenen String. Simple as that 😉

Hier der gesamte Code inklusive der Hilfsfunktion check(). Der Code ist schon etwas älter, also nicht über schlechten Code-Stil wundern – er soll legendlich das Prinzip erklären: http://agrafix.net.pastebin.com/b6exg0SW

Noch ein Screenshot:
Geknackter Captcha

Das war’s schon – viel Vergnügen damit!