October 27, 2026
That βCoupon Already Appliedβ Error Was Lying To Me β Intigriti Monthly Challenge Write-Up
Hey everyone π, myself Nitin Bhanderi β a bug bounty hunter. Today, Iβm sharing how I turned a β¬666 price tag into a β¬0.00 receipt duringβ¦

By CYP#3R
9 min read
Hey everyone π, myself Nitin Bhanderi β a bug bounty hunter. Today, I'm sharing how I turned a β¬666 price tag into a β¬0.00 receipt during the Intigriti October 2026 monthly challenge.
Most write-ups I read focus on the exploit β which is great β but they often skip the part where the author was confused, second-guessing themselves, and staring at a 409 response for way too long. That's the part I want to capture here, because that's where the actual learning happened for me.
The short version: I bought a β¬666.00 item for β¬0.00 by exploiting a bug that lives inside a 409 error message. That sounds like a magic trick, but it's actually a pretty clean business logic flaw β and I want to break it down in the way I wish someone had broken it down for me.
Let's go.
The challenge
The shop has one target item:
- The Midnight Collection β β¬666.00 (badged "Only one available")
- Store credit β β¬66.00
The price is exactly 10Γ the credit. So I need 90%+ total discount to afford it. The intended path is clearly coupon stacking.
Before the exploit β finding the coupons
The shop has one target item: The Midnight Collection β β¬666.00, badged "Only one available." Meanwhile a fresh visitor gets β¬66.00 in store credit. The price is exactly 10Γ the credit β a clear signal that the intended path is discount stacking.
Two coupons exist, and both are public.
NEWS30 (30% off) β scroll to the footer, enter any email in the October newsletter form, hit Subscribe. The response returns {"ok":true,"code":"NEWS30"}.
NIGHT20 (20% off) β printed on the storefront next to the Midnight Collection card. Not hidden at all.
I added the collection to the cart and applied both:
Subtotal: β¬666.00
Discount: β¬333.00 (30% + 20%)
Total: β¬333.00
Credit: β¬66.00Subtotal: β¬666.00
Discount: β¬333.00 (30% + 20%)
Total: β¬333.00
Credit: β¬66.00β¬333.00. Still five times over my credit. So one round of coupons wasn't going to cut it.
Before finding the real bug, I tried the usual things β and none of them worked:
- Case variants β
night20,Night20,NiGhT20β all409 coupon_already_applied. The dedupe check ignores case. - Trailing whitespace β
NIGHT20,NIGHT20, tabs, newlines β all409. The server trims before comparing. - Unicode / fullwidth characters β all
400 not recognised. - Duplicate JSON keys or list values β parser errors or
503. - Mass-assignment on checkout β
{"total":0},{"credit":1000000}β all402 insufficient_credit. The checkout endpoint ignores the body. - 30 concurrent coupon requests β 1 success, 29 rejections. The server serialises coupon mutations correctly.
Every standard trick failed. Which meant the bug had to be something the server was doing on its own β a state-machine flaw, not an input-validation flaw.
The clue came from the coupon response itself. Every coupon in the cart looks like this:
{"code":"NEWS30","percent":30,"amount":19980,"count":1}{"code":"NEWS30","percent":30,"amount":19980,"count":1}That count: 1 field bothered me. Why would a count exist if a coupon could only ever be applied once? I opened the frontend code (/static/js/app.js) and found this line in the cart renderer:
${promotion.percent}% off${promotion.count > 1 ? ` Γ ${promotion.count}` : ""}${promotion.percent}% off${promotion.count > 1 ? ` Γ ${promotion.count}` : ""}The UI has a dedicated branch for rendering Γ 2, Γ 3 next to a coupon. That means multi-count promotions are a supported server state. The app was built expecting a coupon to be applied more than once. The question wasn't "can I reapply a coupon?" β it was "what makes the server increase the count?"
That reframing is what led me to the bug. Below is the exact walkthrough I performed.
Steps to reproduce
Step 1 β Visit the store
Navigated to https://challenge-1026.challenges.intigriti.io/. The header told me the whole story: β¬66.00 credit, empty cart.
Step 2 β Locate The Midnight Collection and add it to the bag
Scrolled down to the product grid. The Midnight Collection β β¬666.00, badged "Only one available." Exactly 10Γ my credit.Clicked Add to bag.
Step 3 β Observe price, credit, and still-required credit β then grab the newsletter coupon
Opened /cart. Single line item for β¬666.00, subtotal β¬666.00, total β¬666.00, available credit β¬66.00. The Complete order button was disabled with the label "Still needed: β¬600.00." So I need at least a 90% total discount to flip the button.
Then scrolled down to the footer, where the newsletter signup form labelled "October newsletter" lives β the only legitimate path in the app that hands out a coupon grant. Typed a throwaway email (attacker@gmail.com) and clicked Subscribe.
Step 4 β Intercept the request and send it to Repeater
Turned Burp Intercept on, submitted the form, and sent the captured request to Repeater:
POST /api/newsletter
{"email":"attacker@gmail.com"}POST /api/newsletter
{"email":"attacker@gmail.com"}
Step 5 β Send the request and observe the response
Forwarded it. Response 200 OK:
{"ok":true,"code":"NEWS30","message":"Your first-order code is ready."}{"ok":true,"code":"NEWS30","message":"Your first-order code is ready."}
The server handed over NEWS30 β 30% off. First coupon acquired.
Step 6 β Navigate back to bag and apply the coupon
Back at /cart, typed NEWS30 into the "Promotion code" input and clicked Apply.
Step 7 β Intercept the request and send it to Repeater
With Intercept on, captured the click and sent it to Repeater:
POST /api/cart/coupon
{"code":"NEWS30"}POST /api/cart/coupon
{"code":"NEWS30"}
Step 8 β Send the request and observe the response
Forwarded it. Response 200 OK:
{"ok":true,"code":"NEWS30","message":"Your first-order code is ready."}{"ok":true,"code":"NEWS30","message":"Your first-order code is ready."}
The server handed over NEWS30 β 30% off. First coupon acquired.
Step 9 β Navigate back to bag, observe the new total
Refreshed /cart β discount row "Promotion NEWS30 Β· 30% off ββ¬199.80", total β¬466.20. Still over seven times my credit.
Step 10 β Apply the NIGHT20 coupon in Repeater
The second coupon, NIGHT20 (20% off), is printed on the storefront right next to the Midnight Collection card. Not hidden β this is meant to be found. Sent it to the same endpoint:
POST /api/cart/coupon
{"code":"NIGHT20"}POST /api/cart/coupon
{"code":"NIGHT20"}Send and observe the response
Forwarded it. Response 200 OK:
"promotions":[
{"code":"NEWS30", "percent":30,"amount":19980,"count":1},
{"code":"NIGHT20","percent":20,"amount":13320,"count":1}
],
"total": 33300"promotions":[
{"code":"NEWS30", "percent":30,"amount":19980,"count":1},
{"code":"NIGHT20","percent":20,"amount":13320,"count":1}
],
"total": 33300
Both coupons applied, each at count: 1. The stacking works normally.
Step 11 β Navigate back to bag, observe the new total
Refreshed /cart. Two rows now:
- "Promotion NEWS30 Β· 30% off ββ¬199.80"
- "Promotion NIGHT20 Β· 20% off ββ¬133.20"
Total β¬333.00. Button still says "Still needed: β¬267.00."
Half price, but half price isn't enough. **So the obvious question becomes: **can I apply NIGHT20 twice?
Step 12 β Re-send NIGHT20 and observe the 409
Back in Repeater, re-sent the exact same request {"code":"NIGHT20"}. Response 409 Conflict:
{"ok":false,"error":"coupon_already_applied","message":"Coupon already applied."}{"ok":false,"error":"coupon_already_applied","message":"Coupon already applied."}
Looks like a clean rejection. And this is where I almost stopped β but I kept the tab open, because a 409 in a coupon handler should never touch state. Something in my gut said to try one more thing.
Step 13 β Immediately re-apply NEWS30, observe the count
Without closing the tab or sending anything else in between, I changed the body to the other already-applied coupon β {"code":"NEWS30"} β and hit Send.
Response was 200 OK. Not 409, not coupon_already_applied β 200 OK, even though NEWS30 was already on the cart.
The response body:
"promotions":[
{"code":"NEWS30", "percent":30,"amount":39960,"count":2},
{"code":"NIGHT20","percent":20,"amount":13320,"count":1}
],
"total": 13320"promotions":[
{"code":"NEWS30", "percent":30,"amount":39960,"count":2},
{"code":"NIGHT20","percent":20,"amount":13320,"count":1}
],
"total": 13320
NEWS30's count had gone from 1 β 2. Its discount had doubled from β¬199.80 to β¬399.60. No new newsletter subscription. No new grant.
That was the bug. The 409 on NIGHT20 had cleared an internal guard on the server, and NEWS30 re-entered the apply path with that guard gone.
I said "wait, what?" .
Step 14 β Navigate back to bag, observe the new total
Refreshed /cart. Now showing:
- "Promotion NEWS30 Β· 30% off Γ 2 ββ¬399.60"
- "Promotion NIGHT20 Β· 20% off ββ¬133.20"
Total β¬133.20. That Γ 2 badge? It's exactly the count > 1 branch from app.js β a UI state the app was built to support.
Step 15 β Repeat the pattern to push the count higher
Back in Repeater, re-sent {"code":"NIGHT20"} β this time it returned 200 OK (not 409), because the intervening NEWS30 200 left the guard in a state where NIGHT20 could be reapplied too. Response showed NIGHT20 at count: 2, amount: 26640.
Sent NEWS30 once more right after and got 200 OK with NEWS30 at count: 2, amount: 39960. Cart state:
"discount": 66600,
"total": 0,
"canCheckout": true"discount": 66600,
"total": 0,
"canCheckout": true
30Γ2 + 20Γ2 = 100% β past the 100% threshold, and the server clamped the total to β¬0.00.
Step 16 β Navigate back to bag, observe the β¬0.00 total
Refreshed /cart: Total β¬0.00. Complete order is now enabled.
Step 17 β Click Complete order
Clicked Complete order.
Step 18 β Intercept the checkout request and send it to Repeater
With Intercept on, the click was captured as:
POST /api/checkout
{}POST /api/checkout
{}Sent it to Repeater.
Step 19 β Send and observe the response β flag!
Forwarded it. Response 200 OK:
{
"ok": true,
"receipt": {
"orderId": "BLE-F0AE5D5C4A",
"paid": 0,
"credit": 6600,
"message": "Your order is confirmed and being prepared.",
"flag": "INTIGRITI{094638e6-ba2f-4c50-8bb4-41ced0d5debd}"
}
}{
"ok": true,
"receipt": {
"orderId": "BLE-F0AE5D5C4A",
"paid": 0,
"credit": 6600,
"message": "Your order is confirmed and being prepared.",
"flag": "INTIGRITI{094638e6-ba2f-4c50-8bb4-41ced0d5debd}"
}
}
paid: 0. The β¬666.00 Midnight Collection, checked out for β¬0.00.
Flag: INTIGRITI{094638e6-ba2f-4c50-8bb4-41ced0d5debd} π―
Root cause
The coupon handler is not side-effect-free on its failure path. In pseudo-code:
on POST /api/cart/coupon { code }:
coupon = lookup(code)
if coupon in cart.promotions:
reset_apply_guard(cart) # BUG: state change on the 409 path
return 409 coupon_already_applied
if code in cart.promotions:
cart.promotions[code].count += 1 # increment with no grant check
else:
cart.promotions.append({code, count: 1})
return 200 carton POST /api/cart/coupon { code }:
coupon = lookup(code)
if coupon in cart.promotions:
reset_apply_guard(cart) # BUG: state change on the 409 path
return 409 coupon_already_applied
if code in cart.promotions:
cart.promotions[code].count += 1 # increment with no grant check
else:
cart.promotions.append({code, count: 1})
return 200 cartTwo problems:
- The 409 branch touches state. A rejection should be a no-op. Instead it resets
apply_guardβ the exact flag preventing re-application. countisn't tied to a grant.NEWS30is only supposed to be granted once per newsletter subscription. The increment path never consults the grant store β only the (now-cleared)apply_guard.
Since total = subtotal β Ξ£(subtotal Γ percent Γ count) clamped at zero, every increment is directly monetisable. Two rounds reach 110%, total clamps to β¬0.00, and checkout only checks total β€ credit.
Impact
- Direct financial loss β the β¬666.00 Midnight Collection is obtainable for β¬0.00 by any anonymous visitor.
- Not item-specific β the bug is in the generic coupon handler, so any future credit-gated product is exploitable with the same primitive.
- Fully self-service β both codes are public (
NEWS30from the newsletter form,NIGHT20from the storefront). No social engineering needed. - Silent β the UI renders
Γ Nnormally, so an exploiting user looks like a legitimate customer with extra coupons. No server-side alarm fires.
Recommended fix
- Make the 409 branch a no-op. Return
coupon_already_appliedwithout touchingapply_guard,count, or any other cart state. - Bind
countto explicit grants. Trackgrants: {NEWS30: 1}per session, incremented only by the corresponding event. The coupon endpoint should consume a grant, never create one. - Make the apply atomic. Per-cart transaction or lock around the dedupe check and mutation.
- Reject
total < 0as an application error instead of silently clamping. - Rotate the leaked flag and audit order history for β¬0.00 checkouts of
midnight-vault.
Takeaway
The lesson wasn't about coupons. It was about status codes.
A 409 means "your request conflicted with something." It does not mean "the server left its state untouched." That gap β between what the response says and what the server actually did β is where a lot of business logic bugs live.
The flag was in the receipt. The lesson was in the error message.
And that's a wrap on the October challenge! π―
From a β¬666 price tag to a β¬0.00 receipt β all it took was sending two requests in the exact right order.
What I love (and hate) about business logic bugs is that they completely ignore your fuzzing wordlists and your 30-thread race condition scripts. They just sit there, hiding behind a normal-looking error message, waiting for you to ask the right question. If there's one thing I'm taking away from this, it's that error messages are just opinions β the server's internal state is the real truth.
Thanks for sticking around till the end. If this breakdown helped you see 4xx responses a little differently, or if you're currently stuck on a CTF and pulling your hair out β don't give up. Sometimes the bug is just one request past the point where you'd normally walk away.
If you enjoyed the read, drop a like or leave a comment. I'd love to hear how you'd have approached this one, or what weird logic flaws you've stumbled into recently!
Let's connect:
- LinkedIn: https://www.linkedin.com/in/nitin-bhanderi/
- Medium : https://medium.com/@hacker_np
- Twitter/X: https://x.com/nitin_bhanderi_