Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2513 min read

Loyalty redeem endpoint missing ownership check I redeemed another customer — QQ F2 (PR #551)

Mikael Helsinki Kallio 36-yo 8-yr independent bug bounty hunter HackerOne Nordic top-5 fintech security specialty @mikael-bb HackerOne disclosure summaries + weekly research blog. Q1 2026 thMenu open-source repo customer-side loyalty redemption flows audit Pro+ tier customers Loyalty Points accumulate reward coupon redeem POST /api/loyalty/redeem body loyalty_member_id + reward_id Stripe coupon code. Endpoint code loyalty_member_id from body customer JWT bearer validated but owner_id never cross-checked against loyalty_members.customer_id authentication passed authorization ownership missing. Threat model attacker legitimate customer profile learns target loyalty_member_id URL leak profile share screenshot kendi JWT different loyalty_member_id POST server deducts balance + issues Stripe coupon victim loyalty points attacker hand. Lab repro 2 test accounts A + B A 500 points test orders B loyalty_member_id hand A JWT POST loyalty_member_id A reward cheap 200 OK A points dropped Stripe coupon A expected. Then A JWT POST loyalty_member_id B reward cheap 200 OK B balance dropped A Stripe coupon cross-customer loyalty redemption A stole loyalty points B. Realistic scenario thMenu Diamond-tier hotel restaurants customers join loyalty QR scan attacker weeks restaurant collecting loyalty_member_id from each customer network tab JWT decode loyalty profile fetch redeems each victim balance own coupon silent exfiltration. Writeup CVSS 7.8 HIGH privilege escalation + financial theft Stripe coupon issuance fix SELECT customer_id ownership match + atomic balance deduct + audit log. Engineering 30 minutes reproduce 3 wrong theories (1) loyalty_member_id UUID v4 unpredictable correct insufficient UUID unpredictable observable URLs network responses QR scan flows missing authorization not lucky-attacker-knows risk active social-engineering vector; (2) customer JWT already encodes loyalty member ID wrong customer_id and loyalty_member_id different identifiers 1:N customer multiple restaurants loyalty member JWT customer_id loyalty_members.customer_id references ownership cross-check explicit; (3) rate-limit prevents enumeration PR #311 IP-hash 15/min shipped single victim 1 redeem enough rate-limit slows bulk doesn't bound single-target. Correct fix ownership match SELECT + atomic balance deduct + structured audit log. Forensic apps/web-menu/src/app/api/loyalty/redeem/route.ts customerId session.customer_id JWT-validated MISSING ownership cross-check SELECT WHERE id=? no customer_id filter OWASP API Security Top 10 #1 Broken Object Level Authorization BOLA customer-side authentication who are you authorization do you have access. Production audit 90 days loyalty redemptions × customer_id JWT vs loyalty_members.customer_id DB 0 attack patterns legitimate but door open Mikael first finder. PR #551 batch QQ F2 3-layer fix Layer 1 ownership match SELECT WHERE id=? AND customer_id=? JWT 404 Not found OR no ownership same response enumeration-resistance. Layer 2 atomic balance deduct ownership inline filter UPDATE WHERE id=? AND customer_id=? AND balance>=? meta.changes=0 422 insufficient_balance_or_ownership_failed race-safe + ownership-validated. Layer 3 structured audit log event loyalty_redeem + customer_id + loyalty_member_id + reward_id + amount + caller_ip + result Logpush + Sentry suspicious pattern retroactive flag. Sibling-surface BOLA sweep monorepo customer-side /api/loyalty/redeem QQ F2 + /api/orders/[id]/tip PR #326 order ownership + /api/orders/[id]/cancel PR #311 table_session_id + /api/orders/[id]/track table_session_id + /api/customer/profile JWT customer_id + /api/wishlist/add JWT customer_id PR #351 + /api/promo/apply order_id ownership. Mikael only outstanding BOLA case engineering BOLA prevention checklist wiki every new customer-side endpoint authentication + authorization cross-check before merging. Production audit 0 attack first finder Mikael €2800 Wise CVSS 7.8 + Hall of Fame + advisory board priority seat HackerOne Nordic bug bounty market top-tier disclosure ecosystems Helsinki AppSec community 5.4k. Pelin Izmir Bornova 33-yo HackerOne TR top-10 payment + loyalty + customer-side specialty 5-yr parallel disclosure €2500 Turkish HackerOne community top-10 entry blog 3.4k. Pattern Authentication alone NOT enough customer-side endpoints authorization ownership cross-check separate layer SELECT WHERE clause AND customer_id=? JWT + UPDATE inline filter AND customer_id=? + 404 enumeration-resistance + structured audit log canonical OWASP API #1 BOLA. Implementation customer-side endpoint identify + JWT customer_id session read + SELECT WHERE id=? AND customer_id=? JWT + UPDATE inline filter atomic + ownership + race-safe + 404 enumeration-resistance (not 403) + structured console.log audit + Sentry suspicious + PR template checkbox + quarterly BOLA audit. PR #551 reference.

th

thMenu Team

thmenu.com

Found this helpful? Share it.