• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Ломаем бизнес-логику через Race Condition

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,311
Розыгрыши
0
Реакции
591
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Состояние гонки иногда позволяет вмешаться в алгоритмы обработки данных: от оплаты корзины в интернет‑магазине до механики перевыпуска API-ключей с повышением привилегий. На примерах с HTTP/2 и single-packet attack я покажу, как правильно подобранный момент атаки дает возможность «вырваться вперед на повороте» и буквально переписать бизнес‑логику приложения.
Это исследование получило первое место на Для просмотра ссылки Войди или Зарегистрируйся в категории «Hack the logic». Соревнование ежегодно проводится компанией Awillix.

warning​

Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Race Condition (состояние гонки) — это уязвимость в веб‑приложениях, которую относят к группе уязвимостей бизнес‑логики. Если ты аппсек, багхантер или пентестер, то тебе будет полезно в этом механизме разобраться.

Как эта уязвимость выглядит в реальных веб‑приложениях? В голову сразу приходит такой сценарий: у меня есть десять рублей, и товар стоит десять рублей, можно одновременно отправить два запроса на его оплату, чтобы веб‑приложение одновременно поработало с 10-рублевым балансом, и если оба пройдут, то получится два товара за десять рублей, итого — один бесплатный товар. Или, например, такое: в кино остался последний билет, а нас двое, можно отправить два запроса на покупку билета одновременно, и если все пройдет как надо, то в ответе будет два билета. По крайней мере такие сценарии и еще пару похожих мне предложил DeepSeek R1.

Такие сценарии работают за счет эксплуатации одной конечной точки (одного запроса), которая неправильно работает с нужными переменными — балансом кошелька или количеством непроданных билетов. Один обработчик запроса параллельно достает баланс или количество билетов из базы данных для двух запросов и видит, что для обоих запросов ему возвращается десять рублей или один последний билет, после чего он радостно идет выдавать покупку, так как денег хватает или билет остался. Профит!

С помощью такой уязвимости мы как бы перескакиваем барьер нулевого баланса и уводим переменную в отрицательные значения (в некоторых случаях наоборот — увеличиваем количество и перескакиваем верхнюю границу).

А что, если я скажу: это лишь вершина айсберга и меньшая часть потенциала уязвимости?


Заезд первый. Новичок в гонках забирает свой первый контракт​

Мы привыкли использовать Race Condition, чтобы перепрыгнуть граничное состояние, где какая‑то переменная равна нулю, но, если действовать оперативно, можно перепрыгнуть целый кусок логики. Давай в качестве первого примера разберем простой и понятный сценарий, он поможет понять суть.

Для просмотра ссылки Войди или Зарегистрируйся
Наш первый заезд будет на трассе с оплатой корзины. Вот как происходит оплата.

  1. Пользователь добавляет в корзину или убирает оттуда товары на сайте интернет‑магазина, тем самым формируя заказ.
  2. Пользователь отправляет запрос на оплату корзины, и ему в ответ возвращается ссылка, по которой фронтенд перенаправляет его в сторону интернет‑эквайринга (прием онлайн‑платежей as a service).
  3. Магазин блокирует изменение статуса корзины (допустим, Waiting for payment) — ее наполнение теперь нельзя изменить.
  4. Пользователь оплачивает корзину, и эквайринг возвращает сайту информацию об успешной оплате.
  5. Получив подтверждение от эквайринга, сайт добавляет в личный кабинет пользователя оформленный заказ.
Вроде все хорошо, но давай посмотрим на процесс с другой стороны. Мы здесь можем повлиять только на первый и второй шаги, а именно изменение состояния корзины и отправку корзины на оплату. Будем считать, что с оплатой на четвертом шаге все окей, так как это сторонний сервис.

Но что, если в нашей гонке мы запустим одновременно добавление товара в корзину и ее оплату? Сможем ли мы между вторым и третьим шагом добавить свой товар так, чтобы состояние корзины изменилось за «мгновение» до отправки запроса в эквайринг? Сможет ли веб‑сайт успеть добавить в корзину товар и одновременно сделать так, чтобы в сумму, которую он передаст интернет‑эквайрингу, вошла стоимость этого товара?

Ответ — нет, специально подготовленные нами HTTP/2-запросы не дадут веб‑сайту шансов! Мы можем отправить запросы в одном TCP-пакете. Эта техника состоит из нескольких этапов: сначала мы отправим первую партию запросов, которая отдаст большую часть данных нашей пары, а последним маленьким TCP-пакетом завершим то, что хотели сказать серверу. В таком случае до бэкенда оба запроса целиком дойдут одновременно — джиттер сети не помешает.

www​

Подробнее об этом — в статье PortSwigger «Для просмотра ссылки Войди или Зарегистрируйся».
Итак, я добавил дешевый товар в корзину (149 рублей), подготовил запросы на оплату и добавление дорогого товара (500 рублей).

Запрос на добавление дорогого товара
Запрос на добавление дорогого товара

В Burp Suite Repeater я сгруппировал две вкладки в одну группу и выбрал режим single-packet attack. Когда я отправил запросы, я получил ссылку на оплату в ответе на POST /v2/cart/pay.

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
Я перешел по ней в браузере и увидел сумму в 149 рублей; обновив корзину на сайте, я увидел, что в ней два товара, — эксплуатация успешна! Конечно, я совершил покупку и убедился в том, что мне выдали два товара.

Для просмотра ссылки Войди или Зарегистрируйся
Теоретически в режиме single-packet attack у нас есть возможность отправить примерно 20–30 HTTP/2-запросов. В нашем случае оставим один под оплату корзины и по‑хорошему один нужно оставлять на Для просмотра ссылки Войди или Зарегистрируйся, оставшиеся — это весь наш потенциальный выигрыш с заезда.

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


Заезд второй. Смогу ли я участвовать в «Формуле-1»?​

А теперь то, ради чего я пригласил тебя прочесть мой рассказ.


Круг 1​

Итак, перед нами сервис для управления инфраструктурой, использующий ключи API. Для удобства работы они интегрированы в систему RBAC: права доступа каждого ключа строго разграничены, и для каждого установлен срок действия.

Из интерфейса нам доступно несколько хендлов API:

  1. GET /api/api-keys — получение информации о токенах на аккаунте (я уточню, что технически ключ API — это токен JWT, его можно использовать в заголовке cookie HTTP-запросов).
  2. POST /api/api-keys — выписывание токена с определенными правами и сроком действия.
  3. PATCH /api/api-keys/ — редактирование токена (его имя, права, срок действия).
  4. DELETE /api/api-keys/ — удаление токена.
Посмотрев подробнее интерфейс администратора, я увидел возможность перевыпустить ключ с параметром времени жизни токена — эта возможность нужна для продления токена без непосредственного удаления и создания точной копии двумя разными запросами.

Реализация была в ручке PUT /api/api-keys/.

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

И здесь меня посетила первая мысль. Смогу ли я перевыпустить в этом хендле сам себе слабый токен, например c бесконечным временем жизни? Я взял из тела своего слабого токена его UUID и дернул ручку перевыпуска.

Для просмотра ссылки Войди или Зарегистрируйся
Ура! "expired_at":null в теле ответа значит, что токен бесконечный. Обрати внимание, что привилегии у токена определены по такому же принципу: если поле permissions отсутствует, то привилегии максимальны.

Мы можем продлевать жизнь токена, который доверил нам администратор на ограниченное время.

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


Круг 2​

Спустя еще несколько часов я заметил одну странность, которая не оставляла меня в покое: во время перевыпуска токен как будто сбрасывался до определенного состояния и из этого состояния спустя какое‑то короткое время привилегии менялись на нужные. В отчете об уязвимости триажер подтвердит мою догадку о существовании некоторого шаблонного состояния.

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

Для просмотра ссылки Войди или Зарегистрируйся
То есть если есть определенный пользователь и он имеет слабый токен, потом перевыпускает его, то при перевыпуске вначале токен наделяется полными правами этого пользователя, а через несколько секунд сбрасывается до нужных из исходного состояния.

Для просмотра ссылки Войди или Зарегистрируйся
Видимо, это сделано для того, чтобы нельзя было прыгнуть выше головы и перевыписать токен, который круче, чем привилегии владельца этого токена (мы помним, что пермишены задаются перечислением, и в такой конструкции «лишним» привилегиям будет просто неоткуда взяться).

В реальности ротацию ключей делает админ, и я подумал: а что, если в этой схеме заменить пользователя админом? Тогда админ перевыпускает слабый токен, на какой‑то момент у нового токена будут права админа, а потом сбросятся до исходных. Можно ли этим воспользоваться?

Для просмотра ссылки Войди или Зарегистрируйся
В таком случае если я перевыпущу перевыпущенный админом токен очень скоро после того, как это сделает админ, то может получиться, что выписанный админом токен при моем перевыпуске (от привилегий нового токена) будет сброшен до своего шаблонного состояния — то есть до пермишенов админа!

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

Теперь давай восстановим реальную последовательность атаки через сиквенс‑диаграмму.

Для просмотра ссылки Войди или Зарегистрируйся
Мы ломаем алгоритм состояний и вклиниваемся посредине в race window — окно в несколько секунд, когда перевыпущенный токен не успел сброситься до своего исходного состояния.

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

Для просмотра ссылки Войди или Зарегистрируйся
Как я говорил ранее, отсутствие поля permissions означает, что перевыпущенный токен — полноправный токен аккаунта. Эксплуатация успешна!


Итоги​

Я зарепортил эти два бага. Первый — как medium, и мне его оценили, повысив до high, а вот репорт с повышением оценили как дубликат, так как фикс одной проблемы решил другую.

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
В заключение я бы хотел сказать, что потенциал Race Conditions очень велик: ты буквально можешь манипулировать целой последовательностью обработки данных. Если получится ее проэксплуатировать, обгонишь всех на повороте и приедешь первым!
 
Activity
So far there's no one here
Сверху Снизу