How-To: Sichere Kontaktformulare bauen
11. September 2007
Viele Besitzer von Webseiten möchten eine Möglichkeit, dass sich Besucher einfach mit dem Besitzer in Kontakt treten können. Meist wird dafür ein kleines PHP-Skript verwendet, was man irgendwo bei Google findet.
Viele wissen jedoch nicht, dass viele dieser Skripte schwerwiegende Fehler haben:
Manche haben keinen CAPTCHA-Schutz und sind deswegen Opfer von Spambots. Andere brauchen register_globals und lassen sich deswegen leicht manipulieren. die meisten haben keine Reload-Sperre, weswegen Mails mehrmals an den Admin versendet werden können. Fast keines hat einen Spamfilter, wenn doch mal ein Bot durch ein CAPTCHA kommt.
Ich werde nun erklären wie man vielen dieser Problemen aus dem Weg gehen kann und ein sicheres Kontakt-Formular einfach selbst baut.
Die Basis
So könnte ein beispielhaftes Formular aufgebaut sein. In den folgenden Schritten wird dies weiter ausgebaut, die Änderungen lassen sich jedoch für fast jedes andere Formular übernehmen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta name="generator" content="PSPad and michfrm" /> <title>Kontakt</title> </head> <body> <h1>Kontakt</h1> <p>Vielen Dank, dass Sie mir eine Nachricht hinterlassen möchten. Hier haben Sie dazu Gelegenheit!</p> <form action="./kontakt.php" method="post"> <fieldset> <legend>Kontakt</legend> Name: <input type="text" name="d[name]" value="" /><br /> E-Mail Adresse: <input type="text" name="d[email]" value="" /><br /> Nachricht: <br /> <textarea name="d[message]" cols="80" rows="10"></textarea><br /> <input type="submit" name="submit" value="Absenden" /> </fieldset> </form> </body> </html> |
Bots
Automatisierte Bots fliegen gerne durch das Web und verursachen Traffic sowie nerven mit ihren sinnlosen Links zu Potenzmittel und Co. Der erste Schritt wird es sein, sehr dämliche Bots abzuhalten.
Ich unterscheide zwischen 2 Arten von Schutz gegen Bots. Der erste ist der passive Schutz, also Tricks mit denen sich der Bot selbst aufdeckt und so einfach geblockt werden kann. Der aktive Schutz sind dann Funktionen wie Keyword-Filter, die auch dann funktionieren wenn Menschen das Formular mit Müll füllen und absenden.
Das unsichtbare Eingabefeld
Ein netter Schutz gegen dumme Bots sind unsichtbare Eingabefelder. Manche Bots parsen die Seite und füllen dann jedes Eingabefeld, welches sie finden können. Dies können wir ausnutzen
Im Stylesheet der Seite dies Eintragen (kann auch umbenannt werden);
1 2 3 | <style type="text/css"> .field { display: none; } </style> |
und dann im Formular:
1 | <input type="text" name="d[lastname]" value="" class="field" /> |
Die Falle ist gelegt, jetzt muss nur noch ein Mechanismus zum Schnappen rein. Außerdem prüft dieser gleich, ob das unsichtbare Feld definiert wurde. Falls nicht, soll das Skript abbrechen. Dieser Teil umbedingt erst in die Abfrage ob das Formular abgesendet wurde, sonst ist ein Aufruf nicht mehr möglich.
if ( isset($_POST['d']['lastname']) ) { if ( !empty($_POST['d']['lastname']) ) { // Die Falle hat zugeschnappt! // Nachricht, dass verschicken erfolgreich war die('Trap invisible field'); } } else { // Komisch, wo ist das Feld hin? die("Trap invisible field missing"); }
Wieso, dass das Verschicken erfolgreich war? Damit ja kein Verdacht geschöpft wird, dass es einen Spamschutz gibt.
Captcha!
Da ich hier schon mal dazu geschrieben habe, bleibt dieser Absatz etwas kürzer
Are you a human?
Ein weiterer netter Schutz sind Checkboxen. Dazu positioniert man 2 Boxen in seinem Formular. Einmal mit “Ich bin ein doofer Bot” und einmal “Ich bin ein Mensch” (je nach Laune auch anders) und prüft, ob die Eingaben auch plausibel. Total dumme Bots markieren keine Checkboxen und fallen durch. Manche Bots markieren alle Boxen und fallen auch durch. Zur weiteren Verwirrung heißen die beiden Checkboxen “iamabot” und “iamhuman”, jedoch vertauscht.
1 2 | <input type="checkbox" name="iamahuman" value="1" />Ich bin ein doofer Bot<br /> <input type="checkbox" name="iamabot" value="1" />Ich bin ein Mensch<br /> |
Für Leute die nicht wissen wie Checkboxen intern arbeiten: Wenn eine Checkbox aktiviert wird wird diese auch per POST übertragen, falls sie nicht markiert ist sieht es bei der Auswertung so aus, dass es sie gar nicht gegeben hat. Lässt sich auch bequem nutzen.
Und hier noch um die Falle zuschnappen zu lassen:
1 2 3 4 5 | if ( !isset($_POST['iamabot']) || isset($_POST['iamahuman']) ) { // Der Bot war wirklich doof :D // Nachricht erfolgreich verschickt - nach /dev/nul } |
Mehrmals schicken? F5!
Die meisten Browser schicken bei einem F5 POST-Daten gleich nochmal, im Falle des Kontaktformulars werden mehrere Mails verschickt. Auch das muss nicht sein!
Einfach zu lösen ist dies auf 2 Arten: entweder benutzt man PHP-Sessions um eine zufällige ID zu erstellen und an das Formular zu binden. Vorteil ist, dass Bots die blind auf Seiten schießen sofort gekickt werden. Nachteil: Benötigt Cookies, der Key kann verfallen. Ich denke, letzeres lässt sich verschmerzen.
Die andere Lösung ist die Speicherung der IP. Bei einem Absenden prüft das Skript zuerst, ob die aufrufende IP z.B. in der letzten Stunde bereits das Formular abgeschickt hat. Die Liste lässt sich per Cron oder mit einer Prüfung bei jedem Aufruf des Formulars einfach putzen. Vorteil ist dass es auch mit Textbrowsern geht die keine Cookies verstehen, jedoch kann man sich auch einfach neu einwählen und erhält so eine neue IP-Adresse.
Ich empfehle deswegen beide wenn man wirklich sehr von Bots geplagt ist
Die Session-Methode
Ich empfehle zuerst das Kapitel zu den Sessions im PHP Manual zu lesen, falls nicht klar sein sollte wie diese funktionieren, da ich darauf nicht näher eingehen werde.
Zuerst einmal muss dem Benutzer eine SID zugeordnet werden, dies geschieht mit session_start(). Danach wird geprüft, ob der Benutzer bereits etwas verschickt hat und bekommt statt dem Formular nur einen Hinweis. Falls er das Formular verschickt (und es darf) wird seine Session aktualisiert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // Keine Ausgaben vor diesem Befehl! session_start(); if ( isset($_SESSION['last_submit']) ) { // Wenn sein letzter submit nicht länger als eine stunde zurück liegt // bekommt er einen Hinweis if ( $_SESSION['last_submit'] > time() - 3600 ) { die('Sie können diese Funktion nur einmal in der Stunde benutzen!'); } } // Formular abgeschickt? if ( isset($_POST['submit']) ) { // Hier könnte der Schutz mit dem unsichtbaren Formularfeld stehen // Aktuelle Zeit eintragen $_SESSION['last_submit'] = time(); // Versenden echo("Sende..."); } |
Die IP-Methode
Der Aufbau der Methode ist ähnlich der mit der Session. Statt in die Session werden die Daten mit dem Timestamp in eine Datei geschrieben. Ich habe mir überlegt statt einer plain Datei mit Trennern ein Array zu speichern, ähnlich wie in meinem Caching-Tutorial.
Vorteile: Schnellere und leicher einzulesen
Nachteil. Braucht viel mehr Platz
Zuerst einmal müssen wir auslesen, ob der Benutzer schon mal da war. Falls ja, halten wir ihn ab das Formular zu benutzen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Wohin die IP-Adressen sollen und die IP des Besuchers $file = dirname(__FILE__) . '/ip.php'; $ip = $_SERVER['REMOTE_ADDR']; // Haben wir schon eine Datendatei? Falls nein: erstellen! if ( !file_exists($file) ) { touch($file); $data = array(); } else { include $file; } // Haben wir den User in der Datei? if ( isset($data[$ip]) ) { // Wenn sein letzter submit nicht länger als eine stunde zurück liegt // bekommt er einen Hinweis if ( $data[$ip] > time() - 3600 ) { die('Sie können diese Funktion nur einmal in der Stunde benutzen!'); } } |
Das bringt aber noch ziemlich wenig, solange nichts in die Datei rein kommt. Das erledigen diese Zeilen:
1 2 3 4 5 | // Aktuelle Zeit eintragen $data[$ip] = time(); $content = "<?php\n /* Kontakt Form IP Table */\n \$data = " . var_export($data, true) . ";\n?".'>'; file_put_contents($file, $content); |
Der Keyword-Filter
Wow, wir haben schon sehr viele Tricks benutzt um Bots loszuwerden. Was jedoch, wenn es einer über diese Barrikaden schafft und ein Formular absenden kann ohne durch den passiven Schutz zu fallen? Schauen wir, ob er Spam verschickt.
Das lässt sich recht hübsch mit strpos erledigen:
1 2 3 4 5 6 7 8 9 10 | // Keyword-Filter $bad_words = array('Viagra', 'Kenia'); // Weitere bei Bedarf eintragen foreach ( $bad_words as $word ) { if ( strpos($message, $word) !== false ) { // Gefunden! Ab damit! die("Trap keyword-filter: {$word}"); } } |
Da das jetzt so schön war, gibt es jetzt das komplette Formular mit. Doch eines fehlt: es kann keine Mails versenden, das war auch nicht Inhalt dieses Artikels
Sicheres Kontaktformular mit Session-Sperre | Sicheres Kontaktformular mit IP-Sperre
Ich würde mich über Kommentare über den Artikel freuen, so sehe ich auch dass meine Schreibarbeit (knapp 2 Stunden Schreiben und Recherche) nicht völlig umsonst war
Ich werd mich dann ma an diesem script für meine neue seite bedienen, danke
Hi, dein Artikel ist sehr schön beschrieben und auch ich werde mich gern daran bedienen
Mfg Quaacks ^^