Web — Two Steps Forward, Zero Back
Category: Web · Difficulty: Hard
Scenario
shop.kashsec-labs.test's checkout is a three-step wizard: /checkout/cart → /checkout/payment → /checkout/confirm. You captured the three requests a normal purchase makes, in order.
Provided data
POST /api/checkout/cart {"session_id": "sess_88f2"} → {"step": "payment", "cart_total": 189.00}
POST /api/checkout/payment {"session_id": "sess_88f2", "card_token": "tok_live_..."} → {"step": "confirm", "payment_verified": true}
POST /api/checkout/confirm {"session_id": "sess_88f2"} → {"order_id": 4471, "status": "shipped"}
Each step is a completely independent API call, tracked only by the shared session_id — there's no single server-side call chaining the three steps together as one atomic transaction.
Your task
The /checkout/confirm endpoint's job is to finalize an order — but does it actually verify that payment was confirmed for this specific session before doing so? Find out, and locate the flag.
Hint 1
Try starting a brand-new session at/checkout/cart to get a fresh, valid session_id — then call /checkout/confirm directly with that new session ID, skipping the /checkout/payment step entirely.Hint 2
The three steps only share state throughsession_id, and nothing in the scenario confirms that /checkout/confirm actually checks a payment_verified flag tied to that session before finalizing the order. Multi-step flows are only as safe as the weakest individual step's own enforcement — a later step assuming an earlier one "must have" run is not the same as it actually checking.Show full answer
CallingPOST /api/checkout/confirm with a fresh session_id that never went through /checkout/payment at all still succeeds: {"order_id": 4472, "status": "shipped", "payment_verified": false, "flag": "KASHSEC{sk1pp3d_p4ym3nt_st1ll_g0t_sh1pp3d}"}. The /checkout/confirm endpoint never independently verifies that a valid, completed payment actually exists for this session — it assumes, incorrectly, that reaching this endpoint at all implies the earlier steps happened in order. Because each step is a fully independent API call rather than a single server-enforced state machine, nothing stops a client from calling the endpoints in whatever order — or subset — they like.The flag
KASHSEC{sk1pp3d_p4ym3nt_st1ll_g0t_sh1pp3d}
Why this matters beyond the CTF
Multi-step flows — checkout wizards, onboarding sequences, password-reset flows, KYC/identity verification — are consistently one of the richest sources of business logic bugs in real applications, precisely because developers build the UI to enforce step order (you can't click "Confirm" until you've clicked "Next" on the payment screen) while forgetting that the API itself must independently enforce the same order, since nothing stops a client from calling any endpoint directly. Path 15's bug bounty material on business logic testing specifically calls out "can later steps be reached without earlier ones?" as a standard test to run against any multi-step flow you encounter in scope.
Bonus round — the same bug, worse consequences
A near-identical multi-step flow exists on this site's account-verification process: /verify/send-code → /verify/check-code → /verify/mark-verified.
Hint
Apply the exact same test — can you reach the final step directly, without ever actually proving you received and entered the correct verification code?Show answer
If/verify/mark-verified doesn't independently check that /verify/check-code actually succeeded for this session, an attacker could mark any account "verified" without ever proving control of the email or phone number the verification was meant to confirm — a significantly more severe consequence than the checkout example, since it undermines an identity/ownership guarantee other parts of the system likely rely on (like account recovery, or elevated trust for verified accounts). This is exactly why business logic testing shouldn't stop at "I found one instance of this bug" — the same missing-check pattern is worth testing against every multi-step flow in scope, since the severity can vary enormously depending on what the skipped step was actually supposed to guarantee.For any flow with sequential steps, ask: "if I call the last step directly, with data that looks plausible, does the server independently verify every earlier step actually happened — or does it just trust that reaching this endpoint implies the rest of the journey occurred?" This single question finds a disproportionate number of real business logic bugs relative to how simple it is to ask.
Next up → AI — Leak the System Prompt