5 Min. Lesezeit

Spam-Abwehr ohne Captcha: Schwache Signale, starke Summe

Wie ein Kontaktformular auf Shared Hosting ohne Captcha spamfrei wurde: Score statt K.o.-Kriterium, umgekehrter Honeypot, eine Heuristik, die die Blacklist auf den Kopf stellt – und warum jeder Endpunkt seine eigene Abwehr braucht.

Eine Kundenseite, die ich betreue, ist eine statische Hugo-Site mit genau zwei dynamischen Stellen: einem Kontaktformular und einem Angebotsrechner, beide gegen ein kleines PHP-Skript auf Shared Hosting. Und wie jedes Formular, das lange genug im Netz steht, wurden beide gefunden – erst von den plumpen Bots, dann von den besseren.

Ein Captcha wollte ich nicht. Es bestraft die Falschen: Echte Interessenten klicken Ampeln an, während moderne Bots Captchas längst lösen lassen. Die Alternative, die sich über mehrere Wellen hinweg bewährt hat, ist unspektakulärer und interessanter zugleich: viele schwache Signale, die einzeln nie sperren – und erst in Summe kippen.


Das Grundprinzip: Score statt K.o.-Kriterium

Jede eingehende Anfrage durchläuft eine Reihe von Prüfungen. Keine davon entscheidet allein, jede vergibt nur Punkte. Erst wenn die Summe eine Schwelle überschreitet, wird blockiert – und jeder Block wird mit Grund geloggt, denn ohne Log lässt sich eine Heuristik weder verteidigen noch korrigieren.

$score = 0;
$score += honeypotCheck($post);      // umgekehrter Honeypot fehlt?
$score += timingCheck($post);        // zu schnell abgeschickt? Formular >12h alt?
$score += contentCheck($text);       // Inhaltsheuristik (dazu gleich mehr)
if ($score >= THRESHOLD) {
    logBlock($reason);
    return;                          // still verwerfen, kein Fehler-Feedback an den Bot
}

Ein paar der Signale, die sich als tragfähig erwiesen haben:

Der umgekehrte Honeypot. Der Klassiker ist ein verstecktes Feld, das Menschen leer lassen und Bots ausfüllen. Bots kennen den Trick inzwischen. Umgekehrt funktioniert es besser: Ein Feld, das per JavaScript gefüllt wird. Wer es leer abschickt, hat kein JavaScript ausgeführt – für einen Browser im Jahr 2026 ein deutliches Signal, aber eben nur ein Signal, keine Alleinsperre: Es gibt die Textbrowser- und Skriptblocker-Minderheit, und die schreibt manchmal die besten Anfragen.

Timing in beide Richtungen. Ein Mensch braucht länger als zwei Sekunden, um ein Formular auszufüllen. Aber auch die Gegenrichtung zählt: Ein Formular, das vor mehr als zwölf Stunden geladen wurde, ist mit hoher Wahrscheinlichkeit ein gehortetes – Bots sammeln Formular-Tokens und spielen sie später ab. Fehlt der Zeitstempel komplett, ist das selbst ein Bot-Signal, denn das legitime Formular sendet ihn immer mit.

Rate-Limit, datenschutzfreundlich. Pro IP gibt es ein Limit – aber die IP wird nur als SHA-256-Hash gespeichert, in einer flock-gesicherten JSON-Datei. Auf Shared Hosting gibt es kein Redis; eine Datei mit Lock tut es für die Größenordnung völlig, und in den Logs steht nie eine Klartext-IP.

DNS-Check gegen Backscatter. Das Skript verschickt eine Bestätigungsmail an den Absender. Wenn ein Bot als Absender beliebig@opfer-domain.de einträgt, wird die eigene Bestätigungsmail zur Spam-Schleuder an Unbeteiligte – Backscatter. Deshalb: erst prüfen, ob die Absender-Domain überhaupt Mail empfangen kann (MX- oder A-Record). Wenn nicht, wird die Anfrage zwar zugestellt, aber die Bestätigung unterdrückt.


Die Erkenntnis, die alles drehte: Die Blacklist verliert immer

Die bestehende Abwehr hatte eine Vokabelliste – die üblichen Verdächtigen, dazu Link-Erkennung und fremde Schriftsysteme. Dann kam eine Welle durch, die alles unterlief: sauberes Englisch, kein Link, kein einziges Wort der Liste. Score 0, gemessen. Die Mails landeten im Postfach, als wären sie echt.

Das Problem ist strukturell, nicht handwerklich: Eine Blacklist muss jede Variante vorhersehen, der Absender braucht nur ein unbekanntes Wort. Dieses Wettrüsten verliert die Liste immer, sie läuft der letzten Welle hinterher.

Also die Frage umdrehen. Statt „Enthält der Text etwas Verdächtiges?" nun: „Gibt es einen Grund, den Text für eine echte Anfrage zu halten?" Die Kundschaft der Seite schreibt Deutsch. Gesucht werden also Umlaute und deutsche Alltags- und Fachwörter – zwei Treffer genügen, um den Text als plausibel einzustufen. Ein Text ohne jeden Treffer sammelt Punkte.

Die Kalibrierung ist dabei wichtiger als die Idee selbst:

  • Erst ab 60 Zeichen – „Rückruf erbeten" muss ohne Nachweis durchkommen.
  • Nie als Alleinsperre – die Heuristik gibt 3 Punkte bei Schwelle 4. Ein englischsprachiger Interessent kommt weiter durch; erst ein zweites Signal kippt ihn über die Schwelle.
  • Gemessen, nicht geraten: die beiden durchgerutschten Spam-Mails gegen sechs echte Anfragen aus dem Archiv getestet. Beide Spam-Mails blockiert, alle sechs echten unverändert durchgelassen.

Jeder Endpunkt ist ein anderes Tier

Der Teil, auf den ich am wenigsten vorbereitet war: Das Kontaktformular war dicht – und der Angebotsrechner ließ weiter Spam durch. Mit exakt derselben Abwehr.

Die Ursache war keine zu lasche Einstellung, sondern ein struktureller Unterschied zwischen den Endpunkten. Das Kontaktformular hat ein Pflicht-Freitextfeld – die Inhaltsheuristik bekommt immer Text zu sehen. Beim Angebotsrechner ist jedes Freitextfeld optional; er besteht fast nur aus Auswahlfeldern. Ein Bot, der stumpf nur die Pflichtfelder POSTet, übergibt der Heuristik einen leeren String. Leerer String, Score 0, durchgewunken. Die beste Inhaltsanalyse ist wertlos an einem Endpunkt, der keinen Inhalt erzwingt.

Zwei Maßnahmen, zugeschnitten auf diese Struktur:

Auswahlfelder gegen eine Whitelist prüfen. Die gültigen Werte sind lange deutsche Strings mit Sonderzeichen („2–3 Personen", mit Halbgeviertstrich) – ein blind POSTender Bot rät sie nicht. Nebeneffekt, der vorher niemandem aufgefallen war: Bis dahin landete jeder beliebige übermittelte String ungeprüft im Betreff der Admin-Mail.

Kombinierte Signale statt neuer Einzelsperren. Eine inhaltsleere Anfrage allein ist von einer echten nicht unterscheidbar – ein eiliger Mensch, der nur Pflichtfelder ausfüllt, sendet dieselben Bytes wie der Bot. Deshalb sperrt „inhaltsleer" nie allein, sondern nur zusammen mit dem fehlenden JavaScript-Nachweis. Wer kein JavaScript hat, tippt erfahrungsgemäß Angaben; wer JavaScript hat, liefert den Nachweis automatisch. Erst die Kombination trennt sauber.

Und wieder: gemessen, am vollständigen Endpunkt-Ablauf. Vier Bot-Muster blockiert, vier echte Anfrage-Muster durchgelassen – darunter bewusst die Grenzfälle „nur Pflichtfelder, mit JavaScript" und „ohne JavaScript, aber mit Angaben".


Was ich mitnehme

  1. Kein Signal verdient eine Alleinsperre. Jedes einzelne hat eine legitime Minderheit, die es auslöst. Erst die Summe unabhängiger Signale trennt Bot von Mensch – genau deshalb funktioniert der Score-Ansatz, wo jede Einzelregel falsch-positiv würde.
  2. Whitelist-Denken schlägt Blacklist-Denken. „Beweise, dass du echt bist" altert besser als „ich erkenne alles Böse" – ob bei Formularwerten oder bei der Sprachheuristik.
  3. Die Struktur des Endpunkts bestimmt die Abwehr. Dieselben Regeln schützen zwei Formulare völlig unterschiedlich gut, wenn eines Freitext erzwingt und das andere nicht.
  4. Ohne Messung ist es Aberglaube. Jede Regel wurde gegen echte Spam-Mails und echte Anfragen aus dem Archiv getestet, jede Blockade wird mit Grund geloggt. Eine Heuristik, die man nicht misst, filtert irgendwann die eigenen Kunden – und man erfährt es nie.