Zurück zum Blog
Sicherheit

Praktischer Leitfaden zur Absicherung von B2B-SaaS-Registrierungen

Josselin Liebe
Josselin Liebe

Letzten Monat kam ein B2B-SaaS-Kunde mit einem vertrauten Problem zu uns: Registrierungen wirkten oberflachlich normal, aber kostenlose Credits verschwanden schnell. Die Analyse der Logs offenbarte einen koordinierten Angriff — Dutzende Registrierungen aus denselben /24-Subnetzen, rotierende Wegwerf-E-Mails und ein Angreifer, der Infrastruktur uber mehrere Fake-Accounts wiederverwendete.

Dieser Artikel beschreibt die Verteidigungs-Stack, die wir empfohlen haben. Namen und Kennungen sind anonymisiert, der Ablauf ist produktionsreif.

Was den Angriff erleichterte

Drei Faktoren spielten dem Angreifer in die Hand:

  1. Alle kostenlosen Credits bei der Registrierung — keine Reibung, keine Verifizierung, sofortiger Wert.
  2. Keine Geratebindung — derselbe Browser-Fingerprint konnte sich immer wieder registrieren.
  3. IP-Prufungen fehlten oder waren falsch konfiguriert — hinter Cloudflares Proxy (orange Wolke) las die App Proxy-IPs statt der echten Besucheradresse.

Registrierungssicherheit ist weniger eine Silberkugel als das Schichten von Signalen, die sich gegenseitig verstarken.

Prioritat 1: Die echte IP-Adresse lesen

Wenn Ihre App hinter Cloudflare mit aktiviertem Proxy lauft (orange Wolke), reichen req.ip oder X-Forwarded-For allein nicht. Cloudflare sendet die echte Besucher-IP im Header CF-Connecting-IP.

function getClientIp(request: Request): string {
  const cfIp = request.headers.get("cf-connecting-ip");
  if (cfIp) return cfIp;

  const forwarded = request.headers.get("x-forwarded-for");
  if (forwarded) return forwarded.split(",")[0].trim();

  return "0.0.0.0";
}

Jede nachgelagerte Prufung — Rate Limits, IP-Reputation, Sperrung — hangt davon ab, diesen ersten Schritt richtig zu machen.

Prioritat 2: IP bei der Registrierung bewerten

Fuhren Sie die Veille IP Reputation API asynchron wahrend der Registrierung aus. Blockieren Sie die HTTP-Antwort moglichst nicht darauf; stellen Sie die Prufung in die Warteschlange und handeln Sie innerhalb von Sekunden.

const API_KEY = process.env.VEILLE_API_KEY!;
const BASE_URL = "https://api.veille.io/v1";

async function scoreIp(ip: string) {
  const response = await fetch(
    `${BASE_URL}/intelligence/ip?query=${encodeURIComponent(ip)}`,
    { headers: { "x-api-key": API_KEY } }
  );
  return response.json();
}

Der threat_score aggregiert erkannte Signale:

Score Bedeutung
0 Saubere Wohn- oder Geschafts-IP
1–25 Geringe Signale (Rechenzentrum, VPN)
25–50 Moderates Risiko (mehrere Signale)
50+ Hohes Risiko (Tor, bekannter Missbraucher, Proxy) — sperren oder Extra-Verifizierung

In den Logs unseres Kunden gruppierten sich Angreifer-IPs in /24-Bereichen mit Scores uber 50. Das Sperren des Subnetzes nach wiederholtem Missbrauch — nicht nur der einzelnen IP — reduzierte das Volumen schnell.

Prioritat 3: FingerprintJS integrieren

Installieren Sie FingerprintJS auf Ihren Registrierungs- und Login-Seiten. Speichern Sie die visitorId bei jedem Versuch.

import FingerprintJS from "@fingerprintjs/fingerprintjs";

async function getFingerprint(): Promise<string> {
  const fp = await FingerprintJS.load();
  const result = await fp.get();
  return result.visitorId;
}

Dann Rate Limits auf IP und Fingerprint anwenden:

  • Nach 5 fehlgeschlagenen Registrierungs- oder Login-Versuchen derselben IP oder desselben Fingerprints → Sperre 24 Stunden.
  • Beide Werte bei jedem Versuch loggen, um im Back Office zu pivotieren.

Prioritat 4: E-Mail asynchron validieren

Wegwerf-E-Mails stoppen keinen entschlossenen Angreifer, filtern aber einen grossen Teil des Low-Effort-Missbrauchs. Rufen Sie die Veille Email Validation API parallel zur IP-Prufung auf:

async function validateEmail(email: string) {
  const response = await fetch(
    `${BASE_URL}/intelligence/email?query=${encodeURIComponent(email)}`,
    { headers: { "x-api-key": API_KEY } }
  );
  return response.json();
}

// Im Registrierungs-Handler:
const [ipResult, emailResult] = await Promise.all([
  scoreIp(clientIp),
  validateEmail(email),
]);

if (emailResult.disposable || emailResult.risk_score > 70) {
  await blockIpTemporarily(clientIp, "disposable_email");
  return { error: "Registrierung blockiert" };
}

if (ipResult.threat_score >= 50) {
  await blockIpTemporarily(clientIp, "high_threat_ip");
  return { error: "Registrierung blockiert" };
}

Fuhren Sie diese Prufungen nach Formularannahme aber vor der Credit-Vergabe aus. Schlagt ein Signal fehl, sperren Sie die IP fur ein konfigurierbares Zeitfenster und geben Sie einen generischen Fehler zuruck.

Prioritat 5: Fingerprint an E-Mail-Verifizierung binden

Ein Konto erstellen ist nicht dasselbe wie zu beweisen, dass Sie die Inbox kontrollieren. Verlangen Sie E-Mail-Verifizierung, bevor Credits nutzbar sind.

Der kritische Schritt, den die meisten Teams auslassen: Der Fingerprint bei der Registrierung muss mit dem Fingerprint beim Klick auf den Verifizierungslink ubereinstimmen.

// Bei Registrierung — ausstehende Verifizierung speichern
await db.pendingVerifications.create({
  email,
  fingerprintSignup: fingerprint,
  ipSignup: clientIp,
  token: verificationToken,
  expiresAt: addHours(new Date(), 24),
});

// Bei Verifizierung — Fingerprint vergleichen
const pending = await db.pendingVerifications.findByToken(token);

if (pending.fingerprintSignup !== currentFingerprint) {
  await flagAccount(pending.email, "fingerprint_mismatch");
  return { error: "Verifizierung fehlgeschlagen" };
}

await activateAccount(pending.email);

Eine Abweichung bedeutet nicht immer Betrug — Nutzer wechseln Gerate — aber wiederholte Abweichungen von Hochrisiko-IPs sind ein starkes Signal.

Prioritat 6: Nicht mehr alle Credits bei der Registrierung vergeben

Kostenlose Credits sind der ROI des Angreifers. Verteilen Sie Wert auf Aktionen, die Bots schwer automatisieren konnen:

Meilenstein Credits Warum
Konto erstellt 0 Kein sofortiger extrahierbarer Wert
E-Mail verifiziert +10 Beweist Inbox-Kontrolle
Onboarding abgeschlossen +25 Erfordert echte Produktinteraktion
Social Share bestatigt +15 Erhoht Reibung und Audit-Trail

Beispiel-Flags im Back Office:

interface UserCredits {
  emailVerified: boolean;      // +10 Credits
  onboardingCompleted: boolean; // +25 Credits
  sharedOnSocial: boolean;      // +15 Credits
}

function getAvailableCredits(user: UserCredits): number {
  let credits = 0;
  if (user.emailVerified) credits += 10;
  if (user.onboardingCompleted) credits += 25;
  if (user.sharedOnSocial) credits += 15;
  return credits;
}

Bots optimieren den schnellsten Weg zu Credits. Ein Onboarding mit echten API-Aufrufen, Konfigurationsentscheidungen oder einem kurzen Tutorial bricht skriptierte Ablaufe.

Gesamtuberblick: Registrierungsablauf

Nutzer sendet Registrierungsformular
        │
        ▼
CF-Connecting-IP lesen
        │
        ▼
Fingerprint- + IP-Rate-Limits prufen ──► gesperrt? → 429, loggen
        │
        ▼
Konto erstellen (0 Credits)
        │
        ├──► async: IP-Reputation
        └──► async: E-Mail-Validierung
                    │
                    ▼
              Bedrohung erkannt? → IP sperren, Konto markieren
                    │
                    ▼
Verifizierungs-E-Mail senden (Registrierungs-Fingerprint speichern)
        │
        ▼
Link-Klick → Fingerprint passt? → +10 Credits
        │
        ▼
Onboarding abgeschlossen → +25 Credits
        │
        ▼
Social Share bestatigt → +15 Credits

Sinnvolle Back-Office-Booleans

Geben Sie Ihrem Team Schalter, um ohne Deployment zu reagieren:

  • block_disposable_emails — Wegwerf-Inboxes bei Registrierung ablehnen
  • block_high_threat_ips — IPs mit threat_score >= 50 automatisch sperren
  • require_fingerprint_match — Fingerprint-Bindung bei E-Mail-Verifizierung erzwingen
  • rate_limit_by_fingerprint — Fingerprint-basiertes Throttling aktivieren
  • grant_credits_on_signup — wahrend aktivem Angriff auf false lassen

Diese Flags erlauben, Verteidigung wahrend eines Vorfalls zu verscharfen und zu lockern, sobald sich der Traffic normalisiert.

Letzter Ausweg: verlangsamen, nicht ankundigen

Wenn eine IP klar missbrauchlich ist, aber Sie den Angreifer nicht warnen wollen, fuhren manche Teams kunstliche Reibung ein — verzogerte Antworten, CAPTCHA beim zweiten Versuch oder ein falscher SMS-Code, der nie ankommt. Das ist umstritten, sollte letzter Ausweg sein, intern klar geloggt werden und nie legitimen Nutzern gezeigt werden.

Das Ziel ist, die Zeit des Angreifers zu verbrennen, nicht Ihre.

Ergebnisse

Nach Implementierung dieser Stack — echte IP-Extraktion, Veille IP- und E-Mail-Checks, FingerprintJS Rate Limits, fingerprint-gebundene Verifizierung und gestaffelte Credits — sank das Volumen missbrauchlicher Registrierungen des Kunden innerhalb von 48 Stunden deutlich. Legitime Nutzer schlossen das Onboarding ab, ohne die zusatzlichen Prufungen zu bemerken.

Verwandte Artikel