Promo Code Bruteforce Without Rate Limits: Finding Hidden Discounts
Promo fields are tiny password boxes. Short alphabets, human-memorable strings, seasonal patterns like SPRING24. If POST /cart/coupon answers distinctly and never slows you down, you can guess your way into margin loss.
Stop treating this as "just a discount." Stacked with first-order perks or free shipping, a guessed code is account-takeover-adjacent financially.
Fingerprint the oracle
Apply a wrong code and a wrong-but-similar code. Compare status codes, body lengths, timing, and whether the cart hash changes. You want a clear invalid signal. Blind 200s with identical JSON make bruteforce noisy and reports weak.
Check whether the code is validated:
- Client-side only (list embedded in JS—yes, still happens)
- Edge function with no auth
- Authenticated cart endpoint tied to
cartId
Unauthenticated apply on a guessable ID is the best case for automation. Authenticated apply still matters if there is no lockout per account or per IP.
Hunt the bundle for promoCodes, validCoupons, or CMS-driven lists shipped to the client for autocomplete. Finding the code in JS is not bruteforce—it is an information leak that collapses the search space to zero. File that clearly.
Attack like a grown-up
Do not throw 100k requests at a live store. Programs will bounce you.
Safer approach:
- Confirm no rate limit with a short burst (for example 30–50 guesses) and show identical latency.
- Demonstrate a hit using a code you were issued legitimately, then show the same endpoint accepts rapid invalids afterward with no CAPTCHA or 429.
- If you discover a second valid code through limited, patterned guesses (year suffixes, store abbreviation + season), document the pattern length and remaining search space.
Wordlists from public coupon blogs are tempting and often out of scope if they thrash CDN bills. Prefer proving the missing control over harvesting every live code.
Rotate soft signals too: new carts, new sessions, and X-Forwarded-For only when the program allows header tests. If a trivial header bypasses a weak IP throttle, that belongs in the same report as the missing lockout.
Case folding and whitespace tricks (spring24 vs SPRING24, trailing spaces) sometimes reveal a second oracle: one form normalizes, another does not. Note which apply path you used.
Hidden channels
GraphQL applyCoupon(code:) aliases, mobile API hosts, and "gift card balance check" endpoints are siblings of promo bruteforce. Balance checks that return exact remaining value without throttling are especially ugly—treat them like account enumeration plus wallet drain recon.
Also try applying the same code twice across accounts when the rule is first-use-only. That is business logic, not bruteforce, but triage often files them together.
What to recommend
Per-IP and per-account rate limits, exponential backoff, CAPTCHA after N failures, generic error messages, long high-entropy codes, and server-side redemption caps. Logging should alert on spray patterns.
A solid report shows response samples for invalid vs valid, evidence of unlimited attempts, and a single redeemed code on your cart with before/after totals—not a dump of fifty stolen promos.