Startseite → Plugins → Spamschutz für Bricks-Formulare
Die Website einer öffentlichen Gesundheitseinrichtung und ein gewöhnliches Kontaktformular in Bricks, das vor Bots geschützt werden musste. Es gab zwei Bedingungen: keine fremden Dienste, weil Patientendaten den Server nicht verlassen dürfen, und keine Rätsel zum Lösen, weil die Website WCAG 2.1 AA erfüllen muss. Herausgekommen ist ein kleines mu-Plugin: ein Honeypot, eine Zeitfalle und eine stille Erfolgsmeldung. Unten steht, was ich dabei aus dem Quellcode von Bricks gelernt habe.
Nichts verlässt den Server. Kein Skript von einer anderen Domain, keine API-Schlüssel, keine Cookies. Die Entscheidung, ob eine Einsendung von einem Menschen stammt, fällt vollständig innerhalb von WordPress.
Besucherinnen lösen nichts. Kein Klicken auf Bilder, kein Abtippen von Zeichen. Für jemanden mit Screenreader ist das Formular genau dasselbe wie vorher.
Es gilt für jedes Bricks-Formular. Auch für die später hinzugefügten — im Popup, im Off-Canvas-Panel, auf einer neuen Seite — ohne ein Feld, an das man denken müsste.
Der Bot erfährt nie, dass er erwischt wurde. Er bekommt dieselbe Meldung „gesendet" wie ein Mensch. Die Nachricht geht nirgendwohin, und der Bot hat keinen Anlass, seine Taktik zu ändern.
Seit Version 1.12.2 lässt sich jedes Formularfeld als Honeypot kennzeichnen, und der Abschnitt zum Spamschutz bietet Turnstile, reCAPTCHA und hCaptcha an. Den eingebauten Honeypot lohnt es sich einzuschalten, und in vielen Fällen genügt er. Hier war eine zweite Ebene nötig — eine Zeitfalle — und der Schutz musste automatisch für jedes Formular gelten, ohne jedes einzelne im Builder zu bearbeiten.
Der Entwurf stand vor der ersten Zeile PHP. Zwei seiner Annahmen änderten sich, nachdem ich gelesen hatte, wie Bricks eine Formulareinsendung annimmt — und es war gut, dass das vor dem Einsatz passierte und nicht danach.
Die naheliegende Idee ist, die versteckten Felder wie Formularfelder zu benennen, mit dem Präfix form-field-, damit sie sicher mitgesendet werden. Bricks hat dagegen eine Sicherung: Jedes Feld mit diesem Präfix, das nicht in den gespeicherten Einstellungen des Formulars steht, lässt die ganze Einsendung als verdächtig abgelehnt werden. Ein PHP-Filter zur Laufzeit kommt daran nicht vorbei, weil der Server prüft, was in der Datenbank liegt. Meine Felder haben deshalb eigene Namen. Mitgesendet werden sie trotzdem, weil das Frontend von Bricks das ganze Formular einsammelt und die Sicherung für sie schlicht nicht greift.
Eine Zeitfalle prüft, wie viel Zeit zwischen dem Laden der Seite und dem Absenden des Formulars vergangen ist. Ein von PHP ausgegebener Zeitstempel würde mit dem übrigen HTML im Seiten-Cache landen, und bald ließe die Falle alle durch — Bots eingeschlossen. Gezählt wird deshalb im Browser: Ein Skript merkt sich den Start und schreibt beim Absenden die Anzahl der Sekunden. Ein Bot, der die Anfrage ohne JavaScript abschickt, hat dort gar keinen Wert — und auch das ist ein Signal.
bricks/form/validate ist ein Filter, keine Aktion: Er bekommt ein Array mit Fehlern und das Formularobjekt und gibt das Fehler-Array zurück. Er läuft nach der Nonce-Prüfung, nach dem eingebauten Honeypot und nach der Prüfung der Feldliste, und vor der Prüfung der Pflichtfelder sowie vor den Aktionen — dem Versand der E-Mail oder der Weiterleitung. Genau dort fällt die Entscheidung, bevor irgendetwas den Server verlässt.
Das Frontend von Bricks zeigt die Erfolgsmeldung nur, wenn die Antwort success: true enthält und data.type auf success steht. Wird Spam erkannt, schicke ich genau diese Antwort, mit der Meldung aus den Einstellungen des Formulars, und beende die Anfrage — die Formularaktionen laufen nie. Der eingebaute Honeypot antwortet standardmäßig mit einem Fehler; das lässt sich mit dem Filter bricks/element/form/honeypot/result ändern.
add_filter( 'bricks/form/validate', function ( $errors, $form ) {
$hp = sanitize_text_field( wp_unslash( $_POST['zz_hp'] ?? '' ) );
$t = sanitize_text_field( wp_unslash( $_POST['zz_t'] ?? '' ) );
$spam = '' !== trim( $hp ) // Honeypot ausgefüllt
|| ! is_numeric( $t ) // keine Zeit = kein JavaScript
|| (int) $t < 3; // zu schnell für einen Menschen
if ( ! $spam ) {
return $errors; // ein Mensch: den Rest macht Bricks
}
$msg = $form->get_settings()['successMessage'] ?? 'Danke.';
wp_send_json_success( [ 'type' => 'success', 'message' => $msg ] );
}, 10, 2 );
Ein Spamschutz, der Bots stoppt, aber ab und zu auch eine echte Nachricht, ist schlimmer als Spam. Diese drei Dinge habe ich deshalb in einem echten Browser geprüft, auf einer Staging-Kopie der Website.
Ein Formular im Popup. Ein Skript, das die Felder einmal nach dem Laden der Seite ergänzt, sieht Formulare nicht, die später erscheinen. So ein Formular würde ohne das Zeitfeld abgeschickt, und der Server hielte einen Menschen für einen Bot. Ein Beobachter des Dokuments ergänzt die Felder auch in Formularen, die nach dem Seitenstart eingefügt werden.
Das Verstecken des Honeypots. Das Feld muss für das Auge, die Tastatur und den Screenreader verschwinden und trotzdem im Formular bleiben. Es wird aus dem Sichtbereich geschoben, aus der Tabulatorreihenfolge genommen, vor assistiver Technik verborgen und hat das automatische Ausfüllen abgeschaltet — damit der Browser es nicht stellvertretend für einen Menschen ausfüllt.
Vier Szenarien an einem Formular. Eine Einsendung wie von einem Menschen — die E-Mail kam an. Ein ausgefüllter Honeypot, eine Einsendung nach einer Sekunde und eine Anfrage ohne JavaScript — jedes Mal die Meldung „gesendet" und keine E-Mail. Die Zeitschwelle liegt bei drei Sekunden und lässt sich mit einem einzigen Filter ändern, ohne den Code anzufassen.
Schreiben Sie, wie viele Formulare es sind und ob die Website Einschränkungen bei fremden Diensten hat. Ich sage Ihnen, ob der eingebaute Honeypot genügt oder sich eine zweite Ebene lohnt.
Ihr Formular beschreiben →Oder per E-Mail: info@asymetria.com.pl