Spam in Kommentaren vermeiden: Formulare schützen

 

Ein Fotoblog neben der Galerie, drei Wochen online, und plötzlich stehen unter den Beiträgen jeden Morgen zehn neue Kommentare: „Great pictures, thanks for sharing“, darunter ein Link auf einen Online-Shop für Potenzmittel. Geschrieben haben sie Programme, die rund um die Uhr Formulare suchen und ausfüllen. Wer Spam in Kommentaren vermeiden will, kommt mit einem einzelnen Trick nicht weit. Wirksam sind mehrere Hürden hintereinander, die echte Besucher gar nicht bemerken.

Spam in Kommentaren vermeiden: Gästebuch mit Feldfalle, Mindestzeit, Link-Grenze und Drosselung, von 140 Einsendungen bleiben 11

Vorweg, damit Sie an der richtigen Stelle suchen: Das Bildergalerie-Script hat keine Kommentarfunktion. Unter den Fotos kann niemand etwas schreiben, also gibt es dort auch keinen Spam-Filter einzustellen. Spam landet im Kommentarbereich eines Blogs, im Gästebuch oder im Kontaktformular neben der Galerie.

Wie ein Spam-Bot ein Formular abarbeitet

Ein Bot lädt das HTML Ihrer Seite, sucht ein form-Element und füllt jedes Feld aus, auch die unsichtbaren. Abgeschickt wird oft schon ein bis zwei Sekunden nach dem Laden, so schnell tippt kein Mensch einen Satz. Auch am Inhalt erkennen Sie Bots, bevor Sie den Text ganz gelesen haben. Im Namensfeld steht statt „Petra“ eine Webadresse, damit der Name selbst zum Link wird. Und das Lob „Nice post, very useful“ passt zu jedem Foto und deshalb zu keinem. Echte Besucher nennen das Motiv.

Drei unsichtbare Hürden hintereinander

Jede Hürde fängt eine andere Sorte Bot. Wie sie zusammenwirken, zeigt eine Modellrechnung für eine Woche auf einem kleinen Fotoblog. Die Zahlen sind Annahmen, keine Messwerte, denn die Anteile hängen davon ab, welche Bots Ihr Formular gefunden haben. Angenommen sind 140 Einsendungen, davon 132 von Bots und 8 von echten Besuchern. Das versteckte Feld fängt 95 Bots ab, die blind jedes Feld füllen. Die Mindestzeit erwischt 24 weitere, die schneller als drei Sekunden absenden. Die Link-Grenze sortiert 10 Bots aus, die mehr als einen Link mitschicken. Übrig bleiben 3 Bots und die 8 echten Kommentare.

Animiertes Waffeldiagramm einer Modellrechnung: 140 Kästchen für eine Woche Kommentare, Feldfalle, Mindestzeit und Link-Grenze blenden nacheinander 95, 24 und 10 Bot-Einträge aus, 11 bleiben zur Freigabe, darunter alle 8 echten
Modellrechnung: Drei unsichtbare Hürden sortieren 129 von 132 Bot-Einträgen aus, von Hand lesen Sie nur noch 11 statt 140.

Dicht ist keine dieser Hürden. Die Übersicht zeigt, was jede prüft und wer trotzdem durchkommt. Dazu kommen drei Hürden für hartnäckigere Fälle.

HürdeWas sie prüftWas Besucher merkenWer trotzdem durchkommt
Feldfalle (Honeypot)ein unsichtbares Feld muss leer bleibennichtsBots, die nur sichtbare Felder ausfüllen
MindestzeitAbstand zwischen Formularaufruf und AbsendennichtsBots, die das Formular neu laden und vor dem Absenden warten
Link-GrenzeZahl der Adressen in Text und Nameselten etwasAdressen ohne „http“ und „www“, etwa „shop-beispiel.de“
Wortfilterverbotene Begriffe als ganze Wörternur bei FehlalarmSchreibweisen wie „V1agra“
Drosselung je IP-AdresseKommentare eines Absenders in kurzer Zeitnur bei gemeinsamem WLANBots mit ständig neuer Adresse
Freigabe von HandIhr eigenes UrteilWartezeitalles, sobald niemand nachsieht

Ein Captcha fehlt hier mit Absicht: Es ist die einzige Hürde, die jeder echte Besucher spürt.

Alle fünf Prüfungen in einem PHP-Script

In ein selbst geschriebenes Kommentarformular oder Gästebuch passen die Hürden aus der Tabelle in ein einziges Script. Es setzt eine Tabelle kommentar mit den Spalten name, text, status, grund, ip_hash und erstellt voraus, $pdo ist Ihre Datenbankverbindung. Die Prüfungen laufen der Reihe nach, die erste, die anschlägt, steht später in der Spalte grund. Ein Link wie „https://www.verein-beispiel.de“ zählt dabei als einer. Was durchfällt, wird als „verdächtig“ gespeichert und nie angezeigt.

<?php
session_start(); function feld(string $name): string { $wert = $_POST[$name] ?? ''; return is_string($wert) ? trim($wert) : '';
} function verbotenes_wort(string $inhalt): bool { foreach (['viagra', 'cialis', 'casino'] as $wort) { if (preg_match('/\b' . preg_quote($wort, '/') . '\b/iu', $inhalt)) { return true; } } return false;
} if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = feld('name'); $text = feld('text'); $marke = feld('marke'); $start = $_SESSION['formulare'][$marke] ?? 0; unset($_SESSION['formulare'][$marke]); // jede Marke gilt nur einmal $links = preg_match_all('~(?:https?://|www\.)\S+~i', $name . ' ' . $text); $ip_hash = hash('sha256', $_SERVER['REMOTE_ADDR'] . 'eigener-geheimer-text'); $zaehler = $pdo->prepare('SELECT COUNT(*) FROM kommentar WHERE ip_hash = ? AND erstellt >= NOW() - INTERVAL 10 MINUTE'); $zaehler->execute([$ip_hash]); if (feld('bitte_leer') !== '') { $grund = 'feldfalle'; } elseif ($start === 0 || time() - $start < 3) { $grund = 'mindestzeit'; } elseif ($links > 1) { $grund = 'links'; } elseif (verbotenes_wort($name . ' ' . $text)) { $grund = 'wortfilter'; } elseif ($zaehler->fetchColumn() >= 3) { $grund = 'drosselung'; // ab dem 4. in 10 Minuten } else { $grund = ''; } $freigabe_noetig = true; // false: unverdächtige sofort zeigen if ($grund !== '') { $status = 'verdächtig'; // aufbewahren, nie anzeigen } elseif ($freigabe_noetig) { $status = 'wartet auf Freigabe'; } else { $status = 'sichtbar'; } $pdo->prepare('INSERT INTO kommentar (name, text, status, grund, ip_hash, erstellt) VALUES (?, ?, ?, ?, ?, NOW())')->execute([$name, $text, $status, $grund, $ip_hash]); exit('Danke für Ihren Kommentar.'); // gleiche Antwort für alle
} // neue Marke mit Startzeit für genau diesen Formularaufruf
$marke = bin2hex(random_bytes(16));
$_SESSION['formulare'] = array_slice($_SESSION['formulare'] ?? [], -19, null, true);
$_SESSION['formulare'][$marke] = time();
?>
<form method="post"> <input type="hidden" name="marke" value="<?= $marke ?>"> <label>Name <input name="name" required></label> <label>Kommentar <textarea name="text" required></textarea></label> <div style="position:absolute;left:-9999px" aria-hidden="true"> <label>Dieses Feld bitte leer lassen <input name="bitte_leer" tabindex="-1" autocomplete="off"></label> </div> <button>Absenden</button>
</form>

Fünf Einzelheiten in diesem Code sind Absicht:

  • Der Feldname: bitte_leer statt „email2“ oder „firma“. Felder mit solchen Namen füllt der Browser trotz autocomplete="off" gern selbst aus, und dann säße ein Mensch in der Falle.
  • Die Marke je Aufruf: Jedes angezeigte Formular bekommt eine eigene Zufallsmarke mit Uhrzeit, und beim Absenden wird sie gelöscht. Ein Bot, der sein Sitzungs-Cookie behält, kann deshalb nicht einmal laden und dann beliebig oft absenden. Für jeden Versuch muss er das Formular neu holen und drei Sekunden warten. Mehrere offene Tabs eines Besuchers stören nicht, die Sitzung hält die letzten 20 Marken.
  • Die Wortgrenzen: Die beiden \b sorgen dafür, dass nur ganze Wörter zählen. Ohne sie träfe ein Filter auf „sex“ auch den Kommentar zu Ihren Bergfotos aus Sexten in Südtirol. Das u am Ende lässt ä, ö und ü als Buchstaben gelten. Fehlt es, sieht PHP neben jedem Umlaut eine Wortgrenze.
  • Der Hashwert: Für die Drosselung speichert das Script nicht die IP-Adresse selbst, sondern ip_hash. Auch dieser Wert gilt nach der DSGVO als personenbezogen, leeren Sie die Spalte deshalb nach sieben Tagen. Weil ein gemeinsames WLAN auch Menschen in die Drosselung bringen kann, markiert sie nur und löscht nichts.
  • Dieselbe Antwort: Jeder liest „Danke für Ihren Kommentar.“ So lässt sich nicht ausprobieren, welche Hürde gegriffen hat.

Wie der Code entscheidet, zeigt das zweite Diagramm an zwei Einsendungen. Der Bot hat kein Sitzungs-Cookie, füllt das versteckte Feld und setzt vier Links. Er scheitert an der ersten Prüfung, die übrigen laufen gar nicht erst. Der Besucher lässt das Feld leer, schreibt 48 Sekunden lang und setzt keinen Link. Er besteht alle fünf Prüfungen, danach bestimmt der Schalter $freigabe_noetig seinen Status.

Animiertes Ablaufdiagramm: Ein Bot ohne Cookie und ein Besucher durchlaufen Feldfalle bitte_leer, Mindestzeit von 3 Sekunden, Link-Grenze, Wortfilter und Drosselung, der Bot fällt bei der Feldfalle durch und wird als verdächtig gespeichert, beim Besucher setzt der Schalter $freigabe_noetig = true den Status auf wartet auf Freigabe
Beide bekommen dieselbe Antwort. Gespeichert wird der Bot als „verdächtig“, der Besucher wegen $freigabe_noetig = true als „wartet auf Freigabe“.

Ob die Hürden bei Ihnen so greifen wie in der Modellrechnung, zeigen erst Ihre eigenen Zahlen. Diese Abfrage in phpMyAdmin zählt die letzte Woche nach Grund aus. Ein leerer Grund heißt: alle Hürden passiert.

SELECT grund, COUNT(*) AS anzahl
FROM kommentar
WHERE erstellt >= NOW() - INTERVAL 7 DAY
GROUP BY grund;

Fängt die Feldfalle kaum etwas, die Mindestzeit aber viel, erkennen Ihre Bots versteckte Felder. Dann hilft ein anderer Feldname. Steht der Wortfilter weit vorn, lohnt es sich, die Liste um die Begriffe zu ergänzen, die Sie in der Spalte text immer wieder finden.

Gegen gezielte Bots: welches Captcha passt

Ein eigens auf Ihr Formular angesetzter Bot lässt das Feld leer, wartet ein paar Sekunden und setzt nur einen Link. Bleibt er bei einer Adresse, fängt ihn die Drosselung. Wechselt er ständig die Adresse, bleibt nur ein Captcha. Jedes Captcha eines fremden Anbieters lädt ein Script von dessen Servern, überträgt die IP-Adresse und gehört in die Datenschutzerklärung.

VarianteWas der Besucher tutWohin die Daten gehenPasst für
Eigene Kontrollfrageeine Frage beantworten, etwa nach dem Ort des letzten Fotosnirgendwohin, Prüfung auf Ihrem ServerFamilien- und Vereinsseiten
Friendly Captchameist nichts, der Browser rechnet im HintergrundAnbieter aus Deutschland, ohne Cookiesoffene Blogs, Kontaktformulare
Google reCAPTCHAHäkchen, manchmal Bilder anklickenGoogle, setzt Cookiesnur mit Einwilligung im Cookie-Banner

Bei reCAPTCHA steckt der Haken im Ablauf: Lehnt ein Besucher die Cookies ab, darf das Script nicht laden, und das Formular ist dann entweder ungeschützt oder unbenutzbar. Bilderrätsel schließen außerdem Menschen aus, die schlecht sehen.

WordPress: die Schalter unter „Diskussion“

Ein WordPress-Blog bringt die wirksamsten Schalter selbst mit, noch vor jeder Erweiterung. Sie stehen unter „Einstellungen“ › „Diskussion“. Die Tabelle nennt die Voreinstellung einer neuen Installation und einen Wert für einen kleinen Fotoblog:

EinstellungVoreinstellungEmpfehlung
Kommentar zurückhalten ab so vielen Links21, dann wartet jeder Kommentar mit Link
Autor braucht bereits einen freigegebenen Kommentaranan: Wer einmal freigegeben ist, schreibt danach direkt
Pingbacks und Trackbacks erlaubenanaus, sie bringen heute fast nur Spam
Kommentare zu älteren Beiträgen schließenaus, Feld steht auf 14 Tagean, etwa nach 60 Tagen

Drei weitere Punkte betreffen gerade Fotoblogs:

  • Die zwei Wortlisten: Weiter unten auf derselben Seite stehen zwei Listen für verbotene Begriffe. Treffer aus der ersten landen in der Warteschlange, Treffer aus der zweiten sofort im Papierkorb. In die zweite gehören deshalb nur eindeutige Begriffe.
  • Anhangseiten: Ältere Installationen legen für jedes hochgeladene Bild eine eigene Seite mit Kommentarfeld an, auf der Bots unbemerkt Einträge ablegen. Neue Installationen schalten diese Seiten seit Version 6.4 ab.
  • Erweiterungen: Akismet schickt jeden Kommentar samt IP-Adresse zur Prüfung an einen Dienst in den USA und gehört in die Datenschutzerklärung. Antispam Bee prüft auf Ihrem eigenen Server.

Spam in Kommentaren vermeiden, ohne Besucher auszusperren

Ein zu eifriger Filter ärgert mehr als ein durchgerutschter Spam-Kommentar, denn der Besucher erfährt nie, warum sein Text fehlt. Neben dem Wortfilter ohne Wortgrenzen sind drei Fehlalarme typisch:

  • Mindestzeit zu hoch: 20 Sekunden treffen jeden, der seinen Text vorher geschrieben und nur eingefügt hat. Drei bis fünf Sekunden genügen.
  • Keine Sitzung ohne Cookies: Wer Cookies blockiert, schickt keine gültige Marke mit und landet mit dem Grund mindestzeit bei „verdächtig“.

Weil solche Kommentare nur markiert werden, lassen sie sich retten. Sehen Sie die verdächtigen einmal pro Woche durch, in WordPress unter „Kommentare“ › „Spam“. Wer ein Motiv aus Ihren Fotos nennt, ist fast immer ein Mensch. Ein Satz unter dem Formular wie „Kommentare erscheinen nach Prüfung, meist am selben Tag“ verhindert, dass jemand seinen Beitrag für verloren hält.

Wenn eine Spam-Welle durchkommt

Angenommen, die Freigabe ist abgeschaltet, und eines Morgens stehen 50 neue Kommentare unter Ihren Beiträgen. Abgeschickt wurden sie sechs bis acht Sekunden nach dem Seitenaufruf, das versteckte Feld ist leer, jeder kommt von einer anderen IP-Adresse und enthält genau einen Link. Dieser Bot ist auf Ihr Formular zugeschnitten. So gehen Sie vor:

  1. Die Freigabe einschalten ($freigabe_noetig = true;), damit nichts Weiteres öffentlich wird.
  2. Das gemeinsame Merkmal suchen: Name, Text und Absender wechseln, der eine Link bleibt. Seine Domain zu sperren nützt wenig, der Bot weicht auf die nächste aus.
  3. Die durchgerutschten Kommentare löschen und dabei prüfen, ob ein echter darunter war.
  4. Nach einer Woche ohne neue Welle die allgemeine Freigabe durch eine Regel für genau dieses Merkmal ersetzen: $freigabe_noetig = ($links > 0); Dann wartet nur noch, was einen Link enthält.

Unter Fotobeiträgen schreibt kaum ein Besucher eine Adresse dazu, die Regel trifft also fast nur den Bot. Kommt die nächste Welle ohne Links, ist ein Captcha aus der Tabelle oben an der Reihe.

Warum die Galerie selbst keinen Spam-Filter braucht

Die öffentliche Seite bildergalerie.php enthält kein Eingabefeld. Sie wertet nur die Kategorienummer kid in der Adresse aus, Text oder eine negative Zahl führen zur Startseite. Das einzige Formular ist die Anmeldung unter /admin/, an der Bots Passwörter ausprobieren. Die Galerie bremst sie ohne jede Einstellung: Nach 5 Fehlversuchen innerhalb von 5 Minuten, gezählt je IP-Adresse und je Benutzername, ist der Zugang 5 Minuten gesperrt. Mehr dazu steht auf der Seite Webseite vor Hackern schützen.

Einen Blog oder ein Gästebuch legen Sie am besten in einen eigenen Ordner, getrennt vom Ordner der Galerie. Dann verschwindet nichts davon, wenn Sie nach einem Update veraltete Galerie-Dateien löschen. Wer eine Galerie ohne eigene Spam-Pflege sucht, kann das Script kostenlos herunterladen und neben einen bestehenden Blog stellen.

Häufige Fragen

Drei Fragen kommen beim Einrichten immer wieder.

Wo im Bildergalerie-Script stelle ich Freigabe, Captcha und Wortfilter ein?

Nirgends, denn das Script hat keine Kommentare. Freigabe, Feldfalle und Wortliste gehören in den Blog, das Gästebuch oder das Kontaktformular daneben.

Gelten dieselben Regeln für das Kontaktformular?

Feldfalle, Mindestzeit und Drosselung lassen sich unverändert übernehmen, nur die Freigabe entfällt. Damit Bots über das Formular keine Mails an Fremde verschicken, steht die Empfängeradresse fest im PHP-Code und nie in einem versteckten Feld. Die Absenderadresse für Reply-To prüfen Sie mit filter_var($mail, FILTER_VALIDATE_EMAIL). Sonst schmuggelt ein Bot per Zeilenumbruch eine Bcc-Zeile hinein.

Hilft es, nur angemeldete Nutzer kommentieren zu lassen?

Gegen Kommentar-Spam ja, aber der Spam wandert dann zur Registrierung: Bots legen massenhaft Konten an. Für einen Familien- oder Vereinsblog mit festem Leserkreis ist das trotzdem eine gute Lösung, für einen offenen Fotoblog schreckt die Anmeldung viele Besucher ab. In WordPress heißt der Schalter „Benutzer müssen registriert und angemeldet sein, um kommentieren zu können“.

Verwandte Themen

Mehr zu Captchas, zur Kommentarfunktion und zum Schutz der Verwaltung: