Manche Sicherheits-Lücken sind in keiner OWASP-Cheat-Sheet-Liste und in keiner Vulnerability-Scanner-Datenbank — weil sie logische Fehler in der Anwendung selbst sind. Workflow lässt sich überspringen, Coupon-Codes lassen sich doppelt einlösen, parallele Anfragen produzieren widersprüchliche Zustände, Validierung passiert zur falschen Zeit. Dieser Artikel behandelt Business-Logic-Flaws und Race Conditions — zwei eng verwandte Klassen, die in Pentests oft die wertvollsten Funde liefern und in produktiven Systemen reale Schäden anrichten.

Was diese Klasse so schwer macht

Klassische Web-Schwachstellen — XSS, SQLi, CSRF — haben Signatur-Muster: bestimmte Eingabe-Strings, bestimmte API-Aufrufe, bestimmte Token-Strukturen. Scanner finden sie, weil das Muster eindeutig ist.

Business-Logic-Flaws dagegen entstehen aus der Kombination legitimer Operationen, die in einer Reihenfolge oder Anzahl ausgeführt werden, die der Entwickler nicht erwartet hat:

  • Bezahl-Workflow lässt sich überspringen, weil der Bezahl-Schritt nur durch Frontend-State enforced wird.
  • Coupon-Code wird im selben Sekundenbruchteil 100-mal eingelöst, weil keine Locking-Logik existiert.
  • Konto-Saldo wird parallel mit zwei Requests abgehoben — beide Requests sehen den ursprünglichen Saldo, beide ziehen ab, Konto wird negativ.
  • Passwort-Reset-Token bleibt nach Nutzung gültig, weil das Invalidieren erst nach der erfolgreichen Reset-Aktion passiert.

Diese Klasse verlangt Verständnis der Anwendung — nicht Suche nach Mustern. Genau deshalb sind Bug-Bounty-Reports in dieser Klasse meist die teuersten: sie werden manuell von Forschenden gefunden, die die Anwendung gründlich studiert haben.

Race Conditions: das Concurrency-Problem

Race Conditions sind eine spezifische Unterklasse: zwei oder mehr Operationen laufen parallel, und das Endergebnis hängt von der Reihenfolge ab, in der sie tatsächlich abgearbeitet werden.

Klassisches Beispiel: doppelte Coupon-Einlösung.

JavaScript coupon-race-condition.js
// Schadhaft: Race Condition möglich
async function redeemCoupon(userId, code) {
  const coupon = await db.coupons.findOne({ code, used: false });
  if (!coupon) throw new Error('Coupon ungültig oder bereits eingelöst');

  // Zwischen findOne und update liegt ein Zeitfenster.
  // Parallele Requests sehen alle: coupon.used === false.

  await db.coupons.update({ code }, { used: true, redeemedBy: userId });
  await db.users.update({ id: userId }, { $inc: { balance: coupon.amount } });
}

Was geht schief:

  1. User schickt 50 parallele Requests mit demselben Coupon-Code.
  2. Alle 50 lesen die DB: coupon.used === false.
  3. Alle 50 prüfen den Check — alle bestehen.
  4. Alle 50 führen den Update aus.
  5. Alle 50 erhöhen das User-Balance.

Statt einmal 10 EUR Gutschrift hat User jetzt 500 EUR.

Schutz-Patterns:

JavaScript coupon-atomar.js
// Sicher: atomare Operation auf DB-Ebene
async function redeemCoupon(userId, code) {
  // findAndModify liefert null, wenn Coupon schon eingelöst
  const coupon = await db.coupons.findOneAndUpdate(
    { code, used: false },
    { $set: { used: true, redeemedBy: userId } },
    { returnDocument: 'after' }
  );

  if (!coupon) throw new Error('Coupon ungültig oder bereits eingelöst');

  // Erst nach erfolgreicher atomarer Markierung den Balance erhöhen
  await db.users.update({ id: userId }, { $inc: { balance: coupon.amount } });
}

Die atomare DB-Operation findOneAndUpdate mit Filter used: false lässt nur einen parallelen Request durch — die anderen 49 bekommen null zurück und scheitern.

TOCTOU — Time-of-Check vs. Time-of-Use

Eine spezifische Race-Condition-Variante: Time-of-Check to Time-of-Use. Anwendung prüft eine Bedingung, führt danach die Aktion aus. Zwischen Prüfung und Aktion ändert sich der Zustand.

Klassisches Beispiel: Konto-Überweisung.

JavaScript toctou-transfer.js
// Schadhaft: TOCTOU
async function transfer(fromId, toId, amount) {
  const account = await db.accounts.findOne({ id: fromId });

  if (account.balance < amount) {
    throw new Error('Insufficient funds');
  }

  // Zeitfenster: parallele Requests können hier zwischenliegen
  await sleep(0); // simuliert IO-Wartezeit

  await db.accounts.update({ id: fromId }, { $inc: { balance: -amount } });
  await db.accounts.update({ id: toId }, { $inc: { balance: amount } });
}

Mit zwei parallelen 100-EUR-Transfers von einem Konto mit 150 EUR Guthaben: beide Checks sehen 150 EUR > 100 EUR. Beide gehen durch. Konto wird auf -50 EUR.

Schutz-Patterns:

  • Atomare Operationen mit Bedingung — UPDATE accounts SET balance = balance - 100 WHERE id = ? AND balance >= 100 RETURNING balance — DB selbst prüft und ändert in einem Schritt.
  • Datenbank-Transaktionen mit Serializable Isolation — DB serialisiert parallele Transaktionen.
  • Optimistic Locking — Version-Spalte, Update schlägt fehl, wenn Version sich geändert hat.
  • Pessimistic Locking — SELECT ... FOR UPDATE sperrt die Zeile bis Transaktion-Ende.

Workflow-Bypass

Multi-Step-Workflows (Checkout, Onboarding, Reset-Verfahren) bestehen aus mehreren Schritten, oft mit Validierung pro Schritt. Wenn die einzelnen Schritte unabhängig erreichbar sind und nicht serverseitig vorgeschrieben werden, lassen sich Schritte überspringen.

Klassische Beispiele:

  • Bezahl-Schritt überspringen. Checkout hat Schritte: Cart → Adresse → Bezahlung → Bestätigung. Wenn der „Bestätigung"-Endpunkt direkt aufrufbar ist, ohne dass der Bezahl-Schritt im Server-State markiert ist — Bestellung wird ohne Bezahlung ausgelöst.
  • MFA überspringen. Login-Endpunkt setzt Session-Cookie nach Passwort-Eingabe. MFA-Endpunkt prüft Code und „aktiviert" die Session. Wenn die Session schon vor MFA gültig ist, kann der MFA-Schritt übergangen werden, indem direkt auf andere Endpunkte gegangen wird.
  • E-Mail-Verifikations-Überspringung. Account hat verified: false nach Registrierung. Wenn sicherheitsrelevante Aktionen nicht alle auf verified: true prüfen, lassen sie sich vor Verifikation ausführen.
  • Free-Trial-Schritt überspringen. Onboarding-Workflow: 14-Tage-Trial → Kreditkarten-Eingabe → Vollzugang. Wenn der „Vollzugang"-Schritt nicht prüft, ob Kreditkarte hinterlegt ist — Endlos-Trial möglich.

Schutz-Patterns:

  • Server-seitiger State-Machine-Status — Workflow-State wird ausschließlich serverseitig gehalten. Jeder Schritt validiert, dass der vorherige Schritt abgeschlossen wurde.
  • Step-Tokens — jeder Schritt gibt ein einmalig gültiges Token zurück, das für den nächsten Schritt benötigt wird.
  • Sequenz-IDs mit serverseitiger Validierung — Reihenfolge ist nicht durch Client manipulierbar.
  • Default-Deny für unbestätigte Workflows.

Limit-Bypass

Wenn Anwendung Limits enforce — „nur 3 Aktivierungen pro Tag", „nur 1 Gutschein pro Konto" — gibt es oft kreative Umgehungs-Wege.

Klassische Beispiele:

  • Per-IP-Limit umgangen durch Tor / VPN / mobiles Netz.
  • Per-Account-Limit umgangen durch Massen-Account-Erstellung (Stichwort: Sneaker-Drops, Bonus-Aktionen).
  • Per-E-Mail-Limit umgangen durch Gmail-Punkte-Trick (alice@gmail.com, a.lice@gmail.com, al.ice@gmail.com sind dieselbe Adresse, werden aber als verschieden gespeichert).
  • Per-Karten-Limit umgangen durch verschiedene Karten desselben Nutzers.
  • Zeitliche Limits umgangen durch Manipulation der Server-Zeit-Annahmen.
  • Negative Werte für Mengen — Bestellung mit quantity: -5 führt zu Guthaben.

Schutz-Patterns:

  • Mehrschichtige Identifikation für Limits — IP + Account + Device-Fingerprint + Zahlungs-Methode kombiniert.
  • Server-seitige Werte-Validierung — keine negativen Werte, keine Werte außerhalb plausibler Bereiche.
  • CAPTCHA / Bot-Schutz vor Massen-Account-Erstellung.
  • Verzögerte Aktivierung und Hold-Times für Bonus-Auszahlungen.
  • E-Mail-Normalisierung vor Vergleichen (lowercase, Gmail-Punkte entfernen).

Authentifizierungs-/Reset-Workflows

Eine besonders sensitive Klasse — Password-Reset und Account-Recovery sind klassische Business-Logic-Fallen.

Konkrete Anti-Patterns:

  • Reset-Token nicht nach Nutzung invalidiert — User kann mit altem Token zurück.
  • Reset-Token nicht nach Passwort-Wechsel invalidiert — wenn parallel ein Reset offen war, kann der/die Angreifer:in das Passwort später nochmal ändern.
  • Session-Cookies bei Passwort-Wechsel nicht invalidiert — wenn Angreifer:in eine Session hatte, behält sie die Session auch nach dem User-Passwort-Wechsel.
  • Reset-Mail kann beliebig oft angefordert werden — Spam-Quelle, manchmal Account-Lockout-Vektor.
  • Reset-Workflow exposes ob ein Account existiert — Enumeration-Risiko.
  • OAuth-Account-Linking ohne erneute Authentifizierung — Angreifer:in mit OAuth-Provider-Zugang verknüpft eigenen Provider mit Opfer-Konto.

Schutz-Patterns:

  • Reset-Token: einmalig + kurz gültig (max. 1 Stunde) + invalidiert bei Nutzung + invalidiert bei Passwort-Wechsel über anderen Weg.
  • Alle Sessions terminieren bei Passwort-Wechsel oder MFA-Änderung.
  • Rate-Limit auf Reset-Anfrage (z. B. max 3 pro Tag pro Account).
  • Generische Antworten bei Reset-Anforderung (kein „Account existiert nicht").
  • Re-Auth bei kritischen Aktionen — Passwort-Wechsel, OAuth-Link, MFA-Removal verlangen erneute Passwort-Eingabe.

Numerische und State-Probleme

Eine Sammlung typischer Edge-Cases:

  • Integer-Overflow / Underflow — quantity * price wird negativ bei extrem hohen Werten. Bei JavaScript-Number ab 2^53 interessante Effekte.
  • Floating-Point-Rundung — Geld in Float speichern (0.1 + 0.2 = 0.30000000000000004) führt zu Diskrepanzen.
  • Negative Zahlen wo positive erwartet wurden — quantity: -5 als „Rückbuchung".
  • Race auf Counter — zwei parallele +1-Operationen führen zu +1 statt +2, wenn nicht atomar.
  • State-Transitions nicht validiert — Bestellung kann von „shipped" zurück auf „pending" gesetzt werden, was Re-Bezahl ermöglichen würde.
  • Cancellation-Logic — Bestellung canceln führt zur Auszahlung, obwohl noch nicht bezahlt wurde.
  • Zeitliche Annahmen — Server-Zeitzone vs. Client-Zeitzone, Coupon „gültig bis 23:59" wird per Client-Zeit umgangen.

Schutz-Patterns:

  • Schema-Validierung mit strikten Typen (positive Integer, Min/Max-Range).
  • Currency-Library für Geld-Berechnungen (Dezimal-Typen statt Float).
  • State-Machine mit erlaubten Transitions, ungültige Transitions werfen Fehler.
  • Atomare Counter (UPDATE ... SET count = count + 1 statt count = ?).
  • Server-Zeit für alle Zeit-Vergleiche.

Wie man Business-Logic-Flaws findet

Drei Strategien — alle aufwendig, alle wertvoll:

1. Bedrohungs-Modellierung pro Workflow. Für jede sensible User-Story: „Was, wenn diese Aktion zweimal gleichzeitig ausgeführt wird?", „Was, wenn Schritt 3 vor Schritt 2 kommt?", „Was, wenn ein Wert außerhalb der erwarteten Range liegt?". Antworten dokumentieren und testen.

2. Property-Based Testing. Statt Beispiele zu testen, Eigenschaften der Anwendung deklarieren — und Tools (Hypothesis, fast-check, jqwik) generieren Tausende Test-Variationen. Findet Edge-Cases, die manuelle Tests verpassen.

3. Adversarial-Modus im Test-Team. Ein Test-Mitglied übernimmt explizit die Rolle des „bösen Nutzers": versucht doppelt einzulösen, Workflow zu überspringen, Limits zu umgehen. Sehr produktiver Pentest-Modus, der oft mehr Befunde liefert als Standard-Scanner.

4. Bug Bounty als laufende Quelle. Forschende finden Business-Logic-Flaws regelmäßig — sie haben das Verständnis, das automatische Tools nicht haben. HackerOne- und Bugcrowd-Reports sind voll von kreativen Workflow-Bypass- und Race-Condition-Funden.

Werkzeuge für Race-Condition-Tests

Konkrete Werkzeuge, mit denen Forschende parallele Anfragen erzeugen:

  • Burp Suite Repeater + Send Group in Parallel — Burp Suite Pro kann Anfragen-Gruppen in Single-Packet-Attacks senden, sodass sie nahezu zeitgleich am Server ankommen.
  • Turbo Intruder (Burp-Erweiterung von James Kettle) — extrem schnelles parallel Senden mit kontrollierbarer Konkurrenz.
  • ab / wrk / hey — klassische HTTP-Last-Test-Tools, auch für Race-Tests nutzbar.
  • xargs -P + curl — niedrigschwellig: parallele Curl-Aufrufe per xargs.

Für Bug-Bounty-Berichte ist die Reproduzierbarkeit wichtig: ein kurzes Python- oder Curl-Script, das die Race-Condition zuverlässig auslöst, macht den Bericht wertvoller.

Häufige Stolperfallen

Race Conditions sind 2023/24 stark gewachsen

Mit dem Aufkommen von HTTP/2-Multiplexing und Single-Packet-Attack-Techniken (James Kettle, PortSwigger Research 2023) sind Race Conditions in der Bug-Bounty-Welt zu einem der attraktivsten Funde-Themen geworden. Anfragen lassen sich in Mikrosekunden-Fenstern parallelisieren, was viele Schutz-Annahmen bricht.

DB-Transaktionen lösen nicht alles

Eine Datenbank-Transaktion mit Default-Isolation-Level (Read Committed) hilft nicht zwingend gegen Race Conditions. Du brauchst entweder atomare Update-mit-Bedingung, Serializable Isolation oder explizites SELECT FOR UPDATE. Ein BEGIN; SELECT; UPDATE; COMMIT; ohne Locking ist nicht race-condition-sicher.

Idempotenz als Schutz

Eine Anfrage ist idempotent, wenn mehrfache Ausführung dasselbe Ergebnis hat wie einmalige. Klassisches Pattern: Client schickt einen Idempotency-Key mit jeder Anfrage. Server speichert die Antwort beim ersten Aufruf. Bei weiteren Aufrufen mit demselben Key wird die gespeicherte Antwort zurückgegeben, ohne die Aktion erneut auszuführen. Stripe, PayPal nutzen das produktiv.

Bug Bounty bezahlt für Logic-Flaws gut

In den meisten Bug-Bounty-Programmen sind Business-Logic-Flaws unter den teuersten Funden — schwer zu finden, oft hoher Wirkung, oft monetäre Auswirkung (Geld-Abfluss, Free-Service-Missbrauch). Hacker-Reports gehen häufig im fünfstelligen Dollar-Bereich.

Workflow-Diagramm als Bedrohungs-Analyse-Tool

Wer einen Workflow sichern will, sollte ihn als State-Machine-Diagramm aufmalen — alle erlaubten Übergänge, alle nicht erlaubten Übergänge. Anschließend für jeden Übergang fragen: „Was, wenn ein:e Angreifer:in versucht, diesen Übergang aus einem anderen State zu erzwingen?" — die Antworten sind die Test-Cases.

Gmail-Punkte-Trick als E-Mail-Normalisierung

Gmail behandelt alice@gmail.com, a.lice@gmail.com und al.ice+test@gmail.com alle als dieselbe Adresse — Zeichen werden ignoriert. Wer User-Anzahl per E-Mail-Adresse limitiert, sollte die Adresse normalisieren (lowercase, Punkte entfernen, Plus-Tags ignorieren), bevor man sie als Identifier verwendet.

Single-Packet-Attack von James Kettle

Eine Forschung von 2023: durch HTTP/2-Multiplexing lassen sich mehrere HTTP-Anfragen in einem einzigen TCP-Paket bündeln. Damit kommen sie nahezu gleichzeitig beim Server an — Race-Condition-Fenster sind dadurch von Millisekunden auf Mikrosekunden geschrumpft. Manche Schutz-Mechanismen, die auf „kommt-bestimmt-nicht-gleichzeitig" setzten, sind seitdem brüchig.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu OWASP Top 10

Zur Übersicht