Race Condition: веб-уязвимость, которую необходимо знать

Race Condition: веб-уязвимость, которую необходимо знать

@cherepawwka

Всем привет!

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

Race Condition


Введение

С развитием интернета веб-приложения стали неотъемлемой частью нашей жизни, упрощая различные задачи и предоставляя нам удобный доступ к некоторым услугам. Однако вместе с увеличением зависимости от веб-платформ растет и риск эксплуатации уязвимостей в них. Одна из таких уязвимостей называется race condition (состояние гонки). В этой статье мы рассмотрим, что такое race condition, какую угрозу она представляет для веб-приложений, и как разработчики могут устранить эту уязвимость для обеспечения надежной безопасности.


Понимание race condition

Race condition возникает, когда несколько процессов или потоков пытаются одновременно получить доступ к общим данным и изменить их, что приводит к непредсказуемым или ошибочным результатам. Эта уязвимость часто возникает в ситуациях, когда состояние ресурса изменяется в параллельных операциях.

Race condition в веб-приложения

Race condition в веб-приложениях может быть особенно опасной, так как она может привести к различным проблемам, включая повреждение данных, отказ в обслуживании (DoS), несанкционированный доступ к конфиденциальной информации и даже захват учетных записей. Из-за высокой параллельности веб-приложений race condition может возникать чаще, если её не обработать должным образом.

Основные причины возникновения уязвимости:

  1. Неправильная синхронизация или параллельный доступ к общим ресурсам: когда несколько пользователей или потоков без должной синхронизации одновременно взаимодействуют с общими ресурсами, такими как базы данных, файлы или переменные, могут возникнуть race condition;
  2. Недостаточное управление сеансами: в веб-приложениях с управлением сеансами race condition может возникать, если два запроса одновременно пытаются изменить один и тот же сеанс. Это может привести к несанкционированному доступу к сеансам других пользователей, что может привести к захвату сеанса и потенциальной компрометации данных;
  3. Неверная конфигурация механизмов кэширования: если механизмы кэширования настроены неверно, race condition может возникнуть, когда несколько запросов одновременно пытаются обновить или удалить записи кэша. Это может привести к предоставлению устаревших или некорректных данных пользователям.

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

Атака, эксплуатирующая race condition, использует тщательно рассчитанные по времени запросы, чтобы вызвать преднамеренные коллизии и использовать это непреднамеренное поведение в злонамеренных целях.

Ниже приведена хорошая инфографика с ресурса PortSwigger, откуда я позаимствовал часть теоретического материала для написания статьи:

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

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

Как и другие ошибки бизнес-логики, влияние race condition во многом зависит от приложения и конкретной функциональности, в которой возникает уязвимость.

Пример: limit overrun race condition

Самый известный тип race condition позволяет атакующему превысить некоторый предел, налагаемый бизнес-логикой приложения. Limit overrun — это подтип так называемых ошибок «от времени проверки до времени использования» (time-of-check to time-of-use, TOCTOU).

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

  1. Убедиться, что мы еще не использовали вводимый код;
  2. Применить скидку к сумме заказа;
  3. Обновить запись в базе данных, чтобы отразить тот факт, что код был использован.

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

Процесс проверки купона на скидку

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

Race condition при проверке кода скидки

Изображение выше демонстрирует, как приложение переходит во временное подсостояние: состояние, в которое оно входит и выходит до завершения обработки пользовательского запроса. В этом случае подсостояние начинается в тот момент, когда сервер начинает обрабатывать первый запрос, и заканчивается, когда он обновляет базу данных, чтобы указать, что вводимый промокод уже был использован. Из-за этого появляется небольшое «race window», в течение которого покупатель может многократно применять скидку.

Существует множество вариаций подобных атак:

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


Рассмотрим примерный PHP-код уязвимого к race condition приложения. Для примера вернёмся к обработке купонов на скидку:

<?php
function applyCoupon($couponCode) {
  // Проверка, что купон действителен
  if (checkCouponValidity($couponCode)) {
    // Получение текущей скидки
    $currentDiscount = getCurrentDiscount();
   
    // Получение возможного дополнительного процента скидки
    $additionalDiscount = getAdditionalDiscount($couponCode);
   
    // Обновление общей скидки
    $newDiscount = $currentDiscount + $additionalDiscount;
    updateTotalDiscount($newDiscount);
   
    // Сообщение об успешном применении купона
    echo "Купон успешно применен!";
  } else {
    // Сообщение о недействительном купоне
    echo "Недействительный купон!";
  }
}

function checkCouponValidity($couponCode) {
 // Проверка, находится ли купон в базе данных или другом хранилище
 // Возвращается true, если купон действителен, иначе — false
 // Важно: здесь отсутствует синхронизация параллельных запросов
}

function getCurrentDiscount() {
 // Получение текущей скидки из базы данных или другого хранилища
 // Возвращается текущая общая скидка (число)
 // Важно: здесь отсутствует синхронизация параллельных запросов
}

function getAdditionalDiscount($couponCode) {
 // Получение возможного дополнительного процента скидки для данного купона
 // Возвращается дополнительный процент скидки (число)
 // Важно: здесь отсутствует синхронизация параллельных запросов
}

function updateTotalDiscount($newDiscount) {
 // Обновление общей скидки в базе данных или другом хранилище
 // Важно: здесь отсутствует синхронизация параллельных запросов
}
?>

Разбор кода:

В данном коде функция applyCoupon() принимает код купона и применяет его к текущей скидке. Однако, код уязвим к race condition, так как не синхронизирует параллельные запросы, что может привести к непредсказуемым результатам.

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

Аналогично, другие функции, такие как checkCouponValidity(), getCurrentDiscount() и getAdditionalDiscount(), также не реализуют синхронизацию для параллельных запросов, что делает их уязвимыми к race condition.

В результате, если несколько запросов одновременно пытаются применить купон на скидку, race condition может возникнуть при чтении и обновлении значения общей скидки.

Чтобы устранить уязвимость к race condition в этом коде, необходимо реализовать синхронизацию параллельных запросов при обновлении общей скидки и чтении значений из базы данных или других хранилищ. Это может быть достигнуто, например, с использованием блокировок, мьютексов или других механизмов синхронизации.


Практика

В качестве практического примера давайте разберём простую лабораторную академии PortSwigger, посвященную limit overrun race condition.

Для её решения нам понадобится свежая версия Burp Suite с новой и очень крутой штукой в Repeater: возможность объединять вкладки в группу и отправлять несколько параллельных запросов. Так, без использования Turbo Intruder или Python мы сможем запросто проэксплуатировать уязвимость стандартными средствами Burp.

Стартанём лабораторию и ерейдём по ссылке:

Страница уязвимого приложения

Практически сразу нам доступен купон на скидку, наша задача — купить l33t jacket за 1337 долларов. Также для лабораторной даны тестовые креды: wiener:peter. С ними мы можем войти в личный кабинет, чтобы в дальнейшем совершать покупки.

После авторизации мы видим баланс нашего пользователя:

Баланс пользователя

Мда, 50 долларов будет маловато, чтобы купить жакет за 1337$. Но тут мы вспоминаем, что у нас есть промокод на скидку, так что самое время эксплуатировать race condition!

Положим товар в корзину и перейдём на страницу чекаута:

Страница оформления заказа

Перехватываем запрос при помощи Intercept, отправляем его несколько раз в Repeater и жмём Drop:

Перехват запроса

После чего идём в Repeater, жмём + и создаем новую группу (Create tab group):

Создание группы в Repeater
Создание группы в Repeater

После создания группы и добавления запросов в неё, нам необходимо выбрать решим отправки запросов: Send group in parallel. Так, у нас будет отправлено сразу несколько одинаковых запросов одновременно, что позволит вызвать состояние гонки в приложении:

Настройка параметров отправки нескольких запросов

После настройки отправляем запросы и получаем на каждой вкладке следующий результат:

Успешный результат применения купона

8 вкладок для эксплуатации оказалось маловато, так как скидка от восьми купонов позволяет сбить цену до 224 долларов (1337*(0.8^8)=224.31).

Для расчёта минимального необходимого количества купонов можно воспользоваться Excel. Свои расчёты я привел на рисунке ниже:

Расчёт необходимого количества купонов

Мы поняли, что нам нужно минимум 15 параллельных запросов, чтобы решить лабораторную. Я же не буду ограничиваться пятнадцатью, поэтому удалю применённый промокод из корзины, добавлю сразу 25 вкладок в группу и повторю процедуру:

Повтор процедуры с большим количеством запросов

Смотрим на результат, обновив страницу чекаута:

Результат применения 16 промокодов

Как мы видим по таблице выше, нам удалось использовать один промокод сразу 16 раз, снизив цену с 1337$ до 37$. Осталось лишь купить жакет, нажав на клавишу Place order.

Лабораторная решена!


Нейтрализация race condition

Раз мы плавно перешли к вопросам нейтрализации атаки, давайте изучим возможные пути устранения race condition:

  1. Правильная синхронизация и механизмы блокировки: как говорилосб выше, включает использование программных конструкций, таких как блокировки, семафоры или мьютексы, чтобы только один процесс или поток получал доступ к общему ресурсу в определенный момент времени;
  2. Практики потокобезопасного программирования: разработчики должны использовать техники потокобезопасного программирования, такие как неизменяемые структуры данных или использование атомарных операций, чтобы минимизировать вероятность возникновения race condition;
  3. Надежное управление сеансами: важно использовать надежные техники управления сеансами, такие как использование уникальных идентификаторов сеансов, истечение сеансов после определенного времени и регулярная повторная аутентификация, чтобы предотвратить race condition, связанный с данными сеанса.
  4. Тщательное обращение с параллельным доступом: разработчики должны обращать особое внимание на сценарии параллельного доступа, убедившись, что чувствительные операции выполняются атомарно или соответствующим образом синхронизируются, чтобы избежать повреждения данных или уязвимостей безопасности.


Заключение

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

До новых встреч!

До новых встреч!


Report Page