Offene Registrierung gegen Bot-Massenanlage absichern
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>
This commit is contained in:
+27
-2
@@ -5,6 +5,7 @@ require_once __DIR__ . '/app/saas-mail.php';
|
||||
require_once __DIR__ . '/app/rate-limit.php';
|
||||
require_once __DIR__ . '/app/legal.php';
|
||||
require_once __DIR__ . '/app/pushover.php';
|
||||
require_once __DIR__ . '/app/spam-guard.php';
|
||||
|
||||
$pdo = app_db_pdo();
|
||||
$errors = [];
|
||||
@@ -19,8 +20,30 @@ $values = [
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
app_require_csrf();
|
||||
|
||||
if (!app_rate_limit_check($pdo, 'register_ip:' . app_client_ip(), 5, 3600)) {
|
||||
$errors = ['Zu viele Registrierungsversuche. Bitte versuche es später erneut.'];
|
||||
// Reihenfolge mit Absicht: erst die billigen Bot-Merkmale, dann die
|
||||
// Rate-Limits, erst danach die DNS-Abfrage. Alle Abweisungen bis
|
||||
// einschliesslich Rate-Limit nennen denselben generischen Text, damit die
|
||||
// Antwort nicht verraet, welche Huerde angeschlagen hat.
|
||||
$genericRejection = 'Zu viele Registrierungsversuche. Bitte versuche es später erneut.';
|
||||
|
||||
if (app_spam_guard_honeypot_tripped()) {
|
||||
app_spam_guard_log('register', 'honeypot');
|
||||
$errors = [$genericRejection];
|
||||
} elseif (app_spam_guard_submitted_too_fast('register')) {
|
||||
app_spam_guard_log('register', 'zu schnell abgesendet');
|
||||
$errors = [$genericRejection];
|
||||
} elseif (!app_rate_limit_check($pdo, 'register_ip:' . app_client_ip(), 5, 3600)) {
|
||||
$errors = [$genericRejection];
|
||||
} elseif (!app_rate_limit_check($pdo, 'register_global', APP_SPAM_GUARD_GLOBAL_MAX, APP_SPAM_GUARD_GLOBAL_WINDOW)) {
|
||||
// Notbremse gegen verteilte Bots: die kommen je ueber eine eigene IP,
|
||||
// an der das IP-Limit nie anschlaegt.
|
||||
app_spam_guard_log('register', 'globales Limit');
|
||||
$errors = [$genericRejection];
|
||||
} elseif (filter_var($values['email'], FILTER_VALIDATE_EMAIL) !== false
|
||||
&& !app_spam_guard_email_domain_resolves($values['email'])) {
|
||||
// Hier bewusst konkret: das ist typischerweise ein Tippfehler in der
|
||||
// Domain, und der Hinweis hilft einem echten Nutzer weiter.
|
||||
$errors = ['Zu dieser E-Mail-Adresse gibt es keine erreichbare Mail-Domain. Bitte prüfe die Schreibweise.'];
|
||||
} else {
|
||||
$result = saas_register_tenant_owner($pdo, [
|
||||
'tenant_name' => $values['tenant_name'],
|
||||
@@ -113,8 +136,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php app_spam_guard_mark_form_rendered('register'); ?>
|
||||
<form method="post" action="register.php">
|
||||
<?php echo app_csrf_field(); ?>
|
||||
<?php echo app_spam_guard_honeypot_field(); ?>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<fieldset>
|
||||
|
||||
Reference in New Issue
Block a user