Ein Verfahren zur SPAM-Abwehr an Webformularen

Einleitung zum Thema

Bestimmt kennen Sie das Phänomen: Seit längerer Zeit haben Sie ein Gästebuch online, aber seit Kurzem wird dieses mit Müll zugespam't. Besonders lästig sind Einträge, die Links zu dubiosen Seiten enthalten, SPAMer versuchen damit, ihren Rank in Suchmaschinen zu verbessern, indem sie Links zu ihren Seiten in möglichst vielen Gästebüchern anbringen.

Der vorliegende Artikel beschreibt, wie Sie sich gegen die Machenschaften der SPAM-Bots schützen können, er beschreibt darüber hinaus, wie Sie mit unliebsamen Einträgen in Ihr Gästebuch leicht fertig werden.

Hinweise für Webmaster

Um es vorweg zu nehmen: Ein Gästebuch zu betreiben ist eine freiwillige Angelegenheit.

Jeder Webmaster, der freiwillig ein Gästebuch betreibt, sollte sich sich darüber im Klaren sein, dass eine derartige Einrichtung gerne ein Angriffsziel für SPAM-Bots ist. Ich zeige Ihnen hier, inwiefern Sie als Webmaster Verantwortung übernehmen können, um die Verbreitung von SPAM über Ihre WebSite zu unterbinden.

Die erste Amtshandlung

... besteht darin, dass Sie dafür sorgen, dass neue Einträge in Ihr Gästebuch nicht gleich sichtbar für die ganze Welt sind, sondern erst, wenn Sie Ihre Zustimmung dazu gegeben haben. Sie müssen neue Einträge also erst lesen, bevor Sie diese veröffentlichen.

Außerdem ist es sinnvoll, HTML-Code komplett abzuschalten, so kann ein SPAMer keine a-HREF-Links erstellen. Friedliebende Besucher können somit zwar auch keine HTML-Links erzeugen, aber Dritte werden den Weg über Copy & Paste nicht scheuen, sofern sie an einer WebSite ein wirkliches Interesse haben.

Bitte sind Sie so fair, den Besucher auf beide Umstände hinzuweisen, also, dass HTML nicht erlaubt ist und dass das Veröffentlichen eines neuen Eintrages erst nach Ihrer Prüfung erfolgt.

Bots erkennen und handeln

Während ein Bot einfach nur POSTs an ausgesuchte Webseiten sendet, weicht das Verhalten eines menschlichen Surfers (HS, Human Surfer) erheblich davon ab.

Ein HS lädt die Seite erst in den Browser und bevor er was postet, vergeht eine gewisse Zeit, denn das was ein HS posten möchte, muss er i.d.R. erst schreiben - im Gegensatz zu einem Bot, der vorgefertigten SPAM schon parat hat zum Posten.

Speziell der Fall "Gästebuch" macht das deutlich: Ein HS liest meistens erst im Gästebuch das was Andere geschrieben haben, bevor er selbst was schreibt. Bis dahin vergehen mindestens 10 Sekunden, ein Bot hingegen versucht, innerhalb von nur einer Sekunde, seinen SPAM zu posten.

Cookies sind hervorragend dazu geeignet, einen HS von einem Bot zu unterscheiden, denn gerade mit Cookies lässt sich das Surfverhalten eines Besuchers gut verfolgen. Geschickt ist es, ein Gästebuch so zu programmieren, dass die Bots gar nicht erst mitkriegen, dass Cookies im Spiel, d.h., zum erfolgreichen Schreiben eines neuen Eintrags überhaupt erforderlich sind.

Der hier vorgeschlagene Lösungsweg über einen Cookie hat für den friedliebenden Besucher außerdem den Vorteil, dass dieser nichts davon mitbekommt, bzw., keine weiteren Umstände damit hat.

Mit Kenntnis dieser Fakten lässt sich nun das Skript für ein Gästebuch wie folgt anpassen:

Praktische Realisierung

Mit dem Aufruf des Gästebuchs ohne Parameter (zum Anschauen) etabliert das Sckript eine Session, die darin besteht, dass auf dem Rechner des Besuchers ein Cookie abglegt wird und gleichermaßen der Wert dieses Cookies serverseitig in eine Datenbank (DB) geschrieben wird. Gleichfalls wird der Zeitstempel in der Datenbank gespeichert und ein drittes Feld als Zähler mit dem Wert "0".

Dieser Cookie gilt lediglich für eine Browser-Sitzung (Session-Cookie).

Anhand des Zählers ermittelt das Script, wieviele POSTs der Besucher bereits gemacht hat (empfohlen: ein POST pro Session).

Mit dem Wert des in die DB geschriebenen Zeitstempels ermittelt das Script die Zeitspanne, die nach dem ersten Aufruf des Gästebuchs bis zu einen POST vergangen ist. Im Falle eines HS sind seither praktisch mindestens 10 Sekunden verstrichen, ein Bot würde jedoch schonmal viel eher einen POST absenden (woher soll der auch wissen, dass er 10 Sekunden warten muss).

Das Skript ist außerdem so geschrieben, dass bei einem purem POST (Aufruf mit Parametern) seitens des Webservers kein Cookie-Header gesendet wird. Damit wird erreicht, dass ein Bot gar nicht erst mitbekommt, dass überhaupt Cookies im Spiel sind. Selbst wenn, tappt der Bot im Dunkeln, weil er nicht weiß was für einen Cookie (Name / Wert) er im Header eines POSTs mitgeben muss um erfolgreich zu sein.

Eine Tabelle, welche díe Sessions speichert, könnte so aussehen, siehe untenstehend:

describe sessions;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| ky    | varchar(100) |      | PRI |         |       |
| ts    | bigint(20)   |      |     | 0       |       |
| cnt   | int(11)      |      |     | 0       |       |
+-------+--------------+------+-----+---------+-------+

Bei einem POST wird geprüft:

Im Falle der Verneinung einer dieser Fragen erfolgt kein "commit", die Parameter dieses POSTs werden nicht in die DB eingetragen. Ansonsten wird der Zähler "cnt" um den Wert eins hochgesetzt und der Vorgang abgeschlossen.

Das Feld "ky" beinhaltet einen eindeutigen Key zur Idendifizierung einer Session. Es sollte dafür gesorgt sein, dass dieser Key absolut eindeutig ist. Mit PERL habe ich dafür eine Lösung gefunden, die selbst unter 500.000 Funktionsaufrufen keine Duplikate generiert:

#!/usr/bin/perl

###########################################################################
use strict;
use Digest::MD5 qw(md5_hex); 
########################################################################### 
sub makeSID{

	my @chars = ('A'..'Z', 'a'..'z', 0..9, '+', '-');
	my $len = scalar(@chars);

	my $id = time();
	$id .= $$;
	for(my $i = 0; $i < $len; $i++){
		$id .= $chars[int(rand($len))];
	}
	$id = substr($id, 0, $len);
	$id = md5_hex($id);
	return $id;
}

Die Felder "ts" und "cnt" sind der Zeitstempel bzw. der Zähler für Posts. Bleibt schließlich und letztendlich noch die Frage, wer die Session-Table aufräumt, damit diese nicht ins Unermessliche anwächst.

Das kann z.B. mit einer Eintrag in die Crontab erfolgen. In meinem Fall habe ich jedoch eine andere Lösung gefunden: Das Bereinigen der Session-Table erledigt mein Seitenaufrufzähler als Nebenjob, der löscht alle Einträge, die älter als 100 Minuten sind.

Alternative zum Verfahren mit Cookie

Zu dem hier vorgestellten Verfahren gibt es eine Alternative:

Das Mailformular wird dynamisch erzeugt als CGI, das ist der erste GET-Request der auf das Formular erfolgen muss. In diesem GET-Request macht das CGI-Script Folgendes:

Der Besucher füllt nun das Formular aus, wofür er eine gewisse Zeit braucht und klickt dann auf [Absenden], womit die Formulardaten per POST an das serverseitige CGI-Script übertragen werden. Dabei ist auch das hiddenField mit dem Sessionkey.

Das CGI-Script auf dem Server nimmt die Formulareingaben entgegen und prüft:

Sofern beide Bedingungen erfüllt sind, wird das Formular erfolgreich verarbeitet (Gästebuch, Mail...) und der Sessionkey in der DB gelöscht. Die Tabelle mit den Sessions ist von Zeit zu Zeit von ungenutzten Einträgen zu bereinigen (ein Job, der per AJAX auch vom Mailformular aus gleich mit erledigt werden kann).

Last-Modified: Tue, 22 Jun 2010 19:50:29 GMT