🛡 Защищаем свои ресурсы. Часть 9: Небезопасная многопоточность

Всем привет 🤗 Продолжаем образовательную рубрику и сегодня разберем уязвимость, связанную с небезопасной многопоточностью (Race Condition).

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

И сервер, пытаясь быть быстрым, сам создаёт проблему 😂.

Что такое Race Condition

Race Condition - это ошибка, возникающая, когда приложение обрабатывает конкурентные запросы без надлежащей синхронизации.

То есть: логика приложения рассчитана на последовательное выполнение операций, а в реальности они накладываются друг на друга.

Как это выглядит в реальной жизни

Допустим, есть операция списания денег со счёта:

1. Проверить баланс → 100 ₽ 2. Вычесть сумму → 50 ₽ 3. Записать новый баланс → 50 ₽

Если два запроса на списание 100 ₽ придут одновременно, они могут оба увидеть баланс 100 ₽, и после двух списаний баланс станет 0 ₽, хотя по логике должен был быть -100 ₽ (и отклонён).

Промежуток времени, когда возможно такое наложение, называется «окно гонки» (race window). Это может быть доля секунды между чтением и записью в базу данных.

Что может сделать злоумышленник

Через Race Condition можно:

• несколько раз использовать одноразовый купон или промокод • проголосовать или поставить оценку несколько раз • снять или перевести больше денег, чем есть на счету • повторно использовать одноразовый CAPTCHA-ответ • обойти ограничение на количество попыток ввода пароля

Причём атака часто выглядит как набор почти одновременных запросов.

Blind Race Condition - когда результат неочевиден

Иногда приложение не показывает явно, что операция прошла несколько раз.

Но это не значит, что состояние гонки невозможно эксплуатировать.

Примеры косвенных признаков:

• изменение итоговой суммы заказа после нескольких быстрых кликов • списание бонусов или начисление баллов больше одного раза • отправка нескольких одинаковых сообщений или уведомлений

То есть: пользователь видит странное поведение системы, хотя «ошибки» нет. Где чаще всего возникает Race Condition

Любое место, где приложение:

• выполняет операцию «прочитать-изменить-записать» • проверяет лимиты или доступность ресурса • использует неделимые (неатомарные) транзакции • работает с файловой системой или кэшем без блокировок • обрабатывает заказы, платежи, бронирования

Если операция состоит из нескольких шагов - это зона риска.

Почему это всё ещё встречается

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

Как защищаются от Race Condition

Надёжные способы:

• использовать атомарные операции БД (UPDATE … WHERE) • применять транзакции с правильным уровнем изоляции • добавлять блокировки (мьютексы) на критические участки кода • использовать оптимистичные или пессимистичные блокировки • внедрять механизм уникальных токенов для идемпотентности (idempotency keys)

❗️Важно: нельзя полагаться только на проверки в коде приложения — защита должна быть на уровне базы данных или системы.

Вывод

Race Condition - коварная уязвимость, потому что её сложно обнаружить при обычном тестировании.

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

Такие ошибки могут годами жить в продакшене и проявиться только при высокой нагрузке или целенаправленной атаке.

Продолжение следует 👉 Часть 7: SSRF