Earthshaker Security Book a scan

← All field notes

checkout 14 September 2026 · 6 min read

Checkout logic flaws scanners miss

The vulnerability classes hiding in a custom checkout that no automated scanner will flag: price tampering, coupon races, and forged payment webhooks.

A scanner is very good at telling you that a form reflects unescaped input. It is close to useless at telling you that your checkout will happily sell a $400 item for $4.

The difference is that the first is a flaw in how the code handles a string, and the second is a flaw in what the application believes. A scanner has no model of what your application is supposed to do, so it cannot notice when the application does something else. That gap is where most of the serious findings in a custom checkout live.

What follows is the set of classes worth testing on any checkout that was written rather than bought. None of them require exotic tooling. All of them require someone who understands what the flow is for.

Trusting a price that came from the client

The oldest one, and still present, usually in a checkout that grew an API halfway through its life.

The pattern: the cart page renders the price, the client posts the cart back at checkout time, and the server writes an order using the price in the request. Sometimes the price is posted openly. More often it is inside a serialised cart blob, or a base64 field, or a JSON body that nobody expects anyone to look at.

Testing it is a matter of changing the number and seeing whether the order total follows. The variants that get missed:

The fix is always the same and always the same amount of work: the server looks up the price. The client sends identifiers and quantities, never amounts.

Quantities that were never bounded

Quantity fields get validated for "is it a number" far more often than they get validated for "is it a number that makes sense."

Worth trying on every cart:

Coupons: stacking, reuse, and the race

Discount logic accumulates special cases faster than any other part of a checkout, and special cases are where the contradictions hide.

Stacking. Two codes that are each valid but were never meant to combine. The check is usually "is this code valid," not "is this code valid given the other code already applied."

Reuse against the rules. A single-use code that is marked used at order completion rather than at reservation. A per-customer limit enforced on account ID while the checkout permits guest orders. A code scoped to one product that is applied at cart level.

The race. This is the one that survives a code review, because reading the code it looks correct — check the counter, apply, increment. It fails because those three steps are not atomic. Fire the redemption twenty times concurrently and a single-use code redeems five, nine, twenty times. The window is small, but it does not need to be large; it needs to be reachable, and an attacker gets to choose the concurrency.

Testing for it means issuing genuinely parallel requests, not twenty sequential ones in a loop. A sequential loop passes cleanly against code that is wide open.

Skipping a step in the state machine

A checkout is a state machine, and the flow is usually enforced by the fact that each page links to the next one. That is not enforcement, it is habit.

The question to ask at every step: what happens if I post to this endpoint without having completed the previous one?

The fix is that each transition validates the state it is transitioning from, server-side, and that order objects become immutable once paid.

Payment webhooks that believe what they are told

This one is worth its own section because it is common, it is high impact, and it sits outside the part of the application most people think of as the checkout.

The payment provider calls your webhook to confirm a charge. Your handler marks the order paid. Two questions decide whether that handler is safe:

  1. Is the signature verified? Every major provider signs webhooks. Not every integration checks. An unverified handler is an unauthenticated endpoint that marks arbitrary orders paid, and it is often absent from the scope document because nobody thinks of it as part of the site.
  2. Is the amount checked against the order? A verified webhook proves the provider sent it. It does not prove the amount matches what the order was supposed to cost. Where an attacker can influence the charge amount — a provider-side flow with a client-supplied value — a genuine, correctly signed webhook for $1 marks a $400 order paid.

Replay is the third question. A webhook captured and re-sent should be rejected on its event ID, not processed twice.

Authorisation between customers

Not strictly checkout logic, but it is found in the same place and by the same method: does object access check ownership?

Order confirmation pages, invoice PDFs, order-status APIs and re-order endpoints tend to be built for the happy path where the ID in the URL belongs to the person in the session. Incrementing an order ID is the first test. The second is doing it against the API rather than the HTML page, because the two frequently have different authorisation code and only one of them was reviewed.

Where this gets expensive is invoices: name, address, email, items, and sometimes the last four digits of a card, enumerable by integer.

Why this needs a person

Every class above is defined by a mismatch between what the code does and what the business meant. No tool has access to the second half of that sentence. A scanner can tell you an endpoint accepts a parameter; only a person can tell you that the parameter should never have been accepted from that side of the connection.

That is the whole reason our testing is structured the way it is. Automation runs first, because it is fast and it clears the ground. Then a person works through the flow asking what the application believes and whether it should. Everything that reaches the report was reproduced by hand, with the request that triggered it and the fix attached — and anything that could not be reproduced does not appear in the report at all.

If you have a checkout that was written rather than bought, these are the questions worth asking of it, whether or not you ask us.

Next
Want this run against your app?
Book a scan