🛡 Защищаем свои ресурсы. Часть 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