presseportale/dev/sicherheit/ENTWICKLUNGSPLAN-SPAMSCHUTZ-2026-10-09.md
2026-10-09 10:55:33 +00:00

15 KiB
Raw Blame History

Entwicklungsplan – Spamschutz für öffentliche Formulare

Anlass: Über das Kontakt-Widget kommen täglich Spam-Mails an die Brand-Adressen. Ziel ist, dass Spam gar nicht erst im System landet oder Mails auslöst.

Datengrundlage: lokale Datenbank, Stand 07.10.2026, Zeitraum der letzten 30 Tage.

Stand: 09.10.2026 – Phasen 1 bis 3 live seit v1.7.0 (09.10.2026), Altbestand und Bot-Konten auf Live aufgeräumt. Offen: juristische Freigabe von Abschnitt 6a der Datenschutzerklärung, Auswertung nach 7 Tagen (ab 16.10.2026).

0. Umsetzungsstand

Baustein Umsetzung Tests
B1 Turnstile App\Services\Spam\TurnstileVerifier, Komponente <x-turnstile>, Config services.turnstile, CSP um challenges.cloudflare.com (script-src, frame-src) ergänzt tests/Feature/Web/ContactSpamProtectionTest.php, tests/Feature/Auth/RegistrationSpamProtectionTest.php
B2 Inhaltsfilter App\Services\Spam\ContactSpamFilter: fremde Links (Links auf eigene Seiten erlaubt, weil Korrekturwünsche sie nennen), Floskel mit Seitentitel, Zeichensalat, Sperrliste, Absenden unter 3 Sekunden. Nur für Gäste und unbestätigte Konten wie oben, tests/Unit/GibberishDetectorTest.php
B3 Quarantäne Status ContactMessageStatus::Spam: keine Mail, nicht im Zähler, kein Verlaufseintrag, Löschung nach 30 Tagen (model:prune). Admin: Filter „Spam“, Sammelaktion, „Als Spam markieren“, „Kein Spam“ (holt die Mail einmal nach) tests/Feature/Admin/ContactMessagesAdminTest.php
B4 Registrierung Turnstile und Namensregel (GibberishDetector::isRandomPersonName, 0 Fehltreffer unter rund 67.000 Personennamen); Befehl users:prune-bot-registrations mit Probelauf, Zeitplan täglich 03:45, aber aus bis USERS_PRUNE_BOT_REGISTRATIONS=true tests/Feature/Auth/PruneBotRegistrationsTest.php
Altbestand Befehl contact-messages:quarantine-spam mit Probelauf tests/Feature/Web/QuarantineContactSpamTest.php
B6 Beobachtung Log-Kanal spam (Grund, Formular, IP-Präfix, nie Inhalte); Betriebsüberwachung: „Spamschutz (Cloudflare Turnstile)“ und „Formulare ohne Bot-Prüfung“ (7 Tage) tests/Feature/Admin/SystemHealthTest.php
Datenschutz Abschnitt 6a „Schutz vor automatisierten Anfragen (Cloudflare Turnstile)“ und Hinweis zur Quarantäne in Abschnitt 6 — Vorschlag, juristische Freigabe steht aus –

Ausfall von Cloudflare (Entscheidung 1, angepasst): Es wird nie blockiert. Formulare, bei denen Cloudflare nicht erreichbar war, werden angenommen und markiert:

  • im Aktivitätsverlauf als Typ „Ohne Bot-Prüfung“;
  • in der Kontakt-Queue als Badge „ohne Bot-Prüfung“;
  • in der Betriebsüberwachung als Warnung.

Das gilt in drei Fällen: Siteverify antwortet nicht, Cloudflare lehnt unseren Secret-Key ab, oder das Widget lud im Browser nicht. Den letzten Fall meldet der Browser selbst und könnte deshalb missbraucht werden. Sollte das passieren, schaltet TURNSTILE_FAIL_OPEN=false auf Ablehnen um.

Messung am Altbestand (lokal, Stand 07.10.):

  • contact-messages:quarantine-spam markiert mit den Live-Hostnamen 125 offene Gast-Anfragen. Die echten Korrekturanfragen KT-32, KT-33 und KT-161 bleiben offen.
  • Was der Filter nicht erkennt, wird über die Sammelaktion markiert. Das sind vor allem die Preisfragen in wechselnden Sprachen und die Newsletter-Bitten.
  • users:prune-bot-registrations --dry-run findet 392 Konten. 379 davon haben einen Zufallsnamen, 13 sind echt wirkende, aber seit über 14 Tagen unbestätigte und ungenutzte Konten.

Nach dem Deploy auf Live (09.10.2026, v1.7.0):

  1. ✅ Turnstile-Widget im Cloudflare-Dashboard mit allen drei Live-Hostnamen, Kontakt-Widget im Browser getestet, Betriebsüberwachung grün.
  2. ✅ contact-messages:quarantine-spam: 152 Anliegen in die Quarantäne, darunter eine neue Welle „XEvil … löst Captchas“ vom 08./09.10. 130 blieben offen, vor allem Preisfragen in wechselnden Sprachen und Newsletter-Bitten; die kommen per Sammelaktion in die Quarantäne. Die echten Korrekturanfragen KT-32, KT-33 und KT-161 blieben offen.
  3. ✅ users:prune-bot-registrations: Probelauf geprüft und freigegeben, ausgeführt, täglicher Lauf eingeschaltet (USERS_PRUNE_BOT_REGISTRATIONS=true). Die Liste bestätigte das Sign-up-Bombing: Zufallsnamen mit fremden Adressen, darunter Behörden (state.gov, usdoj.gov) und SMS-Gateways (vtext.com, tmomail.net).
  4. Lokal die Cloudflare-Testkeys in die .env eintragen (die echten Keys sind an die Live-Hostnamen gebunden).
  5. Ab 16.10.2026: Wirkung auswerten — Spam-Filter in der Kontakt-Queue, storage/logs/spam-*.log, Betriebsüberwachung „Formulare ohne Bot-Prüfung“.
  6. Abschnitt 6a der Datenschutzerklärung juristisch freigeben lassen.

1. Befund

Kontakt-Widget (contact_messages)

  • 141 Anfragen in 30 Tagen, das sind 2 bis 9 pro Tag auf allen drei Portalen. Jede löst eine Mail aus.
  • Davon kommen 5 von angemeldeten Kunden (user_id gesetzt). Nach Durchsicht der letzten 70 sind das die einzigen echten. Der Rest ist Spam.
  • Es gibt fünf Spam-Familien:
Muster Anzahl (30 T.) Beispiel
Link im Text (telegra.ph, tinyurl, <a href>, SEO-Angebote) 56 „2026 Rolls-Royce Phantom for $15,000 https://telegra.ph/…“
Floskel plus Meta-Beschreibung der Seite 43 „Ich wünsche weitere Informationen. Bitte kontaktieren Sie mich per E-Mail — aktuelle pressemitteilungen aus der deutschen wirtschaft.“
Preisfrage in wechselnden Sprachen regelmäßig „Kaixo, zure prezioa jakin nahi nuen.“ (Name „RobertHix“, „Robertfielt“)
Zeichensalat vereinzelt Name „NAEWTRER2720160NEYRTHYT“, Text „METRYTRE2720160MAWRERGTRH“
Newsletter-Bitte auf Englisch vereinzelt „Hi! I'd like to subscribe to your newsletter.“
  • Herkunft: wenige Netze. 212.30.36.x allein bringt 32 Anfragen, 158.173.79.x 13 und 77.90.185.x 11. Das Limit pro IP (5 pro Stunde) greift nicht, weil die Absender innerhalb des Netzes die IP wechseln.
  • Vorhandener Schutz: zwei Honeypot-Felder (website, homepage), ein Limit pro IP und throttle:10,1. Das hält diese Bots nicht auf. Sie füllen die Honeypots offenbar nicht aus und laden die Seite vorher (page_url ist immer gesetzt).

Registrierung – größer als das Kontaktformular

  • 452 neue Konten in 30 Tagen. 389 davon sind Bot-Konten: Name als Zufallsbuchstaben („ruLrlotwaCoVStmZetSxD“), E-Mail nie bestätigt, keiner Firma zugeordnet. Das sind 6 bis 21 pro Tag.
  • Echte Neukunden im selben Zeitraum: 59 (bestätigt).
  • Jedes Bot-Konto löst eine Bestätigungsmail an eine fremde Adresse aus. Darunter sind SMS-Gateways von US-Mobilfunkanbietern (tmomail.net, vtext.com). Das ist bekanntes Sign-up-Bombing:
    • Die Täter nutzen unseren Mailversand, um Postfächer zu fluten.
    • Das schadet unserer Absender-Reputation und damit der Zustellung echter Mails.
  • Vorhandener Schutz: nur ein Limit pro IP (10 pro Stunde).

Übrige öffentliche Formulare

Diese Formulare haben einen Honeypot und ein Limit. Spam fällt dort noch nicht auf (z. B. 14 Rechtsanfragen in 30 Tagen). Sie verschicken aber Mails an Adressen, die der Absender angibt:

  • Widerruf
  • Kündigung
  • Meldeformular
  • Rechtsanfrage

2. Bausteine

B1 – Cloudflare Turnstile (Kern)

Konfiguration

  • Neuer Eintrag services.turnstile mit site_key, secret_key und enabled. Grund: Wegen config:cache darf env() nur in config/ stehen.
  • Die Keys stehen bereits in der .env. Es sind echte Keys (0x…), keine Testkeys.

Serverseitige Prüfung

  • Ein Dienst TurnstileVerifier ruft POST https://challenges.cloudflare.com/turnstile/v0/siteverify auf.
  • Mitgesendet werden secret, response und remoteip.
  • Geprüft werden success, hostname (gegen die drei Portal-Domains) und action (pro Formular).
  • Timeout 3 Sekunden.
  • Ein Token gilt nur einmal und höchstens 300 Sekunden.

Blade-Komponente <x-turnstile action="…" />

  • Das Skript wird nur dort geladen, wo ein Formular sichtbar ist.
  • Im Kontakt-Widget lädt es erst beim Öffnen des Panels (render=explicit). Normale Seitenaufrufe stellen so keine Verbindung zu Cloudflare her.
  • Modus „Managed“: meist unsichtbar, nur bei Verdacht eine Checkbox.

Livewire (Registrierung)

  • Den Container mit wire:ignore ausnehmen.
  • Ein Callback setzt das Token in ein Property.
  • Nach einem Validierungsfehler turnstile.reset() aufrufen. Sonst scheitert der zweite Versuch am verbrauchten Token.

CSP: https://challenges.cloudflare.com muss in script-src und frame-src aufgenommen werden (App\Support\ContentSecurityPolicy). Das gehört vor das Scharfschalten der CSP und deckt sich mit der Wiedervorlage ab 15.10.

Tests und lokale Entwicklung

  • phpunit.xml leert die Keys. Die Tests nutzen Http::fake.
  • Lokal kommen die Cloudflare-Testkeys in die .env (1x… = besteht immer, 2x… = scheitert immer). Die echten Keys sind an die Live-Hostnamen gebunden.

Cloudflare-Dashboard: Beim Widget müssen alle Hostnamen eingetragen sein: pressekonto.com, presseecho.de, businessportal24.com sowie weitere Live-Domains aus config/domains.php.

B2 – Inhaltsfilter fürs Kontaktformular (zweite Linie)

Diese Regeln gelten nur für nicht angemeldete Absender. Kunden bleiben ungefiltert.

  • Links: URL, www. oder <a href im Text. Das deckt 56 Fälle ab. Eine Regex dafür gibt es bereits in NoPromotionalText. Zusätzlich die vorhandene BlacklistService nutzen.
  • Floskel-Erkennung: Text enthält die Meta-Beschreibung oder den Claim der Seite. Das deckt 43 Fälle ab.
  • Zeichensalat: Name ohne Vokalstruktur oder mit Ziffernblock in Großbuchstaben, wie in den Beispielen.
  • Zeitfalle: ein signierter Zeitstempel im Formular. Absenden unter 3 Sekunden oder nach mehr als 2 Stunden gilt als verdächtig.
  • E-Mail: Die Regel PlausibleEmailDomain gibt es schon, sie wird mitbenutzt.

B3 – Spam-Quarantäne statt Löschen

Neuer Status ContactMessageStatus::Spam

  • Gefilterte Anfragen werden mit dem Grund in payload gespeichert.
  • Für sie geht keine Mail raus.
  • Sie zählen nicht im Nav-Zähler und stehen nicht im Standardfilter der Admin-Queue.
  • Fehlalarme lassen sich so prüfen und mit „Kein Spam“ zurückholen. Die Mail wird dann nachgeholt.

Antwort an den Absender: immer dieselbe neutrale Erfolgsmeldung, wie beim Honeypot heute. Der Bot lernt nichts.

Ausnahme ist ein fehlendes oder ungültiges Turnstile-Token. Dann gibt es eine sichtbare Fehlermeldung („Bitte bestätigen Sie, dass Sie kein Roboter sind“), denn ein echter Mensch muss es erneut versuchen können.

Aufräumen: Spam-Einträge werden nach 30 Tagen automatisch gelöscht (Scheduler, prune).

Admin

  • In der Liste: Sammelaktion „Als Spam markieren“.
  • In der Detailansicht: „Kein Spam“.

B4 – Registrierung absichern

  • Turnstile auf /register (B1).

  • Zufallsnamen ablehnen: Muster wie „ruLrlotwaCoVStmZetSxD“, also ein Wort ab 12 Buchstaben mit vielen Großbuchstaben mitten im Wort. Die Fehlermeldung bleibt neutral. Das ist bewusst eng gefasst, damit echte Namen nie getroffen werden.

  • Bot-Konten aufräumen: Ein Scheduler-Befehl löscht unbestätigte Konten nach 14 Tagen.

    Achtung: Im Bestand sind 66.870 unbestätigte Altkonten aus dem Legacy-Import. Diese dürfen nie erfasst werden. Gelöscht wird deshalb nur, wenn alle drei Bedingungen gelten:

    1. das Konto wurde über das neue Registrierungsformular angelegt, also nach dem Go-live und ohne Legacy-ID;
    2. die E-Mail wurde nie bestätigt;
    3. das Konto hat keine Firma, keine Meldung, keine Bestellung und keinen Login nach der Registrierung.

    Zuerst gibt es einen Probelauf mit --dry-run.

B5 – Übrige öffentliche Formulare

  • Dieselbe Komponente <x-turnstile> kommt in diese Formulare:
    • Widerruf
    • Kündigung
    • Meldeformular
    • PM-Meldung (Link anfordern und Anliegen)
    • Rechtsanfrage
    • Newsroom verwalten
  • Kündigung und Widerruf sind gesetzlich vorgeschrieben (§ 312k BGB, Widerrufsrecht). Ist Cloudflare nicht erreichbar, werden sie dort trotzdem angenommen (fail-open) und nur als „ohne Bot-Prüfung“ im Log vermerkt.

B6 – Beobachtung

  • Jede abgewiesene Anfrage wird mit Formular, Grund und IP-Präfix gezählt, ohne Inhalt. Dafür ist ein eigener Log-Kanal spam vorgesehen, analog zu csp.
  • Die Betriebsüberwachung bekommt einen Check: In Produktion sind die Turnstile-Keys gesetzt.
  • Nach 7 Tagen wird ausgewertet, wie viel durchkommt und wie viel in der Quarantäne liegt.

3. Reihenfolge

Phase Inhalt Wirkung
1 B1 + B2 + B3 fürs Kontakt-Widget, CSP-Ergänzung Die täglichen Spam-Mails hören auf.
2 B4: Turnstile und Namensregel bei der Registrierung Keine Bestätigungsmails mehr an fremde Postfächer.
3 B4: Aufräumbefehl für Bot-Konten (erst Probelauf) Rund 390 Bot-Konten weg, saubere Kennzahlen.
4 B5: übrige Formulare Gleicher Schutz überall.
5 B6: Auswertung nach 7 Tagen Schwellen nachschärfen oder lockern.

Phasen 1 und 2 passen in ein Release. Es ist keine Migration nötig: contact_messages.status ist eine String-Spalte (string(40)), der Status spam ist nur ein neuer Enum-Wert.

4. Entscheidungen (09.10.2026)

Alle wie empfohlen, mit einer Anpassung bei Nr. 1: Bei nicht erreichbarem Cloudflare wird überall durchgelassen und markiert, also auch bei Kontaktformular und Registrierung. Nr. 4: Ein Textvorschlag ist eingebaut.

  1. Cloudflare nicht erreichbar. Empfehlung:
    • Kontaktformular und Registrierung: fail-closed, mit der Meldung „bitte später erneut“.
    • Kündigung und Widerruf: fail-open (siehe B5).
  2. Altbestand im Kontakt-Postfach. Rund 140 offene Spam-Anfragen der letzten 30 Tage nachträglich als Spam markieren? Empfehlung: ja, per einmaligem Befehl mit Probelauf, nach denselben Regeln wie B2.
  3. Bot-Konten löschen (B4, Phase 3). Empfehlung: ja, aber erst nach deiner Freigabe der Probelauf-Liste.
  4. Datenschutzerklärung. Turnstile überträgt die IP und Browsermerkmale an Cloudflare (USA, EU-US Data Privacy Framework). Ein Absatz in datenschutz.blade.php ist nötig, Rechtsgrundlage Art. 6 Abs. 1 lit. f DSGVO (Missbrauchsschutz). Den Text sollte jemand juristisch freigeben.
  5. Links von Gästen. Komplett sperren oder nur ab einem Link in Kombination mit einem weiteren Signal? Empfehlung: Gäste dürfen keine Links senden. Echte Anfragen kamen in der Stichprobe ohne Link oder von angemeldeten Kunden.

5. Weitere Ideen (nicht eingeplant)

  • Ein Limit pro /24-Netz zusätzlich zum Limit pro IP. Das würde 212.30.36.x direkt bremsen, Turnstile macht das aber weitgehend überflüssig.
  • Statt einer Mail pro Anfrage eine tägliche Sammelmail für das Kontakt-Postfach.
  • Einwegadressen-Domains wie notboxletters.com und belettersmail.com als kleine Sperrliste. Sie wechseln aber ständig.
  • KI-Klassifizierung der Nachricht. Für diese Mengen ist das überdimensioniert. Sinnvoll erst, wenn nach Turnstile noch nennenswert Spam durchkommt.