Any Account Mints Permanent Premium Reward Perks for Free
The Risk
Anyone who signed up for a free account, including throwaway accounts that had never bought anything, could grant themselves a paid membership perk worth a full year of free experiences without paying for it. The app handed back a special link that unlocked the rewards portal, and the attacker could set that link to never expire, so it kept working until the year 2099 even after the original account was deleted. Each free signup produced a new permanent link, and every reward claimed through it was a real cost the company paid to the venue on behalf of someone who never qualified. There was no limit on how many of these links could be created.
The Vulnerability
The platform sells a paid membership tier whose headline benefit is a premium days-out perk, marketed as a year of free experiences and explicitly gated behind a qualifying purchase. To unlock the perk in the app, a request is sent to a rewards minting endpoint:
POST /rewards/mint-perk
{"type":"DAYS_OUT","expiryDate":"2099-12-31"} The endpoint responds with a signed redemption URL of the form:
{"url":"https://rewards-portal.example?token=<accountId>,<expiryDate>&value=<base64 HMAC-SHA256>"} Two design flaws combine here:
- No entitlement check. The server signed and returned a valid perk URL regardless of the caller's actual membership state. A freshly registered account whose rewards wallet reported
status: "INELIGIBLE"with zero perks received exactly the same signed URL as a paying member. - Caller-controlled expiry. The
expiryDatefield was taken verbatim from the request body and folded into the signed token. SettingexpiryDate=2099-12-31produced a near-permanent URL.
The signature itself was a sound HMAC-SHA256 over a token of the form <accountId>,<expiryDate>, and the downstream portal genuinely enforced it: a single tampered byte in the signature was rejected. The flaw was never weak cryptography. It was that the minting service signed valid tokens for accounts that had no entitlement, using an expiry the attacker chose.
The Attack
The full chain required nothing more than a free account and a single request.
Confirm the account has no entitlement
After signing in with a throwaway account, the rewards wallet was queried and returned status: "INELIGIBLE", an all-zero wallet, and perks: []. By the platform's own server-side state, this account was entitled to nothing.
Mint a permanent perk URL
A single POST to the minting endpoint with expiryDate set to a far-future date returned an HTTP 200 with a signed redemption URL. The caller-supplied expiry was reflected verbatim into the signed token, and the wallet remained INELIGIBLE throughout.
Use the URL with no account at all
Opened in a fresh browser context with no session cookies, the minted URL authenticated the holder straight into the live rewards portal, which rendered the full perk experience with claimable vouchers for real attraction admissions and similar experiences. Reaching the claim modal showed the portal's own redemption rules on screen. Testing stopped at the claim step to avoid drawing down a real voucher. Because the token carries its own signature and far-future expiry, it keeps working after the originating account is deleted.
Deterministic and unthrottled
Firing five mint requests in parallel returned five byte-identical signed URLs. There was no nonce, no per-request entropy, no rate limit, and no audit signal visible to the caller. A single minted URL is therefore replayable forever, and scaling the attack costs one free signup per additional permanent URL.
A second perk class on the same code path
The same endpoint also accepted type: "PROMO", returning an additional signed parameter pair that minted a distinct second perk class under identical conditions. This confirmed the entitlement gap was not unique to one perk slug but a property of the minting service itself. Mass-assignment probes against fields like status, accountId, amount and perkId were silently ignored, and the account identifier in the signed token came from the session rather than the request body, so this was an entitlement bypass rather than a cross-account identifier attack.
The Impact
Every voucher claimed through the rewards portal is a paid-for attraction entry that the platform settles with the partner venue on the user's behalf. That settlement is the mechanism that makes the perk a genuine annual benefit in the first place, and it is a direct cash outflow.
Because the mint endpoint had no eligibility gate, no rate limit, no caller-visible audit signal, and produced permanent, account-detachable URLs, an attacker could:
- Grant themselves a paid-tier perk from a free account that never met the qualifying-purchase condition that funds the programme.
- Keep the perk indefinitely, surviving deletion of the originating account, out to a self-chosen 2099 expiry.
- Claim vouchers at the portal's maximum cadence (roughly four to eight per month per minted URL) with no further interaction with the platform.
- Scale without limit, one fresh free signup per additional permanent URL.
Each claim is money the platform pays a venue for someone who never qualified, so the harm is a direct, repeatable revenue leak rather than a one-off nuisance.
Why Mobile Matters Here
The perk-minting flow is the unlock path the mobile app uses to hand its members into the rewards portal. The signed-URL handoff is exactly the mobile-to-portal bridge an attacker abuses: the portal even rejects tampered links with a message instructing the user to navigate from the app. The vulnerability lives in the API behind that app feature, where a server-side entitlement decision was simply never made.
Remediation
- Reject any mint request where the caller's wallet status is
INELIGIBLEor the requested perk type is not present in the account'sperks[]list. - Derive the expiry server-side from stored wallet state. Never sign an expiry value taken from the request body.
- Add a per-token nonce and a short, server-derived time-to-live so a single mint cannot be replayed indefinitely.
- Log every token issuance and rate-limit minting per account so abuse is visible and bounded.
- Apply the same entitlement and expiry controls across every perk type the endpoint can mint, not just the primary one.