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.
// 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:
- User schickt 50 parallele Requests mit demselben Coupon-Code.
- Alle 50 lesen die DB:
coupon.used === false. - Alle 50 prüfen den Check — alle bestehen.
- Alle 50 führen den Update aus.
- Alle 50 erhöhen das User-Balance.
Statt einmal 10 EUR Gutschrift hat User jetzt 500 EUR.
Schutz-Patterns:
// 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.
// 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 UPDATEsperrt 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: falsenach Registrierung. Wenn sicherheitsrelevante Aktionen nicht alle aufverified: trueprü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.comsind 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: -5fü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 * pricewird negativ bei extrem hohen Werten. Bei JavaScript-Number ab2^53interessante 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: -5als „Rückbuchung". - Race auf Counter — zwei parallele
+1-Operationen führen zu+1statt+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 + 1stattcount = ?). - 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
- OWASP – Business Logic Vulnerability
- OWASP Authentication Cheat Sheet
- OWASP Web Security Testing Guide – Business Logic Testing
- James Kettle – Smashing the State Machine (Single-Packet-Attack, 2023)
- PortSwigger Web Security Academy – Race Conditions
- Stripe API – Idempotency Keys
- Hypothesis – Property-Based Testing für Python
- fast-check – Property-Based Testing für JavaScript
- HackerOne Hacktivity – Public Reports