In 2015, a Shopify merchant discovered a flaw in the coupon flow. Applying a coupon, then editing cart quantities post-coupon, produced a checkout price that was neither the discounted total nor the correct pre-coupon amount. The coupon calculation ran once, against the original quantity. Shopify's backend recalculated line items on every quantity update but didn't re-evaluate the coupon binding. A $100 cart became purchasable for under $15.
Burp Suite produced zero findings against that endpoint. No payload, no signature, no anomaly. The request was structurally valid, the session was authenticated, and the coupon was real. The vulnerability was the checkout workflow itself, executed in a sequence the developers hadn't modeled.
What Separates Logic Bugs from Technical Vulnerabilities
Technical vulnerabilities have signatures. SQL injection sends a quote; XSS sends a script tag; a buffer overflow sends too many bytes. Security scanners work by matching input patterns to known vulnerability classes, then verifying server responses deviate from expected behavior. That model breaks entirely against business logic bugs.
A business logic vulnerability is a defect in the application's implementation of its own intended workflow. The input is valid. The session is authenticated. The server is doing exactly what its code says. The problem is that the code models an incorrect assumption about what sequences of operations are possible. No scanner can test for assumptions it doesn't know exist.
Bug bounty programs have known this for years. HackerOne's most valuable disclosed reports are consistently in this category. The payout gap is real: a $5,000 SQL injection vs. a $25,000 price manipulation report. Logic bugs are harder to find, require domain understanding, and often have direct financial impact.
Common Patterns
Price manipulation through workflow reordering. Cart and checkout flows commonly calculate discounts, taxes, and totals at specific points in a multi-step process. When developers don't invalidate or recalculate those values on every subsequent mutation, the numbers drift. The Shopify coupon case fits here. HackerOne saw nearly identical reports against multiple e-commerce platforms between 2019 and 2022. The sequence: add item at price A, apply coupon, swap to price B item. The discount persists as if calculated against A.
Privilege escalation through workflow bypass. Approval workflows, role gates, and access controls are frequently enforced at specific API endpoints rather than as invariants on state transitions. A user who knows the endpoint sequence can skip steps. In several SaaS platforms, document approval flowed through a three-step process with separate API calls for each step. Calling step 3's endpoint directly, without completing steps 1 and 2, produced a fully approved document. The final endpoint checked only whether the user held write access, not whether prior steps had completed.
Excessive trust in client-side controls. Any validation that runs only in the browser is not a control. Price fields sent from the client, discount codes validated in JavaScript, access tier checks in frontend conditionals: none of these are real controls. Intercepting the request with Burp Suite and modifying the value before it reaches the server bypasses all of them. This class appears repeatedly in fintech applications. Account type or subscription tier is checked client-side before enabling premium features, and the backend accepts whatever tier value the client claims.
State machine violations. Every application has an implicit or explicit state machine. Orders can be pending, confirmed, shipped, delivered, refunded. The expected transitions are forward-only. When backend endpoints don't enforce transition validity, attackers can drive the application into states the developers never tested. A 2021 HackerOne report against a travel booking platform found that a refund endpoint would process a refund for a booking already in refunded state. The endpoint checked whether the booking existed, not whether it had already been refunded. The platform paid out twice on the same booking.
GitHub OAuth scope creep. GitHub disclosed in 2018 that OAuth application authorization could be manipulated to obtain tokens with broader scope than the user granted. The flow involved multiple redirects and token exchanges. By manipulating the state parameter and interleaving authorization requests across two sessions, a researcher obtained a private-repository-scoped token. The victim had authorized only public repository access. The vulnerability was not in OAuth's cryptography. It was in GitHub's session management across parallel authorization flows: a state machine violation at the protocol level.
The Race Condition Subclass
Race conditions in business logic occupy a specific niche that deserves separate treatment. These bugs arise from non-atomic multi-step flows. The application checks a condition, then acts on it in separate database calls, without holding a lock between the two operations.
The pattern is: read state, verify precondition, write updated state. When two requests execute this sequence concurrently, both can pass the precondition check before either write completes. Classic TOCTOU (time-of-check to time-of-use) applied to application-layer logic.
A fintech case from 2020: a peer-to-peer payment platform checked available balance before processing a transfer. The check and the deduction were two separate database operations. By sending concurrent transfer requests within the same 50-millisecond window, researchers triggered both balance checks before either deduction was committed. A $100 account balance funded two $100 transfers. Total deducted: $200 from a $100 balance.
Reproducing this class of bug is straightforward. Burp Suite's Turbo Intruder or a Python script using asyncio can send dozens of requests within milliseconds. Any fintech, gaming economy, or subscription system that handles balance or quota operations without atomic transactions is potentially vulnerable. The specific operations to probe: transfers, coupon redemptions, referral credit claims, subscription activations, and free trial conversions.
The code pattern that creates the risk:
# Vulnerable: check then act, non-atomic
balance = db.get_balance(user_id)
if balance >= amount:
db.deduct_balance(user_id, amount)
db.credit_recipient(recipient_id, amount)
Both concurrent requests read balance = 100 before either write runs. Both pass the >= amount check. Both execute the deduction.
Detection Methodology
Scanners can't map a state machine they don't know exists. Manual mapping is the only approach that works.
Start by reading API documentation or intercepting traffic to understand the intended workflow. Draw the state machine explicitly: what states can an entity occupy, and which transitions are valid from each? For an order system, that might be six states and eight valid transitions. The attack surface is every invalid transition.
For each invalid transition, ask: is there an API endpoint that could trigger this directly? Can this transition be caused by reordering valid requests? Can it be triggered concurrently? What happens if the flow is interrupted at each intermediate step, then resumed?
Price manipulation testing follows the same model. Map every point in the checkout flow where a monetary value is calculated. Test whether that value is recalculated or simply trusted at every subsequent step. The specific sequence: modify a quantity, change an item, apply a second coupon, switch currency after the total is displayed. Any value that survives without server-side recalculation is a candidate.
For race conditions, identify any operation that reads a constraint and then modifies a resource in separate steps. The minimum viable test: two concurrent requests to the same endpoint, sent with Turbo Intruder, targeting transfers, coupon redemptions, and subscription activations. A response that differs from a sequential execution confirms the race.
Defense
The defensive posture against logic bugs is fundamentally different from patching a technical vulnerability. There is no WAF rule to write, no input sanitization to add.
Server-side state validation on every transition. Every API endpoint that advances a workflow must verify that the current state permits that transition, not just that the user holds the correct role. An order cannot move to shipped if it is in refunded state. The server enforces this; the client has no opinion that matters.
Atomic transactions for any check-then-act pattern. Any operation that reads a balance, quota, or constraint and then modifies it must execute within a single database transaction with appropriate isolation. In PostgreSQL, SERIALIZABLE or SELECT FOR UPDATE on the balance row prevents concurrent reads from both observing the pre-deduction state:
BEGIN;
SELECT balance FROM accounts WHERE id = $1 FOR UPDATE;
-- balance check happens inside the lock
UPDATE accounts SET balance = balance - $2 WHERE id = $1;
COMMIT;
Idempotency keys for payment and credit operations. Any endpoint that grants money, credits, or access should require a client-supplied idempotency key and reject duplicate keys within a defined window. Stripe has required this since 2014. Duplicate requests with the same key return the same response without repeating the operation, closing the retry-based race condition entirely.
Recalculate, never trust. Any value calculated at a prior step in a workflow must be recalculated on the server at the final step. Cart totals, discount amounts, tax calculations: the server recalculates everything at checkout using current server-side state. Client-sent totals are discarded.
The applications that fail these tests share a common assumption: that users will follow the path the developers envisioned. They won't. The checkout flow is a suggestion. A scanner will confirm the SSL certificate is valid and move on. The person who reads the state machine and asks what happens if you skip step two will find something worth $25,000.
Top comments (0)