Verifizierung der Benutzereingabe
Kommen wir nun zu Listing 2. Unser generiertes Captcha wird in die HTML-Seite eingebettet.<input type='hidden' name='formimage' value='<?=$image_name?>'>.
Jetzt fehlt noch ein Formularfeld für die Benutzereingabe:
<INPUT type='text' name='valr'>
Mit dem ersten Aufruf unseres Formulars wird eine SESSION initialisiert. Wir wollen dem Anwender nur eine begrenzte Zahl an Lösungsversuchen zugestehen. Um die Anzahl falscher Eingaben zu zählen, speichern wir einen Zähler in unserer SESSION.
$_SESSION["Captcha_trys"] = 0;
Bei jedem falschen Lösungsversuch wird dieser Zähler inkrementiert. Hat der Benutzer die maximal zulässige Anzahl an Fehlversuchen ($attempts = 5) erreicht, ist ein weiterer Versuch nicht mehr möglich.
if ($_SESSION["Captcha_trys"] >= $attempts-1) die("message");
Die SESSION dient noch einem weiteren Zweck, zu dem wir im nachfolgenden Beispiel kommen. Bisher haben wir lediglich ein Formular, welches unser Captcha darstellt und dem Benutzer die Möglichkeit gibt, seinen Lösungsvorschlag abzuschicken. Nun müssen wir diesen Lösungsvorschlag aber auch noch verarbeiten, um zu entscheiden, ob der Benutzer den Turing-Test bestanden hat.
Wir müssen mit unserem Formular also auch die zu erratende Zeichenkette übertragen. Würden wir dies unverschlüsselt tun, wäre der Captcha-Test für Bots kein Hindernis, da er einfach diesen Wert aus dem HTML auslesen könnte. Als wir das Bild generierten, haben wir den Dateinamen mithilfe eines MD5 Hash erzeugt.
md5($string.$salt)
Theoretisch könnten wir auch diesen Wert nehmen, um die Benutzerangabe abschließend zu prüfen. Ich habe mich in diesem Beispiel aber für eine weitere Verschlüsselung entschieden.
Dies soll bei den Entwicklern von Bots zusätzliche Verwirrung stiften. Die in der Datei func_md5.php definierten Funktionen md5_decrypt() und md5_encrypt() ermöglichen uns eine 128 Bit CFB (Cipher Feedback Mode)-Verschlüsselung.
Die Funktionen wurden von Alexander Valyalkin in der php.net-Dokumentation veröffentlicht. Zur Verschlüsselung und Entschlüsselung benötigen wir einen eindeutigen Key. Um für jeden Benutzer einen einmaligen Key zu generieren, bedienen wir uns erneut der Funktion mk_string(), welche uns ja bereits den Captcha-String geliefert hat.
if (!isset($_SESSION["Captcha_key"])) $_SESSION["Captcha_key"] = mk_string(15);
Wir haben nun also einen zufällig generierten, 15 Zeichen langen Key in unserer Benutzersession gespeichert. Wir verschlüsseln unsere Captcha-Lösung mit diesem Key und schreiben sie in ein verstecktes Formularfeld.
<input type='hidden' name='pasw' value='<?=$enc_text?>'>
Nach dem Absenden des Formulars wird der verschlüsselte String $_POST["pasw"] entschlüsselt und mit der Benutzereingabe $_POST["valr"] verglichen. Sollten diese beiden Strings übereinstimmen, so hat der Anwender den richtigen String eingetragen und den Turing-Test bestanden.
Gültigkeitsdauer
Theoretisch könnte nun der Benutzer, solange seine SESSION aktiv ist, sich immer wieder neu anmelden, da er ja bereits einen Test bestanden hat und sich sein Key während der aktiven SESSION nicht mehr verändert. Er könnte quasi ab jetzt die Arbeit an einen Bot übertragen. Aus diesem Grund löschen wir das erzeugte Bild nach jedem Lösungsversuch. Nur wenn das Bild im Filesystem des Browsers noch vorhanden ist, kann der Test bestanden werden. So stellen wir sicher, dass keine manipulierten oder automatisierten Formularabfragen zum Bestehen des Turing-Tests benutzt werden können. Zusätzlich ändern wir den Key der SESSION bei jedem falschen Lösungsversuch. Diese Lösung hat den nützlichen Nebeneffekt eines Garbage Collectors, da fast alle generierten Bilder automatisch wieder vom Server gelöscht werden.Unserer Captcha-Lösung fehlt noch ein entscheidendes Sicherheitsmerkmal: die Gültigkeitsdauer eines jeden Bildes. Nach Ablauf eines gewissen Zeitfensters sollen selbst korrekte Lösungen nicht mehr akzeptiert werden. So können wir beispielsweise die Zeit einschränken, die ein Benutzer oder Bot benötigen kann, um ein Bild erfolgreich zu entschlüsseln. Dies ist eine weitere wichtige Sicherheitsvorkehrung, da OCR bei guten Captchas durchaus viel Zeit kosten kann.
Gelöst haben wir dies in unserem Fall ebenfalls mit der Benutzersession. Sobald das Captcha generiert wird, setzen wir unsere Variable $_SESSION["Captcha_time"] ) auf die aktuelle Unixzeit des Servers. Nach der Übertragung des Formulars vergleichen wir die in der SESSION gespeicherte Zeit mit der aktuellen Uhrzeit – Offset in Sekunden.
if($_SESSION["Captcha_time"] < time()-120) $a_errors[] = "Timeout";
Der Benutzer hat also in diesem Beispiel zwei Minuten bzw. 120 Sekunden Zeit, um seinen Lösungsversuch zu übermitteln. Da wir mit jedem Aufruf einen neuen SESSION-Key erzeugen, besteht nicht die Gefahr, dass sich der Benutzer eine neue SESSION["Captcha_time"] besorgt, indem er eine weitere Instanz unseres Skripts startet. Er könnte dieses zwar tun, wäre dann allerdings aufgrund des geänderten SESSION-Keys nicht mehr in der Lage, das gestellte Captcha zu lösen.
Listing 2--------------------------------------------------------------------------------------------<?phperror_reporting(E_ALL);require_once("func_md5.php");require_once("func_image.php");session_start();if (!isset($_SESSION["Captcha_trys"])) $_SESSION["Captcha_trys"] = 0;if (!isset($_SESSION["Captcha_key"])) $_SESSION["Captcha_key"] = mk_string(15);if (!isset($_SESSION["Captcha_time"])) $_SESSION["Captcha_time"] = time();$attempts = 5;//Anzahl erlaubter Versuche$timeout_s = 120;//Sekunden bis zum Timeout$rnd_chars = mt_rand(5,8);//Anzahl Zeichen$a_errors = array();if (!isset($_POST["pasw"])) $_POST["pasw"] = FALSE; //verschlüsselter CODEif (!isset($_POST["valr"])) $_POST["valr"] = FALSE; //eingegebener CODEif (!isset($_POST["formimage"])) $_POST["formimage"] = FALSE; //Benutztes Bild//CAPTCHA generieren$Captcha_string = mk_string($rnd_chars);$image_name = mk_Captcha($Captcha_string);$enc_text = md5_encrypt($Captcha_string, $_SESSION["Captcha_key"]);$post_image_name = "antibrutforce_images/".$_POST["formimage"];//Formular Überprüfungif ( $_POST["pasw"] || $_POST["valr"] ) {$systemcode = $_POST["pasw"] ;$userinsertcode = strtolower($_POST["valr"]);$systemcode = strtolower(md5_decrypt($systemcode, $_SESSION["Captcha_key"]));//Sicherheits Abfragenif($_SESSION["Captcha_time"] < time()-$timeout_s) $a_errors[] = "Timeout";if($systemcode != $userinsertcode) $a_errors[] = "Falscher CAPTCHA Code";if(!is_file($post_image_name)) $a_errors[] = "Invalid CAPTCHA";if($_SESSION["Captcha_trys"] >= $attempts-1) $a_errors[] = "Es sind nur ".$attempts." Versuche erlaubt.";//falscher Code oder Timeout => neues CAPTCHA generierenif (count($a_errors) > 0) {foreach ($a_errors as $value)echo "-".$value."<br>";@unlink($post_image_name);//lösche Bild$_SESSION["Captcha_key"] = mk_string(15);//neuer Key$_SESSION["Captcha_time"] = time();//neue Zeit$_SESSION["Captcha_trys"]++;$Captcha_string = mk_string($rnd_chars);$image_name = mk_Captcha($Captcha_string);$enc_text = md5_encrypt($Captcha_string, $_SESSION["Captcha_key"]);} else {unlink($post_image_name);//lösche Bildunset($_SESSION["Captcha_key"]);unset($_SESSION["Captcha_time"]);echo "Richtiger Code - geschütze Inhalte freigeben etc.";die();}}?><form method="post" action="index.php"><img src="antibrutforce_images/<?=$image_name?>"><input type='hidden' name='pasw' value='<?=$enc_text?>'><input type='hidden' name='formimage' value='<?=$image_name?>'><INPUT type='text' name='valr'><input value="verify" type="submit"></form>
Fazit
Es gibt viele Argumente, die für die Verwendung von Captchas sprechen, um sensitive Inhalte zu schützen. Wichtig ist die Erkenntnis, dass ein Captcha nicht garantieren kann, dass es sich um einen realen Anwender und nicht um einen Bot handelt. Wenn Sie eine hundertprozentige Sicherheit benötigen, müssen Sie zum Beispiel Live Operator oder ähnliches einschalten.Ein neuer Trick zum halbautomatischem Lösen von Captchas besteht darin, diese von einem echten Benutzer unter der Vorspielung falscher Tatsachen lösen zu lassen. Einem Benutzer wird auf einer anderen Webseite beispielsweise kostenloses pornografisches Material angeboten, er soll jedoch zuvor ein Captcha lösen. In Wirklichkeit löst er aber ein Captcha eines Drittanbieters, welches lediglich auf einer fremden Webseite eingebunden wurde.
Für die meisten Anwendungsszenarien genügt aber ein grafisches Captcha, um Bots weitgehend auszuschließen. Die Switch-Anweisung unserer Funktion mk_image() ermöglicht es Ihnen, die verschiedensten grafischen Varianten zu erstellen. Natürlich unter Beachtung der genannten Regeln für sichere Captchas.
An dieser Stelle möchte ich noch auf die Webseite PWNtcha von Sam Hocevar hinweisen, welcher eine Vielzahl von Captchas geknackt hat. So zum Beispiel auch die Captcha-Grafik der beliebten Forumssoftware phpBB.
Weiterhin ist zu erwähnen, dass Sie bei der Verwendung von grafischen Captchas Menschen mit einer Sichtbehinderung gegebenenfalls von Ihren Dienstleistungen ausschließen. Alternativ gibt es auch noch akustische Captchas, dessen Erstellung aber ein Thema für sich darstellt.
Generell sollten Sie immer gut überlegen, ob ein Captcha wirklich notwendig ist oder dem Benutzer nur zusätzliche Hürden in den Weg legt.




