July 4, 2026
How a Race Condition in an Order Checkout Led to a $4,000 Triple-Coupon Glitch
We often look at security bugs through the lens of broken code syntax or memory leaks. But some of the most expensive vulnerabilities in…

By Tanvi Chauhan
3 min read
We often look at security bugs through the lens of broken code syntax or memory leaks. But some of the most expensive vulnerabilities in e-commerce platforms stem from the invisible gap between checks and state changes.
When a database performs tasks sequentially, everything behaves perfectly. But when multiple identical database write requests hit the server processor at the exact same millisecond, standard application logic can completely fracture. This is a classic Race Condition, specifically a Time-of-Check to Time-of-Use (TOCTOU) vulnerability.
This is the story of how I targeted a global luxury fashion retailer — let's call them ModaStyle — and applied a single, one-time-use $100 promotional coupon multiple times to a single order, resulting in a $14,000 bug bounty reward.
The Target: The Promo Code Engine
ModaStyle had an active, high-paying public bug bounty program. Their checkout workflow was well-audited and implemented strict server-side validation.
I set up a test account, added an expensive designer jacket to my shopping cart, and looked at how the application processed coupon redemptions. When you apply a promotional code at checkout, the backend server executes three core operations in rapid succession:
- Verify: Check the database to see if the coupon code is valid and hasn't been used yet.
- Apply: Deduct the coupon's discount value (e.g., $100) from the order total.
- Burn: Mark the coupon code as
status = 'used'in the database so it cannot be claimed again.
Under normal browsing conditions, this logic handles validation seamlessly. If you try to open a second tab and apply the same coupon code to another order, the database hits Step 1, realizes the coupon is already marked as spent, and blocks the request with an error message: "This coupon has already been redeemed."
But what happens if you send five identical coupon requests to the server at the exact same millisecond?
The Footprint: Crafting the Concurrent Attack
To exploit a race condition over the public internet, traditional sequential multi-threading often fails because network jitter and latency naturally spread out the arrival times of your HTTP requests at the target server gateway.
To overcome this, I used HTTP/2 single-packet multiplexing via the Turbo Intruder extension in Burp Suite. This technique allows a researcher to queue up dozens of distinct HTTP requests and compress them into a single TCP network frame.
The API gateway receives this single packet and unpacks all the requests simultaneously, dumping them into the backend microservices at the exact same microsecond, rendering internet lag irrelevant.
I intercepted the coupon application request in Burp Suite:
HTTP
POST /api/v3/checkout/apply-coupon HTTP/1.1
Host: app.modastyle.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
{
"cart_id": "CART_992104",
"coupon_code": "WELCOME100"
}POST /api/v3/checkout/apply-coupon HTTP/1.1
Host: app.modastyle.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
{
"cart_id": "CART_992104",
"coupon_code": "WELCOME100"
}I sent this request to Turbo Intruder and configured a custom Python routing script to gate 30 identical requests, preparing them to fire synchronously.
The Twist: Short-Circuiting the Database Check
The script engine lined up the concurrent burst using a single-packet gate execution pattern:
Python
def queueRequests(target, wordlist):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=30,
pipeline=False)
# Gate all 30 requests to release them simultaneously
for i in range(30):
engine.queue(target.req, gate='race_gate')
engine.openGate('race_gate')def queueRequests(target, wordlist):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=30,
pipeline=False)
# Gate all 30 requests to release them simultaneously
for i in range(30):
engine.queue(target.req, gate='race_gate')
engine.openGate('race_gate')I launched the script against my test cart. Thirty requests containing the single-use code WELCOME100 blasted into ModaStyle's API endpoint.
When the execution completed, I analyzed the response table. Instead of receiving one single success response and twenty-nine 400 Bad Request validation errors, three distinct requests returned a 200 OK success status before the remaining twenty-seven requests caught up and failed with a "coupon already used" validation error.
The Exploit: Stacking the Discounts
Because the backend lacked proper row-level isolation or table locks, three independent server processing threads checked the coupon status at the exact same millisecond.
All three threads read Step 1 simultaneously, saw that WELCOME100 was still marked as active, and proceeded to apply the $100 deduction to my cart. By the time the first thread reached Step 3 and updated the status to used, the other two threads had already bypassed the verification gate.
I navigated back to my browser window and refreshed my cart summary:
- Item Total: $450.00
- Discount Applied (WELCOME100): -$100.00
- Discount Applied (WELCOME100): -$100.00
- Discount Applied (WELCOME100): -$100.00
- Final Balance Due: $150.00
The single-use coupon had stacked three times on a single order, letting me deduct $300 off the jacket instead of the intended $100 limit. If I had increased the concurrency volume or used a larger voucher value, I could have dropped the order total down to zero or triggered a negative balance error.
The Remediation and Payout
I immediately canceled the test transaction to ensure no financial harm was done, gathered the Turbo Intruder logs, and submitted a detailed proof-of-concept report detailing the concurrency flaw to ModaStyle's triage panel.
- Submission Date: Monday, 11:30 AM
- Triaged as P2 (Critical): Monday, 2:15 PM
- Patch Implemented globally: Tuesday, 10:00 AM
- Bounty Awarded: $4,000
ModaStyle resolved the vulnerability by implementing atomic database transactions with pessimistic locking. In their updated architecture, when a thread queries a one-time resource row for validation, it uses a strict SELECT ... FOR UPDATE statement. This command locks that specific row instantly, forcing any concurrent incoming queries to wait in line until the original transaction completely finishes verifying, applying, and burning the coupon.
Core Lessons
- For Developers: Never rely on application-level logic or non-atomic checks to handle business-critical state updates. When processing monetary transactions, inventory balances, or single-use promo keys, always implement row-level locking or distributed synchronization locks (like Redlock for Redis) to guarantee threads execute linearly.
- For Bug Hunters: Race conditions can be found anywhere numerical data or account states change. Test coupon inputs, financial transfers, point redemptions, and add-to-cart operations. Always use modern HTTP/2 single-packet multiplexing tools to ensure your concurrent requests hit the internal server gates at the exact same instant.