All articles
When a Valid Request Violates the Business Rules
26 September 2026Security, Web Applications, Penetration Testing

When a Valid Request Violates the Business Rules

An anonymized commerce assessment examined through payment binding, entitlement consistency and server-side authorization.

I was asked to help assess a developing e-commerce platform that also sold access to a monthly newsletter. Using Burp Suite, I identified problems involving prices, subscription access and administrative permissions. In each case, information controlled by the browser could influence a decision that the server needed to enforce.

The organization remains unnamed. My account records the finding categories and the work I did with the person responsible for the platform, but it does not include redacted requests, patch diffs or a complete retest record. The examples below explain the engineering problem without reconstructing private endpoints or claiming a particular implementation.

“Validate on the server” is a useful starting rule. The more demanding question is what the server must establish before changing its state. A price can be calculated on the server and still be attached to the wrong order. A payment notification can be authentic and still concern a different transaction. A role claim can be intact and still be obsolete.

The interface describes an intended path

Burp let me examine the requests behind the interface. Repeater supports modifying and resending messages, including sequences in a multi-step process. That makes it possible to separate what the interface encourages from what the backend accepts.

A disabled button is one proposed interaction path. It does not establish that an independently submitted request will be denied. The important observation is the server's decision and resulting state: whether a purchase acquired altered terms, a protected resource became available or a privileged operation was permitted.

The common failure was accepting a client assertion where the operation required trusted evidence.

The price belongs to an accepted order

The pricing issue concerned the authority given to client-supplied transaction data. OWASP's payment-testing guidance distinguishes a purchase request containing an item and quantity from one that supplies an authoritative price. It also examines the handoff to a payment gateway, where the same trust problem can reappear.

The server needs to interpret a request as intent. It looks up the relevant product, validates the requested quantity and applies the actual discount, shipping and tax rules. It then records the terms the customer accepts.

For an illustrative two-decimal euro price, two units at €12.50 produce a subtotal of 2,500 cents before any separately defined adjustment. The example's currency matters as much as the integer. A number without its denomination is not a complete amount. Stripe's currency documentation, used here only as a concrete API reference, illustrates minor-unit amounts and currencies with different decimal conventions.

That calculation is still only part of a checkout. The resulting amount needs to remain bound to a server-generated order identifier, the authenticated account, the selected items and the corresponding payment operation. A later browser field must not silently substitute different terms.

Catalog changes expose the distinction. Suppose a product's price changes after the customer accepts a quote. The system needs a policy: honor the recorded terms, or expire the quote and obtain acceptance of revised terms. Comparing a payment against an unrelated current price is not a substitute for that policy.

This suggests a useful completion invariant for a narrow, one-time purchase:

mark this order paid only when trusted payment evidence
matches this merchant, order, amount and currency,
satisfies the required payment state,
and has not already fulfilled a different purchase

The exact fields depend on the integration. The obligation does not: the evidence must authorize this transition for this order.

Authentic evidence can still be attached to the wrong transaction

A signed message establishes something about its origin and integrity under the relevant verification scheme. It does not automatically establish every relationship the application needs.

For example, an authentic payment record for one merchant cannot settle an order belonging to another. A valid receipt for one purchase cannot be reused as proof that a second purchase was paid. Checking only that a payment identifier exists leaves those bindings unexamined.

Wang and colleagues studied this class of problem in How to Shop for Free Online, published at IEEE Security and Privacy in 2011. They traced API arguments and state across merchant, cashier and client interactions. Their payment-completion invariant connected the merchant, item, payment recipient, amount and purchase. The exchanges could use HTTPS while the application-level relationships remained wrong.

Their evidence had limits: selected historical systems, unavailable cashier internals and a bounded model of a subset of merchant logic. The model's implemented invariant also omitted product ownership, excluding some attack classes. Its success at finding modeled flaws was therefore not a proof that all checkout logic was correct. That limitation is useful here: a precise invariant can expose an error while still leaving another obligation outside its scope.

A return page is another easily confused piece of evidence. Reaching a URL labeled “success” is a browser event, not independent payment confirmation. Conversely, a customer can pay and lose connectivity before reaching it.

Stripe's fulfillment guide makes this distinction operational: a server retrieves and evaluates the payment session, and fulfillment must tolerate repeated, concurrent calls. A landing page can trigger that verification; it should not replace it. Delayed payment methods also require distinguishing checkout completion from later payment success. Stripe is a reference design in this discussion, not an identification of the private platform's provider.

A subscription is a changing entitlement

The newsletter issue made the consequences visible beyond checkout. Having an account did not by itself establish access to paid functionality.

An entitlement is a policy decision about a subject, a resource and a period of validity. It may depend on a verified billing state, an allowed trial, an explicit grace period or a cancellation policy. Access without a newly completed payment is not inherently a vulnerability if the business deliberately permits it. The defect is granting access outside the intended policy.

Provider state and local access state also change asynchronously. As one concrete reference, Stripe's subscription guidance instructs integrations to retrieve the associated subscription after an invoice is paid and confirm the relevant status before extending access. It notes that an active subscription does not necessarily mean every outstanding invoice has been paid. A generic “paid” flag erases distinctions that the policy may need.

Retries create a second problem. If receiving a renewal notification means “add one month from now,” processing the same event twice can extend access twice. Deriving the covered period from verified billing evidence avoids treating delivery time as a new purchase. Calendar periods should follow the billing policy, not an assumed thirty-day month.

Authenticity alone does not solve this. Stripe documents duplicate and out-of-order webhook delivery, as well as signature verification over the raw body and an authenticated timestamp. Those checks serve different purposes. A valid signature does not make an event unique, current or correctly associated with a local account.

Consider a proposed system that applies subscription snapshots in arrival order:

cancellation arrives       -> local access denied
older active event arrives -> local access restored

The messages can have different identifiers and both be authentic. Deduplicating one identifier cannot prevent the stale overwrite.

Fetching current state still requires coordination

One response is to treat an event as a trigger to retrieve current provider state. That is useful, but concurrency can undermine an otherwise sensible design:

Worker A reads active, worker B reads canceled and stores deny, then worker A stores its older allow resultLocking only the final write is too late: both workers already hold incompatible snapshots. Diagram: Daniel Buttigieg

These are illustrative executions, not failures observed in the original assessment.

For a proposed one-subscription entitlement, a single local owner could serialize the entire fetch-and-apply operation. Locking only the final write would be too late: both workers could already hold incompatible snapshots. With multiple workers or replicas, the ownership mechanism must prevent an expired or superseded worker from applying its old result, for example through a fenced write or equivalent coordination.

This closes a local stale-result race under the stated assumptions. It does not make the provider and application one atomic database. A cancellation can occur after the fetch. Later notifications and periodic reconciliation are needed to repair the local projection, and the product must define its acceptable delay before revocation becomes effective.

Availability policy also needs to be explicit. A provider outage should not manufacture evidence for a new grant. Continuing an existing, verified access period or allowing a documented grace period is a separate decision. Returning “allow” whenever verification fails would turn a dependency failure into an authorization policy.

Freshness and crash recovery are separate obligations of the same entitlement transition. The delivery path also needs durable state. In a proposed design, verified notifications enter a durable inbox before acknowledgment. Applying a local entitlement transition and recording that it was processed belong in the same atomic operation. Marking an event processed first, then crashing before the entitlement change, would lose the work.

That local transaction still does not make an external action happen exactly once. Sending a message or invoking another service needs its own durable delivery and idempotency design. Provider API idempotency keys likewise have documented scope and retention; they do not replace the application's own record of which business operation has been completed.

Cookies carry credentials, not unlimited permission

The administrative-permission finding involved client-controlled information associated with cookies. The important result was access to functionality a normal user should not have had, rather than the visibility of an administrator button.

It would be misleading to conclude that cookies are inherently unsafe. An opaque session identifier can refer to trusted server-side session and permission state. OWASP's session guidance describes this separation between the identifier carried by the client and its server-side meaning.

Authenticated client-carried claims are another possible architecture. A signed token can protect a role claim against modification when verification is correct. The JWT security best current practice requires appropriate algorithm, cryptographic, issuer, subject and audience validation. Merely decoding a token is not that verification. Nothing in my account establishes that this platform used JWTs.

Even verified integrity does not establish indefinite permission. A token issued while someone was an administrator may still contain that role after demotion. Expiration, revocation, a permission version or current server-side lookup must define when the changed authority takes effect. Signing the old claim more securely does not make it current.

The operation also matters. A valid role does not establish access to every object. OWASP's authorization guidance calls for resource-specific checks on every request. A sensitive decision needs the authenticated subject, requested action, target resource and applicable trusted policy. A client-supplied account identifier or role label cannot supply those permissions by assertion.

For strict revocation requirements, checking a permission when a job is submitted may be insufficient if the job executes later. Transaction-authorization guidance places a final authorization gate at execution. The system must define the boundary: which new operations are denied after revocation, which in-flight operations recheck, and which already committed effects cannot be undone. Session renewal alone is not a rollback mechanism.

A repair needs both permission and denial evidence

I worked with the person responsible for the platform on the findings before it became publicly available. That collaboration is part of the original account. A claim that every path was fixed would require a preserved patch and retest record that this article does not have.

A useful future retest would pair each prohibited transition with a valid baseline. The ordinary purchase should still succeed while a changed browser price cannot alter its accepted terms. A verified payment should grant the intended access once, while duplicate or delayed messages cannot create another grant or revive revoked access outside policy. An authorized administrator should complete the permitted operation, while an ordinary user's direct request leaves protected state unchanged.

The state check is essential. An error page is not proof that nothing changed; a successful response is not proof that the correct account or order changed. Positive controls also prevent a broken login or unavailable service from masquerading as successful access control.

The server needs to preserve the relationship between evidence, identity, object and state transition. That relationship is what makes a purchase, an entitlement or an administrative action legitimate, even when every individual request is syntactically valid.


Image credits