Browse CTFs New CTF Sign in

Coupon stacking

web_injection_logic Difficulty 1–5 30 min certifiable

Theory

Why This Matters

A 2021 bug bounty report against a large online retailer revealed that an attacker could apply the same 20%-off promotional code an unlimited number of times by removing and re-adding a single item to the cart between each application. The cart's discount state was stored client-side and the server re-validated the code on each cart update without checking how many times it had already been applied to that session. The researcher demonstrated a $400 order reduced to $0.14 after 47 code applications. A separate finding on the same platform showed two otherwise exclusive codes — a student discount and a flash sale — could be applied simultaneously by manipulating the order of API calls. Combined, these findings represented hundreds of thousands of dollars in potential revenue loss and earned a combined bounty exceeding $15,000.

Core Concept

Coupon stacking is the accumulation of discounts beyond what the application's business rules intend. The attack surface includes three distinct mechanisms. First, re-application of a single-use code: the server validates that the code exists and is not globally expired, but does not check whether the current user or session has already applied it. Second, stacking mutually exclusive codes: the server enforces only that each individual code is valid, not that the set of applied codes is permitted as a combination. Third, negative discount manipulation: a coupon that applies a percentage discount can be abused if the percentage parameter is transmitted client-side, allowing manipulation to values greater than 100 (producing credit) or to negative amounts (charging more than the list price, which is less exploitable but demonstrates injection).

The underlying invariant violated in all cases is the redemption ledger: a server-side record of which codes have been applied to which orders or user accounts. Without this ledger, the server cannot enforce one-time-use or mutual-exclusion constraints. Coupon code enumeration — discovering valid codes through sequential patterns (SUMMER21, SUMMER22) or short alphanumeric brute-force — expands the attack surface further, feeding valid codes into the stacking exploit.

Referral code self-referral is a related logic flaw: a user generates their own referral code, creates a second account with a shared email alias ([email protected]), applies the referral code to the new account, and both accounts receive referral credit — sometimes indefinitely through a loop.

Technical Deep-Dive

# Step 1: Add item to cart
POST /api/cart/items HTTP/1.1
Host: shop.example.com
Cookie: session=abc123
Content-Type: application/json

{"product_id": 9001, "quantity": 1}

# Response: {"cart_total": 100.00, "discount": 0.00}

# Step 2: Apply coupon — first application (legitimate)
POST /api/cart/coupon HTTP/1.1
Host: shop.example.com
Cookie: session=abc123
Content-Type: application/json

{"code": "SAVE20"}

# Response: {"cart_total": 80.00, "discount": 20.00, "applied_codes": ["SAVE20"]}

# Step 3: Remove item (resets discount state server-side without revoking code)
DELETE /api/cart/items/9001 HTTP/1.1
Host: shop.example.com
Cookie: session=abc123

# Step 4: Re-add item
POST /api/cart/items HTTP/1.1
Host: shop.example.com
Cookie: session=abc123
Content-Type: application/json

{"product_id": 9001, "quantity": 1}

# Step 5: Re-apply same coupon — server validates code exists, not re-application
POST /api/cart/coupon HTTP/1.1
Host: shop.example.com
Cookie: session=abc123
Content-Type: application/json

{"code": "SAVE20"}

# Response: {"cart_total": 64.00, "discount": 36.00}
# Repeat steps 3-5 until cart_total approaches 0
# Coupon code enumeration — sequential pattern brute-force
import requests
import string, itertools

session = requests.Session()
session.cookies.set('session', 'abc123')

BASE = 'https://shop.example.com'

# Pattern 1: Known prefix with incrementing suffix
for year in range(2020, 2027):
    for suffix in ['OFF', 'SALE', 'DEAL', 'SAVE']:
        code = f'SUMMER{year}{suffix}'
        r = session.post(f'{BASE}/api/cart/coupon', json={'code': code})
        if r.status_code == 200 and 'discount' in r.json():
            print(f'[+] Valid code found: {code}')

# Pattern 2: Short alphanumeric brute-force (4-character codes)
charset = string.ascii_uppercase + string.digits
for combo in itertools.product(charset, repeat=4):
    code = ''.join(combo)
    r = session.post(f'{BASE}/api/cart/coupon', json={'code': code})
    if r.status_code == 200:
        print(f'[+] Valid: {code}')

Security Assessment Methodology

  1. Map the discount API surface — Identify all endpoints that accept, validate, or apply coupon codes: /cart/coupon, /checkout/promo, /order/discount. Note whether they are stateful (cart-bound) or stateless (order-bound).
  2. Test single-code re-application — Apply a valid code, then remove and re-add a cart item. Re-attempt the same code. Observe whether the discount accumulates. Also try submitting the same code in the same request multiple times in an array: {"codes": ["SAVE20","SAVE20","SAVE20"]}.
  3. Test mutually exclusive code stacking — Obtain two codes that should be exclusive (e.g., a student discount and a flash sale). Apply both in sequence, varying the order. Use Burp Repeater to alternate between cart modification and code application.
  4. Probe for negative or excessive percentage parameters — If a percentage discount is present in a response body, attempt to replay the coupon application with a modified discount_pct parameter in the request. Test values: -10, 0, 101, 200.
  5. Enumerate coupon codes — Identify the code naming pattern from any known valid code. Use Burp Intruder with a wordlist of common promotional patterns. Respect rate limits; use Turbo Intruder if enumeration is throttled.
  6. Test self-referral loops — Create two accounts using email aliasing (user+a@, user+b@). Apply your own referral code to the second account. Confirm whether both accounts receive credit and whether the loop is repeatable.

Defensive Countermeasure — Maintain a server-side redemption ledger table (coupon_redemptions(coupon_id, user_id, order_id, redeemed_at)) and enforce unique constraints before applying any discount. Apply mutual-exclusion rules as a set of allowed code combinations checked at checkout, not as individual code validations. Never trust client-supplied discount percentages or amounts — compute all discount values server-side from the canonical coupon record. Rate-limit coupon validation endpoints to 10–20 attempts per session per minute and log failed validation attempts for anomaly detection.

Common Assessment Errors

  • Testing only the happy path — Submitting the coupon once and seeing 200 OK does not reveal re-application bugs. Always test the remove-and-reapply cycle and array-valued code parameters.
  • Missing the cart-state reset vector — Some applications reset applied coupons when the cart is modified, but then allow re-application. The re-application step is the vulnerability, not the reset.
  • Assuming rate limiting prevents enumeration — Many platforms apply rate limits per IP but not per session or user account. Rotating through multiple IP addresses or distributing enumeration across accounts bypasses IP-based limits.
  • Not testing code combination ordering — Mutual-exclusion checks sometimes only fire when codes are applied in a specific order. Always test both orderings: A then B, and B then A.
  • Overlooking referral self-referral — Referral systems are often scoped to different teams than promo codes and may have weaker validation. They are worth testing independently even if the core coupon system is well-protected.
  • Ignoring expired codes — Expired codes are sometimes re-activatable by manipulating a valid_until parameter in the request, or by replaying a previously captured request that was made before expiry.

NICE Framework Alignment

Code Knowledge/Skill/Task Statement How This Card Develops It
K0007 Knowledge of authentication, authorization, and access control methods Connects redemption ledger enforcement to authorization of financial operations
K0065 Knowledge of web application security testing techniques Develops cart manipulation and coupon brute-force techniques within a structured methodology
K0070 Knowledge of system and application security threats and vulnerabilities Categorises coupon stacking as a business logic threat distinct from injection or authentication bypass
S0001 Skill in conducting vulnerability scans and recognizing vulnerabilities in security systems Trains systematic enumeration and re-application testing across discount API surfaces
T0028 Identify and analyze vulnerabilities and risks in web applications Applies invariant analysis to identify missing redemption ledger enforcement
T0570 Perform technical security assessments of web applications Guides end-to-end coupon stacking assessment from discovery through proof-of-concept

Further Reading

  • OWASP Testing Guide v4.2, Section 4.10: Business Logic Testing — OWASP Foundation
  • The Web Application Hacker's Handbook, 2nd Ed., Chapter 11 — Stuttard & Pinto
  • HackerOne Disclosed Report: Coupon Reuse via Cart Manipulation — HackerOne Platform (anonymised)

Challenge Lab

Reinforce your learning with a hands-on generated challenge based on this card's competency.