Всем привет! Меня зовут Роман. В ИТ я больше семи лет: начинал с разработки, а теперь занимаюсь AppSec и параллельно пробую себя в багхантинге. Сейчас вхожу в топ-25 рейтинга на Standoff Bug Bounty. Здесь я выступаю как начинающий исследователь багов и буду рад поделиться своими наработками. Сегодня обсудим уязвимости бизнес‑логики — сложные и часто недооцененные ошибки, способные привести к серьезному ущербу. Разберем, как их находить, почему они опасны и что делает охоту за ними в багбаунти такой увлекательной. Погнали!

Если вы делаете первые шаги в багхантинге, советую посмотреть, как стартовали ребята из сообщества Standoff Bug Bounty. Для тех, кто уже подбирается к наградам, будет полезна статья @olegbrain про поиск уязвимостей SSRF с кучей ссылок на полезные ресурсы и инструменты. Отдельно рекомендую почитать про Broken Access Control ‑ один из самых распространенных типов уязвимостей, а также про топ-10 инструментов для старта в багбаунти. 

Все сказанное ниже можно посмотреть и послушать

Включайте видео или смотрите прямо здесь:

Статья носит исключительно информационный и образовательный характер и не является инструкцией или призывом к совершению противоправных действий. Описанные материалы предназначены для повышения осведомленности о возможных уязвимостях и методах их предотвращения. Любое тестирование безопасности допускается только при наличии явного разрешения правообладателя соответствующих ресурсов или в рамках официальной программы багбаунти. Несанкционированные действия могут нарушать законодательство. Автор статьи не поощряет и не поддерживает неправомерное использование представленной информации и не несет ответственности за ее использование в противоправных целях. Помните, что нужно уделять внимание защите своих данных и использовать информацию из статьи исключительно в законных целях. 

Бизнес‑логика и типы уязвимостей 

Для начала немного теории. Бизнес‑логика — это набор правил, которые определяют, как должно работать приложение, какие действия и в каком порядке разрешены пользователям. Если эти правила реализованы некорректно, возникают уязвимости бизнес‑логики: ошибки, позволяющие заставить приложение выполнять изначально непредусмотренные сценарии. Проще говоря, разработчик задумывает определенный ход взаимодействия, а взгляд багхантера на систему может оказаться совсем другим.

Уязвимости бизнес‑логики образуют довольно широкую категорию. В нее входят такие типы уязвимостей, как нарушение контроля доступа (Broken Access Control), состояние гонки (Race Condition), обход авторизации (Auth Bypass), а также специфические логические ошибки (Specific Logic Bugs), которые проявляются только в конкретных функциях или сценариях приложения.

Многообразие типов уязвимостей
Многообразие типов уязвимостей

Broken Access Control 

Рассмотрим классический пример ролевой модели с обычным пользователем и администратором. Корректная реализация подразумевает строгое разграничение доступа: пользователь может взаимодействовать только с предназначенными для него эндпойнтами, а администратор — со своими. Проблема возникает, когда приложение недостаточно проверяет права доступа к конкретному маршруту. В таком случае, используя рядовую учетную запись, пользователь может обратиться к административному роуту и получить данные, которые ему не полагаются.

Broken Access Control 
Broken Access Control 

Это, конечно, самый простой случай. На практике модели могут быть разнообразны и сложны, и в каждой из них можно обнаружить какие‑то «дыры». Ошибки бывают разными: одна роль может получить доступ к закрытому для нее маршруту, а неочевидная последовательность действий в рамках одного метода API даст пользователю возможность расширить свои привилегии. Сложность модели напрямую влияет на вероятность появления таких недочетов: чем насыщеннее система ролей и правил, тем выше риск, что разработчик упустит какую‑либо проверку. 

В этом простом примере пользователь просто «заглядывает» в административный роут, получая при этом какие-то данные, но реальная задача — найти эти маршруты. Как это сделать? Один из способов — изучить JavaScript‑файлы, где могут быть указаны все пути, а другой — выполнить перебор (brute‑force).

Есть и более удобный метод: обратить внимание на то, как клиент получает данные о своей роли от сервера. После авторизации сервер обычно отдает объект, содержащий, к примеру, поле role. Браузер, получив эту информацию, формирует пользовательский интерфейс: обычному пользователю — простой набор элементов, администратору — расширенный. Если перехватить ответ сервера и изменить роль user на admin, браузер отрисует админский интерфейс и, следовательно, покажет новые роуты, к которым теперь можно попытаться обратиться. Таким способом можно «подсмотреть», какие административные функции скрыты от обычного пользователя.

Broken Access Control 
Broken Access Control 

Пример с редактированием профиля 

Представим, что у пользователя есть возможность изменить свой профиль — по умолчанию приложение позволяет менять username. При успешном запросе сервер отвечает кодом 200, подтверждая, что изменение прошло. Однако в модели пользователя присутствуют и другие поля: email, телефон, а также, например, role. Из интерфейса может быть не очевидно, что эти дополнительные атрибуты тоже можно изменить. Если перехватить запрос обновления профиля и подменить один из отправляемых параметров, сервер может принять и сохранить новое значение без дополнительной проверки. В результате обычный пользователь из своей учетной записи получит административные привилегии.

Race Condition

Суть этого типа уязвимостей (состояние гонки) в том, что несколько параллельных запросов могут одновременно работать с одной и той же сущностью, а отсутствие надежных блокировок на стороне сервера позволяет выполнить действие несколько раз. Наглядный пример — промокод, который по задумке может быть использован единожды. Если пользователь отправит почти одновременно два запроса на его применение, оба могут увидеть статус «не использован» и применить его, в результате получив двойную скидку. Главный фактор здесь — отсутствие атомарного (неделимого) обновления флага о том, использовался ли промокод.

Состояние гонки может возникать не только в рамках одного пользователя. Представьте, что существует уникальный промокод‑сертификат, который должен быть применен всего один раз. Если два разных пользователя одновременно отправят запросы на его использование, каждый из них может получить выгоду, пока система не успеет заблокировать промокод.

Технически такие уязвимости появляются из‑за отсутствия необходимых блокировок на бэкенде: отправляемые параллельно запросы получают неактуальное значение флага об использовании. При одновременном выполнении оба потока проходят проверку, считают промокод свободным и оба завершают запись, тем самым обходя защитный механизм. 

Auth Bypass 

Приведу реальный пример следующего типа уязвимости — обхода авторизации — из багбаунти. В системе пользователь может запросить детали своих прошлых заказов. В запросе передается токен JWT и идентификатор конкретного заказа. К этой схеме добавился микросервис. Микросервисная архитектура подразумевает наличие гейт‑сервиса, который проверяет подпись токена, извлекает из него идентификатор пользователя и заменяет соответствующее поле в запросе.

Проблема появляется, когда бэкенд‑служба, написанная на другом фреймворке, берет идентификатор пользователя не из того места, где его подставил гейт, а из второго параметра запроса, который проконтролировать не удалось. В итоге злоумышленник может подменить в запросе поле user_id на любой желаемый идентификатор, а гейт оставит токен валидным, поскольку подпись совпадает. Бэкенд, не проверив согласованность токена и user_id, вернет данные о чужом заказе.

Таким образом, несовпадение источников данных в разных микросервисах открывает путь к обходу авторизации.

Auth Bypass 
Auth Bypass 

Specific Logic Bugs 

Бизнес‑логика часто подразумевает детали, которые трудно увидеть в общем виде, но они могут стать настоящими уязвимостями:

  • Блокировка товара до оплаты. Приложение маркетплейса, например, резервирует товар в корзине, но не ограничивает количество единиц, которое один пользователь может зарезервировать. В результате любой желающий может добавить в корзину все имеющиеся товары без оплаты, тем самым парализуя магазин (видимый DoS‑эффект — отказ в обслуживании на уровне бизнес-логики).

  • Ошибки округления при конвертации валют. В финансовых приложениях некорректная работа с числами с плавающей запятой может приводить к необоснованному получению средств при пересчете валют.

  • Утечка геокоординат через функцию «люди рядом». Приложение показывает расстояние до другого пользователя, добавляя случайную погрешность в диапазоне от 0 до некоторого значения n. При большом количестве запросов злоумышленник может подобрать свое местоположение так, чтобы погрешность стремилась к нулю, получая тем самым точные координаты цели. По сути, это метод триангуляции.

Практика 

Для демонстрации я использовал два тестовых веб‑приложения и универсальный инструмент для тестирования Burp Suite. Основная идея проста: включаем перехват, добавляем целевой хост в scope и начинаем «кликать» по всем доступным функциям, фиксируя запросы.

Регистрация с ролями 

В первом веб-приложении, перехватывая запрос регистрации, я заметил, что в теле передается поле role. Подменив значение user на admin и отправив запрос, успешно зарегистрировался как администратор. После входа в систему увидел административный интерфейс и новую функциональность, смог вызвать роуты, предназначенные только для администратора. Можно писать отчет и отправлять его на багбаунти площадку.

Это был базовый кейс. Если в запросе параметр роли отсутствует, его все равно можно обнаружить с помощью фаззинга или найти по JavaScript‑коду и добавить вручную. При этом сервер принимает значение без дополнительных проверок, что и выдает уязвимость.

Демонстрация решения:

Онлайн‑магазин 

Изменение роли в ответе сервера

Во втором тестовом приложении я перехватил запрос профиля, в ответе получил объект, где хранится текущая роль пользователя. Через Match & Replace в Burp заменил useradmin. После обновления страницы браузер отрисовал админскую панель и пошли обращения типа (api/admin/...). Это свидетельствовало о том, что клиентское приложение строит интерфейс исключительно на основе полученной роли, а сервер не проверяет права доступа при обработке последующих запросов к API.

Контроль доступа к административным роутам

Некоторые роуты, такие как api/admin/stats, оказались открытыми даже без токена — я получил информацию о выручке, количестве пользователей и заказов. Другие, например api/admin/users, корректно возвращали 403 Forbidden, показывая, что проверка прав реализована лишь частично.

Изменение цены 

Отправив запрос на изменение цены продукта из учетной записи обычного пользователя, я отметил, что цена действительно изменилась. В результате появилась возможность приобрести товар всего за 10 рублей. Это критически опасный баг. Безусловно, после обнаружения такой уязвимости следует вернуть изначальные цены и сообщить об ошибке вендору.

Race Condition с промокодом 

Промокод INTERNAL99 был помечен как «одноразовый». Обычно я стараюсь отправлять сразу несколько тестовых запросов от двух и более пользователей, потому что последствия могут различаться в разных сценариях. Race condition, например, может проходить от разных пользователей, но не от одного. Такой подход помогает выявить недостаток, каким бы он ни был.

Я скопировал запрос применения промокода, создал по две копии от разных пользовательских профилей и отправил их параллельно, создав группу в Repeater. Один из них прошел успешно, другой запрос тоже получил скидку, продемонстрировав возможность одновременного использования промокода несколькими пользователями.

DoS‑атака через резервацию товаров 

Кроме того, я обнаружил, что один пользователь может добавить в корзину неограниченное количество товаров, а наличие средств проверяется только при окончательной оплате. Таким образом, с помощью скрипта, постоянно добавляющего в корзину все доступные единицы, злоумышленник может полностью «заблокировать» склад, сделав его недоступным для остальных покупателей.

Демонстрация решения:

Реальные кейсы 

Auth Bypass через дублирование сущности

Это уже упомянутый кейс, где один гейт в микросервисной архитектуре проверял токен, а другой — доставлял данные из базы. Подделав идентификатор пользователя в запросе, удалось обойти первую проверку и получить доступ к чужим данным.

Auth Bypass через дублирование сущности
Auth Bypass через дублирование сущности

Auth Bypass через удаление куки

При входе по номеру телефона и SMS‑коду сервер выдавал куки. Однако уже после авторизации, в следующем эндпойнте предназначенном для проверки статуса, отсутствие куки не приводило к ошибке 401 Unauthorize — бэкенд-сервер просто возвращал авторизационные куки другого пользователя в ответе. Это позволяло получить токен без полноценной проверки. Но будьте внимательны: система может дополнительно проверять IP‑адрес, поэтому куки не нужны.

Auth Bypass #2
Auth Bypass #2

Всегда проверяйте и перепроверяйте все возможные сценарии. Иногда что‑то кажется недостижимым или, наоборот, как будто очевидным, но на самом деле это не так. Логика приложения может быть самой разной, наша задача — отыскать в ней изъян. 

Оба примера подчеркивают, насколько важно проверять согласованность всех элементов запроса (токен, куки, параметры) в разных сервисах. 

Выводы 

Сканеры уязвимостей — полезный инструмент для багхантера, но далеко не единственный. Чтобы найти ошибки бизнес‑логики, нужно глубже погрузиться в работу приложения, понять замысел разработчика, а затем попытаться «сломать» его, отклоняясь от заданного пути. Стандартный набор рекомендаций для начинающих: лаборатория PortSwigger, материалы OWASP и практические стенды, похожие на те (12), которые я разобрал.

Помните: то, что кажется невозможным, часто реализуемо, если попытаться посмотреть на систему под другим углом. Тренируйтесь, мыслите нестандартно и, как всегда, — try harder!