Auf der Testumgebung liefen seit Juli 2026 taeglich 10-20 automatisierte Registrierungen, die zufaellige Namen mit fremden echten E-Mail-Adressen kombinierten - der Server verschickte Vertrags- und Verifikationsmail an Unbeteiligte. Das IP-Rate-Limit griff nicht, weil jede Anfrage ueber eine eigene Rechenzentrums-IP kam. app/spam-guard.php ergaenzt daher Honigtopf, Zeitfalle, eine globale Notbremse ueber alle IPs hinweg und eine MX/A-Pruefung der Mail-Domain. Die ersten drei antworten mit derselben generischen Meldung wie das Rate-Limit, damit die Antwort nicht verraet, welche Huerde angeschlagen hat; Treffer landen im Error-Log. Bewusst ohne Captcha, um keinen Drittanbieter in den Registrierungspfad zu holen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
136 lines
3.6 KiB
PHP
136 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Regressionstest fuer app/spam-guard.php - die Huerden, die seit der
|
|
* Bot-Registrierungswelle auf der Testumgebung vor register.php stehen.
|
|
* Braucht keine Datenbank: geprueft werden Honigtopf, Zeitfalle,
|
|
* Domain-Aufloesung und die Gestalt des versteckten Feldes.
|
|
*/
|
|
|
|
require __DIR__ . '/../app/spam-guard.php';
|
|
|
|
function spam_guard_assert(string $label, bool $condition, array &$failures, int &$passes): void
|
|
{
|
|
if ($condition) {
|
|
$passes++;
|
|
echo "PASS {$label}\n";
|
|
return;
|
|
}
|
|
|
|
$failures[] = $label;
|
|
echo "FAIL {$label}\n";
|
|
}
|
|
|
|
$failures = [];
|
|
$passes = 0;
|
|
|
|
// --- Honigtopf ---
|
|
$_POST = [];
|
|
spam_guard_assert('Leeres Honigtopf-Feld ist unauffaellig', !app_spam_guard_honeypot_tripped(), $failures, $passes);
|
|
|
|
$_POST['contact_reference'] = 'https://example.com';
|
|
spam_guard_assert('Ausgefuellter Honigtopf schlaegt an', app_spam_guard_honeypot_tripped(), $failures, $passes);
|
|
|
|
$_POST['contact_reference'] = ' ';
|
|
spam_guard_assert('Nur Leerzeichen zaehlen nicht als Treffer', !app_spam_guard_honeypot_tripped(), $failures, $passes);
|
|
$_POST = [];
|
|
|
|
// --- Zeitfalle ---
|
|
$_SESSION = [];
|
|
spam_guard_assert(
|
|
'Ohne ausgeliefertes Formular gilt die Antwort als zu schnell',
|
|
app_spam_guard_submitted_too_fast('register'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
app_spam_guard_mark_form_rendered('register');
|
|
spam_guard_assert(
|
|
'Sofortiges Absenden nach dem Rendern schlaegt an',
|
|
app_spam_guard_submitted_too_fast('register'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
// Menschliche Bearbeitungszeit simulieren, ohne den Test warten zu lassen.
|
|
$_SESSION['spam_guard_rendered']['register'] = time() - (APP_SPAM_GUARD_MIN_SECONDS + 1);
|
|
spam_guard_assert(
|
|
'Nach ausreichender Bearbeitungszeit geht die Antwort durch',
|
|
!app_spam_guard_submitted_too_fast('register'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
spam_guard_assert(
|
|
'Der Zeitstempel gilt nur fuer sein eigenes Formular',
|
|
app_spam_guard_submitted_too_fast('anderes-formular'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
// --- Domain-Aufloesung ---
|
|
spam_guard_assert(
|
|
'Domain mit MX-Eintrag wird akzeptiert',
|
|
app_spam_guard_email_domain_resolves('jemand@gmail.com'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
spam_guard_assert(
|
|
'Erfundene Domain wird abgewiesen',
|
|
!app_spam_guard_email_domain_resolves('jemand@diese-domain-gibt-es-ganz-sicher-nicht-4711.invalid'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
spam_guard_assert(
|
|
'Adresse ohne Punkt in der Domain wird abgewiesen',
|
|
!app_spam_guard_email_domain_resolves('jemand@localhost'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
spam_guard_assert(
|
|
'Zeichenkette ohne @ wird abgewiesen',
|
|
!app_spam_guard_email_domain_resolves('keine-adresse'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
// --- Gestalt des versteckten Feldes ---
|
|
$field = app_spam_guard_honeypot_field();
|
|
spam_guard_assert(
|
|
'Honigtopf ist aus dem Sichtbereich geschoben',
|
|
str_contains($field, 'left:-9999px'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
spam_guard_assert(
|
|
'Honigtopf ist fuer Screenreader und Tastatur unsichtbar',
|
|
str_contains($field, 'aria-hidden="true"') && str_contains($field, 'tabindex="-1"'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
spam_guard_assert(
|
|
'Honigtopf wird nicht von Passwortmanagern befuellt',
|
|
str_contains($field, 'autocomplete="off"'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
spam_guard_assert(
|
|
'Honigtopf ist kein type=hidden (das ueberspringen Bots)',
|
|
!str_contains($field, 'type="hidden"'),
|
|
$failures,
|
|
$passes
|
|
);
|
|
|
|
echo "\n";
|
|
if ($failures !== []) {
|
|
fwrite(STDERR, 'Spam-Guard-Check fehlgeschlagen: ' . implode('; ', $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
|
|
echo "Spam-Guard-Check bestanden mit {$passes} Zusicherungen.\n";
|