Spambots? Ist mir doch egal!

Dieser Artikel erklärt einige Strategien und Methoden, um Spambots und deren Hinterlassenschaften von der Webseite zu verbannen.

Diesem Thema habe ich mich zwar bereits 2006 angenommen (Artikel auf developia.de), aber es hat über die Jahre sicher nicht an Bedeutung verloren.

So nutze ich die Gelegenheit etwas Inhalt zu schaffen und vergleiche, was sich in den letzten knapp vier Jahren so getan hat.

Spambots? Ist mir doch egal!

Spam ist sicherlich jedem, der das Internet nutzt ein Begriff. Es handelt sich dabei um Nachrichten, die dem Empfänger meist elektronisch, aber immer unerwünscht zugestellt werden und oft werbenden Inhalt haben. Diese Art der Belästigung weitet sich u.a. durch die Verbreitung von Standardsoftware, wie z.B. Gästebücher, Foren, Blogs etc. nun auch auf Webpages aus.

Häufiges Ziel dieser Aktionen ist es, massenhaft eigene Links auf fremden Seiten zu platzieren, um dadurch das Ranking der beworbenen Seite bei Suchmaschinen zu verbessern. Die Seite soll wertvoller erscheinen, da so viele verschiedene Seiten darauf verlinken. Nicht zuletzt geht es natürlich auch um Werbung.

Was lässt sich nun dagegen tun, dass so genannte Spambots die eigene Webseite in eine Müllkippe verwandeln?

Um heraus zu finden, ob Eingaben von einem Menschen oder von einem Programm gemacht worden sind, lassen sich Captcha einsetzen. Leider sind einzelne Captcha keine Wunderwaffe gegen die Werbeflut aber eine Strategie. Verschiedene begleitende Maßnahmen können den Schutz verbessern.

Die folgenden Prüfmethoden lassen sich sowohl einzeln oder besser noch in einem Gesamtkonzept einsetzen. Voraussetzung für die Arbeit sind grundlegende Programmier- und PHP-Kenntnisse. Selbstverständlich lassen sich die beschriebenen Techniken auch mit anderen Sprachen umsetzen (Perl, Java etc).

1. Generierte Bilder/Captchas

Das wie ich glaube bekannteste Captcha beruht auf generierten Grafiken, die eine für Maschinen möglichst unlesbare Zeichenkette enthalten, die jedoch für einen Menschen kein Problem darstellen sollten. Gerade bei diesen Bildern hat sich in den letzten Jahren einiges getan.

Die Bilder sind mit zunehmender Qualität der heutigen OCR-Software (Optical Character Recognition) immer komplexer und dadurch auch für den Menschen schwieriger zu lesen. Die Erkennung der Bilder lässt sich jedoch auch recht leicht umgehen, indem die Erkennung an Menschen auf anderen Seiten delegiert wird.

Gerade auf Seiten, die sich um eine möglichst barrierefreie Bedienung bemühen, ist das ein Problem. Als Alternative werden zunehmend Audio-Captcha angeboten. Dabei werden dem Benutzer die geforderten Eingaben vorgelesen.

Captcha ist, meiner persönlichen Meinung nach, nach wie vor störend und nicht die eleganteste Methode. Wenigstens wurde dem Abtippen der Bildschnipsel in den letzten Jahren durch ReCAPTCHA ein gewisser Mehrwert verliehen.

Bei der Verwendung von Captcha ist es wichtig, die Bilder so aufzurufen, dass der Bot die Zeichenkette nicht ausspähen kann.

<img src="captcha.php?string=smwm">

Der im Beispiel gezeigte Aufruf wäre also nicht wirklich sinnvoll, da der Bot den String mittels RegEx aus der Bild-URL lesen könnte. Besser ist es, den String (oder die jeweilige ID bei gecachten Bildern) in einer Session zu speichern und dann in allen Scripten zu verwenden.

An dieser Stelle rate ich ausdrücklich von der Verwendung von Cookies ab, da die Informationen beim Anwender gespeichert werden und so auch manipuliert oder verarbeitet werden können.

Benötigte Kenntnisse/Funktionen:

2. Einfache Aufgaben

Eine weitere inzwischen recht verbreitete Methode der Überprüfung ist es, dem Anwender eine einfache Aufgabe zu stellen und die Eingabe dann mit der richtigen Antwort zu vergleichen.

Am einfachsten sind kleine Rechenaufgaben (z.B. 3+7, 2*3 etc.), denn sie lassen sich schnell und leicht generieren und selbst ein Grundschüler sollte die Antwort kennen.

Jedoch könnte ein Bot auch relativ leicht so programmiert werden, dass er die Aufgabe aus dem HTML-Quelltext ausliest, löst und das Ergebnis verwendet. Hier wäre die Kombination mit generierten Bildern vorstellbar, die die Aufgabe enthalten.

Etwas komplexer und sicherer (auch ohne generierte Bilder) ist die Frage nach Themen, die ein Computer nicht ohne weiteres lösen kann, da sie Textverständnis voraussetzen, z.B. “welcher Tag ist Heute?”, “Wie lautet der dritte Buchstabe im Wort Spambot?” etc. Zu bedenken ist dabei auch, dass der Programmieraufwand größer wird und zu schwierige Fragen evtl. auch den einen oder anderen Besucher daran hindern, einen netten Eintrag im Gästebuch zu hinterlassen.

Der stark vereinfachte Code zum Erstellen einer Aufgabe könnte etwa so aussehen:

srand (time ());
$zahl1 = rand (1, 9);
$zahl2 = rand (1, 9);
$_SESSION['aufgabe'] = $zahl1 . '+' . $zahl2;
$_SESSION['ergebnis'] = $zahl1 + $zahl2;

Benötigte Kenntnisse/Funktionen:

3. Session-Counter

Für die meisten Überprüfungen ist eine Session sinnvoll oder notwendig. Diese lässt sich auch gut zum Zählen der Seitenaufrufe nutzen. Da die meisten moderen Anwendungen ohnehin mit einer Session arbeiten, stellt diese Möglichkeit keinen großen Mehraufwand dar.

Die wenigsten Bots werden wohl zuerst das Formular aufrufen und dann die Seite, die die Eingaben verarbeitet/speichert und selbst wenn, dann ist der geforderte Aufwand schon wieder etwas größer.

Ein menschlicher Benutzer hingegen wird wahrscheinlich beides tun, d.h. sein Zählerstand ist dann größer als eins.

session_start ();
if (isset ($_SESSION['count'])) {
  $_SESSION['count'] = 0;
}
$_SESSION['count']++;

if ($_SESSION['count'] > 1) {
  // mach was
} else {
  die ();
}

Benötigte Kenntnisse/Funktionen:

4. Zeitmessung

Bekanntlich sind Menschen langsamer als Maschinen und brauchen für gewöhnlich für die Eingabe der Daten in das Formular eine gewisse Zeit. Das können wir uns nun zu Nutzen machen und die Zeitdifferenz zwischen der Anzeige des Formulars und dem Eingang der Daten messen.

Benötigte Kenntnisse/Funktionen:

5. Token (vgl.Security-Token)

Eine, wie ich finde, recht einfache, sichere und fehlerunanfällig Methode ist der Einsatz von Token. Token sind serverseitig generierte Einwegschlüssel, die als Datei oder besser noch in der Session des jeweiligen Benutzers gespeichert und gleichzeitig in einem verstecktem Formularfeld übergeben werden.

Einfache Token sind MD5-Summen aus beliebigen Werten.

session_start ();
$_SESSION['token'] = md5 (time ());

Die Token können beliebig generiert werden, dabei sollte darauf geachtet werden, dass der Schlüssel möglichst schwer zu erraten ist.

Im so sähe das HTML-Formular aus…

…im PHP-Script:

<form action="..." method="post">
  <input name="token" type="hidden" value="<?php echo $_SESSION['token']; ?>" />
  <!-- weitere Formularelemente -->
</form>

…in der HTML-Ausgabe:

<form action="..." method="post">
  <input name="token" type="hidden" value="ed740e1aba2df..." />
  <!-- weitere Formularelemente -->
</form>

Die Abfrage funktioniert dann später so:

if (isset ($_POST['token']) && $_POST['token'] == $_SESSION['token']) {
  $_SESSION['token'] = md5 (time ()); // neuer Schlüssel
  // mach was
}

Zu beachten ist, wann und an welcher Stelle der Token-Schlüssel überschrieben wird, da es sonst die Prüfung immer fehlschlägt.

Benötigte Kenntnisse/Funktionen:

6. JavaScript

Eine aus meiner Sicht etwas kritische Methode ist, zwei versteckte HTML-Formularelemente mittels JavaScript clientseitig anzugleichen und dann serverseitig auf Identität zu überprüfen. Diese Methode ist deshalb kritisch, weil Benutzer ohne aktives JavaScript im Browser keine Aktion ausführen können. Die Akzeptanz gegenüber JavaScript ist gefühlt zwar besser geworden, aber dennoch ist das nicht 100%ig zuverlässig.

Es bietet sich hier an, einfach die Token-Prüfung zu erweitern, da wir bereits ein verstecktes “Zufallsfeld” haben, dessen Wert auf dem Server gespeichert ist.

Die Erweiterung…

…im PHP-Script:

<form action="..." method="post">
  <input name="token_c" type="hidden" value="<?php echo md5(0); ?>" />
  <!-- weitere Formularelemente -->
</form>

…in der HTML-Ausgabe:

<form action="..." method="post">
  <input name="token_c" type="hidden" value="cfcd208495d56..." />
  <!-- weitere Formularelemente -->
</form>

Beim Absenden des Formulars wird des JavaScript-Handler “onSubmit” aufgerufen und führt die darin enthaltene Anweisung aus. In unserem Fall bekommt das Element “token_c” den Wert vom Element “token” zugewiesen. Auf der Serverseite werden dann beide Werte verglichen.

if ($_POST['token'] == $_POST['token_c']) {
  // mach was
}

Benötigte Kenntnisse/Funktionen:

7. Bezeichner verschlüsseln

So weit, so gut! Das alles kann schon einen ausreichenden und erfolgreichen Schutz gegen Spambots bieten. Wie nicht? Der Bot meint es ernst? Ok, wir können auch anders…

Bislang stehen alle Bezeichner im Klartext im HTML-Quelltext und so könnte z.B. die JavaScript-Prüfungen auch durch ein RegEx im Bot-Programm ersetzt werden, ebenso kennt das Bot-Programm wahrscheinlich auch alle anderen Feldnamen/-bezeichner und aus diesem Grund werden jetzt die Eingabefelder verschlüsselt.

Wir brauchen nun eine Funktion, die für uns einheitlich und flexibel die Bezeichner “verschlüsselt”. Der verschlüsselte Bezeichner ist ein Ausschnitt aus einem mit md5() erzeugtem String, auf der Grundlage zufälliger, persönlicher, konstanter und variabler Werte.

Der zufällige Wert wird, wie es der Name vermuten lässt, zufällig erzeugt und als “seed” in der Session gespeichert. Persönliche Werte der Benutzers sind z.B. $_SERVER['HTTP_USER_AGENT'], diese variieren evtl. je nach verwendeten Browser, bleiben aber im “Normalfall” während der Session konstant.

Die zu 90% konstanten Werte könnten $_SERVER['SERVER_NAME'] o.ä. sein, solange nicht verschiedene Domains auf die Datei zugreifen oder ein fest vorgegebener String. Die Variable ist letztlich unser Parameter $key.

Zuerst initialisieren wir die Session mit “seed”:

session_start ();
if (!isset ($_SESSION['seed'])) {
  srand (time ());
  $_SESSION['seed'] = md5 (rand (0,250000) . time());
}

Und unsere PHP-Funktion könnte nun so aussehen:

function scramble_key ($key, $length=10) {
  $tmp = $_SESSION['seed'];
  $tmp .= $_SERVER['HTTP_USER_AGENT'];
  $tmp .= $_SERVER['SERVER_NAME'];
  $tmp = $key;
  return substr (md5 ($tmp), 0, $length);
}

Den Einbau ins Formular demonstriere ich an dem bekannten “Token”-Formular:

Und so sähe das dann aus…

<form action="..." method="post">
  <input name="<?php echo scramble_key ('token_c'); ?>" type="hidden" value="<?php echo md5(0); ?>" />
  <!-- weitere Formularelemente -->
</form>

…in der HTML-Ausgabe:

<form action="..." method="post">
  <input name="9fefa8db9c" type="hidden" value="cfcd208495d56..." />
  <!-- weitere Formularelemente -->
</form>

Abgefragt werden die Eingaben dann fast wie immer:

$form_token = scramble_key ('token');
$form_token_c = scramble_key ('token_c');

if ($_POST[$form_token] == $_POST[$form_token_c] {
  // mach was
}

Wichtig ist, wenn diese Verschlüsselung zusammen mit der JavaScript-Prüfung verwendet wird, dass der Schlüssel mit einem Buchstaben beginnt.

Dafür reicht es die Funktion scramble_key minimal zu verändern:

function scramble_key ($key, $length=10) {
  $tmp = $_SESSION['seed'];
  $tmp .= $_SERVER['HTTP_USER_AGENT'];
  $tmp .= $_SERVER['SERVER_NAME'];
  $tmp = $key;
  return 's' . substr (md5 ($tmp), 0, $length-1);
}

Bei der Verwendung von Frameworks wie z.B. dem Zend Framework können verschiedene Prüfungen relative zentral (z.B. in den Decorators und Helpers) verbaut werden, so dass sie beim täglichen Gebrauch kaum mehr Arbeit verursachen.

Die Möglichkeit der Captchas wurde sogar schon mit Zend_Form_Element_Captcha umgesetzt und auch für unseren Token gibt es mit Zend_Form_Element_Hash eine fertige Komponente.

Benötigte Kenntnisse/Funktionen:

Zusammenfassung/Vergleich

Capcha lässt sich inzwischen schnell und einfach in Anwendungen einbauen und auch die Prüfung ist einfach. Der Schutz hängt von der Komplexität der generierten Bilder ab und wird dadurch auch zunehmend schwerer für Menschen zu lesen.

Dem Benutzer irgendwelche Aufgaben zu stellen ist ebenfalls recht einfach umsetzbar und kann beliebig komplex erweitert werden. Gut kombinierbar mit generierten Bildern.

Session-Counter und Zeitmessung stellen aus meiner Sicht nur einen mittleren Schutz dar, da beides auch relativ einfach in einem Bot-Script umgangen werden kann. Etwas besser schützt dagegen ein Token. Diese Methode verhindert nebenbei auch mehrfache Verarbeitung der Daten, wenn der Benutzer das Formular mehrfach abschickt.

Das Formular mit JavaScript zu schützen ist ebenfalls einfach und kann bei Bedarf noch etwas erweitert werden. Benutzer mit deaktiviertem JavaScript werden dabei allerdings ausgesperrt. Die Methode lässt sich gut mit den anderen kombinieren. Wenn das Prinzip erkannt wird, lässt sich der Schutz u.U. auch von einem Bot umgehen.

Die Feldnamen zu verschlüsseln rundet das Konzept ab. Der Aufwand dafür hängt hauptsächlich von der zu schützenden Anwendung ab und in Frameworks lässt sich dieser Ansatz meist zentral implementieren.

Da die Formulare durch die verfremdeten Namen schwerer lesbar sind, sollte der Schutz während Entwicklungsarbeit abschaltbar sein.

Fazit

Die beschriebenen Methoden bieten alle keinen 100%igen Schutz, da sich jede Sperre mit entsprechendem Aufwand. Jede Schutzmanahme stellt nur einen gewissen Mehraufwand dar, aber eben dieser Mehraufwand wird die Zahl der potentiellen Störenfriede bereits merklich senken.

Ebenso sollten die verschiedenen Möglichkeiten als Bausteine verstanden werden, die sinnvoll je nach den Gegebenheiten der jeweiligen Anwendung eingesetzt und angepasst werden sollten. Eine Sperre für Bots sollte einen menschlichen Besucher so wenig wie möglich stören.

Schlagwörter: Adapter, Amazon, Animation, Annotations, Anonyme Klasse, Ant, Apache, API, Array, ArrayAccess, Attachment, AutoLoader, Bedienung, Bedingung, Benchmark, Bildbearbeitung, BOM, Bootstrap, Bot, Byte Order Mark, Callback, CamelCase, Canvas, Captcha, Cheatsheet, CLI, Closure, Cloud, CodeSniffer, Community, Comparator, Contest, Controller, Converter, CouchDB, Countable, Cronjob, CSV, CustomLibrary, Custom_Model, Data Mapper, Datei, Datenbank, Datenstruktur, Datentypen, Dating, Decorator, Dekorierer, Design Patterns, Dump, Duplikat, each, Eclipse, Entwicklung, Entwurfsmuster, Enum, Erweiterung, Eventhandling, Exception-Handling, Extension, Factory, Fehler, Flash, Foreach, Formatierung, Formular, Funktion, Futon, Header, HTML5, HTTP, IDE, If, Implementierung, InnoDB, Interceptor, Interface, isset, Iterator, Java, JavaScript, jQuery, Konfiguration, Konsole, Kontrollstruktur, kopieren, Late Static Binding, Layout, Linux, Listeners, Logging, Löschen, Magento, Magic Methods, Marketing, Methode, Model, MVC, MySQL, NetBeans, Objekt, Observable, Observer, OOP, Operator, Parameter, Partnersuche, Performance, PHP, phpMyAdmin, PHPUnit, Plugin, Proxy, Qualitätssicherung, Query, Reflection, Request, Response, Rest-API, Rockstar, Routing, S3, Samba, Scheifen, Schleife, Schutz, Secure Shell, Selbstreferenz, Shop, Sicherheit, Sicherung, Singleton Pattern, SOAP, Sortierung, Sourcecode, Spam, Speicherproblem, Spickzettel, SPL, SSH, Statement, Stellvertreter, Strategy Pattern, Stream, String, Sun VirtualBox, Support, Switch, Symfony, Symfony2, Symfony Live, Tag, Template, Template Method, Ternär Operator, Testing, Thumbnail, Tool, Tour, Twig, Type-Cast, Umwandlung, Underscore, unset, Vererbung, Verzweigung, Video, Videospiel, Virtualisierung, Visitor Pattern, Vorschaubild, walk, Webserver, Webservice, Weiterleitung, Wrapper, Youtube, Zeitsteuerung, Zend Framework, Zend_Cloud, Zend_CodeGenerator, Zend_Http_Client, Zend_Service, Zugriffsmethode