Ein Formular ohne Captcha vor Spam schützen: von Akismet zu meinem eigenen Spamschutz
Im November 2016 veröffentlichte ich in meinem WordPress-Blog einen Beitrag mit dem Titel „Wie richtet man Akismet ein?“. Ich erklärte darin, wie man das mit WordPress mitgelieferte Anti-Spam-Modul aktiviert, auf der offiziellen Website einen API-Schlüssel holt und dann zwischen dem direkten Löschen unerwünschter Kommentare und einem Prüfordner von vierzehn Tagen wählt. Ich teilte sogar einen kleinen Filter, den man in functions.php einfügt, um diese Frist auf dreißig Tage zu verlängern, mit einem Satz, der eigentlich schon alles zusammenfasst: „wir sind nie vor einem falsch Positiven sicher.“ Zehn Jahre später schütze ich keine Blogkommentare mehr, sondern die Formulare meiner eigenen Website, gebaut auf Pulsar, meinem PHP-Framework. Und dieser Reflex von 2016, eine zweifelhafte Nachricht lieber zu behalten als eine gute zu verlieren, ist zur zentralen Regel des Spamschutzes geworden, den ich am Ende selbst geschrieben habe.
Was Akismet mir beigebracht hat
Damals delegierte ich alles. Akismet kombinierte ein Wörterbuch unerwünschter Schlagwörter, ein Regelwerk und eine schwarze Liste und sortierte Kommentare in wartend oder Spam ein. Für einen Blog funktionierte das gut, und ich empfahl es ohne Vorbehalt.
Aber Delegieren hat einen Preis. Jede Nachricht wandert zur Analyse auf fremde Server, was bei einem Kontaktformular, auf dem ein Interessent seinen Namen und seine E-Mail-Adresse hinterlässt, zu einem echten DSGVO-Thema geworden ist. Und vor allem hat sich der Preis eines falsch Positiven in der Größenordnung verändert: ein verlorener Blogkommentar ist schade. Eine verlorene Angebotsanfrage ist ein Kunde, der nicht zurückkommt. Meine alte Website brach am Ende unter dem Spam über das Kontaktformular zusammen, und beim Neudenken der neuen setzte ich eine Ausgangsbedingung: ernsthaft filtern, ohne die Rechnung jemals den Menschen zahlen zu lassen.
Warum kein sichtbares Captcha
Die übliche Antwort auf Spam ist ein Captcha. Ich habe sie aus drei Gründen verworfen.
Zuerst die Reibung. Jeder Schritt, der vor dem Absenden hinzukommt, lässt echte Besucher abspringen, während ernsthafte Bots diese Rätsel lösen oder von Klickfarmen lösen lassen. Man verliert Menschen, um Maschinen auszubremsen, die längst nicht mehr ausgebremst werden.
Dann die Barrierefreiheit. Ampeln in verschwommenen Kacheln zu erkennen schließt sehbehinderte Menschen aus, und die Audio-Alternativen sind für alle mühsam.
Schließlich der Datenschutz. Die Captchas der großen Plattformen laden Skripte Dritter, beobachten das Verhalten des Besuchers und werfen Einwilligungsfragen auf, die ich nicht verwalten wollte. Meine Website ruft für ihre Formulare keinen einzigen externen Dienst auf: alles ist selbst gehostet, nichts verlässt das Haus.
Ein Captcha bestraft den Menschen für ein Problem, das von Bots verursacht wird. Ich wollte das Gegenteil: Prüfungen, die nur Bots bemerken.
Sieben unsichtbare Schichten statt einer Mauer
Keine Prüfung ist für sich allein perfekt. Gestapelt halten sie den Großteil des automatisierten Spams auf, ohne dass der Besucher irgendetwas davon sieht. Seit der ersten Fassung dieses Artikels habe ich zudem in einem Punkt meine Meinung geändert: Ich blieb auf der Ebene des Konzepts und hielt die Schwellenwerte zurück, aus Vorsicht. Ein Filter, der nur hält, weil seine Parameter geheim sind, ist ein fragiler Filter; dieser hält auch dann, wenn er vollständig beschrieben ist. Hier ist er also komplett, Zahlen inbegriffen. Jede Übermittlung durchläuft sieben Schichten, in dieser Reihenfolge:
- das CSRF-Token, beim Absenden erneut geprüft;
- die Ratenbegrenzung pro IP-Adresse;
- die signierte Zeitfalle;
- der Honeypot;
- die unsichtbare Proof-of-Work-Challenge;
- das Inhalts-Scoring, das nie blockiert;
- die ausdrückliche DSGVO-Einwilligung.
Die Eingangstür: CSRF-Token und Ratenbegrenzung
Noch bevor von Spam die Rede ist, wird das beim Rendern des Formulars ausgegebene CSRF-Token beim Absenden erneut geprüft. Das ist ein klassischer Schutz gegen gefälschte Anfragen, aber er weist bereits die Skripte ab, die direkt an den Endpunkt senden, ohne die Seite je geladen zu haben.
Dann folgt die Ratenbegrenzung pro IP-Adresse: zehn Versuche pro Stunde und pro Formular, in einem festen Fenster, gezählt im Datei-Cache, den sich die PHP-Worker teilen. Darüber hinaus antwortet der Server mit 429 und einer Meldung in der Sprache des Besuchers. Es ist die einzige Schicht, die frontal ablehnt: eine Salve sieht keinem Menschen ähnlich, so eilig er es auch hat.
Die stillen Fallen: Zeitfalle und Honeypot
Die Zeitfalle. Beim Rendern des Formulars gibt der Server ein verstecktes Feld mit einem Zeitstempel aus, signiert mit HMAC. Ein Mensch braucht Sekunden, oft Minuten, um ein Formular zu lesen und auszufüllen; eine Übermittlung, die in weniger als etwa drei Sekunden zurückkommt, ist ein Bot. Die Website antwortet ihm dann „Nachricht gesendet“, ohne etwas zu senden. Und ist die Signatur ungültig oder fehlt sie, lässt die Schicht die Übermittlung durch: fail-open, nie ein falsch Positiver. Sie blockiert nur bei einem positiven Nachweis der Automatisierung, die anderen Schichten decken den Rest ab.
Der Honeypot. Ein verstecktes Feld, für Menschen unsichtbar, das Bots ausfüllen, weil sie alles ausfüllen. Ist es ausgefüllt, gilt dieselbe Behandlung: stille Annahme, nichts gesendet. Der Bot weiß nicht, dass er erkannt wurde, also lernt er nichts.
Sein Name, „ref_code“, bedeutet nichts, und das ist Absicht. Chrome und Passwortmanager ignorieren autocomplete="off" weitgehend und leiten den Typ eines Feldes aus seiner Beschriftung ab. Eine Falle namens „Website des Unternehmens“ wird am Ende vom Browser eines echten Besuchers ausgefüllt, dessen Anfrage dann im Papierkorb landet, ohne dass es jemand merkt. Ja, ich veröffentliche den Feldnamen: ein Spammer, der diesen Artikel liest, um ihn zu umgehen, läuft in die sechs anderen Schichten.
Die unsichtbare Challenge, ein eigenes Turnstile
Blieb der gut ausgerüstete Bot, der JavaScript ausführt und sauber ausfüllt. Für ihn gibt es eine selbst gehostete Proof-of-Work-Challenge, mein eigenes Gegenstück zu Turnstile: der Server gibt ein signiertes Token für einmaligen Gebrauch aus, und der Browser löst im Hintergrund eine kleine Berechnung von etwa sechzehn Bit Schwierigkeit, während der Besucher seine Nachricht schreibt. Kein Klick, kein Kästchen, keine Zebrastreifen-Bilder. Für einen Menschen nicht wahrnehmbar, teuer für einen Bot, der massenhaft sendet, ohne API-Schlüssel und ohne Dienst Dritter. Und führt der Browser kein JavaScript aus, greift ein Fallback: die fehlende Antwort blockiert das Absenden nicht, die serverseitigen Schichten übernehmen. Graceful Degradation gilt auch für die Sicherheit.
Die Regel vom null verlorenen Lead
Sechste Schicht, das Inhalts-Scoring: Linkdichte, Textqualität, Erkennung von Dubletten. Jedes Signal zählt Punkte zu einem Score hinzu, und genau hier weicht die Philosophie von klassischen Filtern ab: dieser Score lehnt nie eine Übermittlung ab. Häufen sich die Signale, wird die Nachricht trotzdem zugestellt, einfach zur manuellen Durchsicht in meinem Postfach markiert.
Die Pipeline unterscheidet also zwei Familien von Prüfungen. Positive Nachweise der Automatisierung, wie der Honeypot, die Zeitfalle oder eine Salve, lehnen ab. Inhaltssignale vergeben nur Punkte. Ein echter Kunde, der drei Links zu seiner bestehenden Website einfügt oder zwei knappe Zeilen vom Handy schreibt, kommt durch. Ich lese lieber zwei Spam-Nachrichten, als einen Kunden zu verlieren. Das ist genau die Logik meiner dreißig Tage Akismet-Prüfzeit von 2016, bis zum Ende gedacht: der Zweifel kommt immer der Nachricht zugute.
Die siebte Schicht hat nichts Algorithmisches: ein ausdrückliches DSGVO-Einwilligungskästchen, der einzige Teil dieser ganzen Pipeline, den der Besucher wirklich sieht. Nachdem die Skripte Dritter gerade dem Datenschutz zuliebe verschwunden sind, musste das Formular selbst über jeden Zweifel erhaben sein.
Die ehrliche Anzeigetafel
Einen Spamschutz beurteilt man an Belegen, nicht an Versprechen. Hier sind die vier Szenarien, die ich unter realen Bedingungen geprüft habe, keines davon verschwiegen:
- ein echter Kunde füllt das Formular aus: die Nachricht wird zugestellt;
- ein einfacher Bot füllt alles aus, Honeypot inbegriffen: still angenommen, nie gesendet;
- ein schneller Bot sendet in weniger als drei Sekunden: die Zeitfalle erkennt ihn, still angenommen, nie gesendet;
- eine Salve von derselben Adresse: 429 nach dem zehnten Versuch innerhalb einer Stunde.
Wie das in der Praxis aussieht
Auf der Kontaktseite wie auch im Angebotsrechner sieht der Besucher nichts von dieser Mechanik. Kein Kästchen „Ich bin kein Roboter“, kein Rätsel, kein Einwilligungsbanner für ein Skript Dritter. Das Formular füllt sich aus und sendet, fertig. Es ist dieselbe Sorgfalt, die ich in meine gesamte Arbeit als Webentwickler in Belgien stecke: Robustheit darf nie mit Reibung für den Nutzer bezahlt werden.
Wenn Ihr eigenes Formular unter Spam zusammenbricht, passt mein Vorschlag in einen Satz: bevor Sie ein Captcha hinzufügen, das Ihre Besucher vertreibt, stapeln Sie unsichtbare Prüfungen und behalten Sie die Ablehnung den Nachweisen der Automatisierung vor. 2016 beendete ich meinen Beitrag mit der Einladung, zu kommentieren und zu teilen. 2026 ist die ehrliche Fassung einfacher: wenn das Thema Sie anspricht, schreiben Sie mir über genau dieses Formular. Es hält stand.
