August 15, 2023

Race Condition

В Burp была добавлена функция "Repeater send group in parallel". Когда вы выбираете эту опцию для группы вкладок, Repeater отправляет запросы сразу из всех вкладок группы. Это можно использовать, чтобы проводить атаку race conditions без применения Turbo Intruder.

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

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

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

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

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

Попробую показать принцип работы данной уязвимости на примерах лабораторных и репортов баг-баунти.

Lab: Limit overrun race conditions

Поиск точки входа

Заходим в аккаунт и покупаем самый дешёвый товар, используя промокод для скидки. Таким образом можно будет понять как происходит процесс покупки товара:
Определим все конечные точки, которые позволяют взаимодействовать с корзиной, например, POST /cart добавляет товары в корзину:
POST /cart/coupon запрос применяет код скидки:
Если применить промокод больше одного раза, мы получаем ответ Coupon already applied:
Проверим, что в корзине есть товар, а затем отправляем GET /cart запрос в Burp Repeater и попробуем посмотреть корзину как с файлом cookie:
Так и без него:

Из этого можно сделать вывод, что:

  1. Состояние корзины сохраняется на стороне сервера в нашем сеансе.
  2. Операции с корзиной производятся с помощью нашего сессионного идентификатора.

Это позволяет предположить что в момент когда мы впервые применяем промокод, и обновлением базы данных, чтобы отобразить что промокод применен, может возникнуть Race Condition.

Создание новой группы вкладок (понадобится дальше):

Нажимаем кнопку добавления группы, и выбираем Create tab group:
Добавляем название группы, добавляем запросы в группу и выбираем цвет группы, жмём создать, чтобы группа появилась:

Сравнение поведения

  1. Удаляем из корзины купон.
  2. Отправляем запрос на применение кода скидки ( POST /cart/coupon) в Repeater 20 раз. Совет: Вы можете сделать это быстро, используя Ctrl/Cmd + R.
  3. В Repeater добавляем 20 этих вкладок в новую группу.
Теперь отправляем группу запросов последовательно, используя отдельные соединения, чтобы уменьшить вероятность помех:

Обратим внимание, что первый ответ подтверждает, что скидка была успешно применена, но остальные ответы отвечают что купон уже применен:

Удаляем промокод из корзины и отправляем группу запросов, но на этот раз параллельно, чтобы попробовать применить промокод несколько раз одновременно:
Посмотрев ответы, можно заметить что в несколько запросах наш промокод применился, и скидка 20% применилась несколько раз:

Exploit

Удаляем примененные коды и произвольный товар из корзины и добавляем необходимый по задания товар. Отправляем группу запросов на POST /cart/coupon параллельно, обновляем корзину и видим что скидка применилась достаточное количество раз и товар можно заказать дешевле его стоимости:

Lab: Bypassing rate limits via race conditions

Поиск точки входа

Проверим функциональность входа в систему, отправив неверный пароль для своей учетной записи. Можно заметить, что если ввести неправильный пароль более трех раз, нас временно заблокируют и дальнейшие попыткив хода в систему для той же учетной записи:
Попробуем войти в систему, используя случайное имя пользователя, и можно убедиться что мы снова видим Invalid username or password. Это указывает на то что ограничение применяется для каждого имени пользователя, а не для каждого сеанса:

Из этого всего можно понять, что количество неудачных попыток для каждого имени должно храниться на стороне сервера и в момент когда увеличивается счетчик неудачных попыток, может возникнуть Race Condition.

Сравнение поведения

Найдем в истории POST /login, содержащий неудачную попытку входа в нашу учетную запись:

Отправляем этот запрос Burp Repeater 20 раз. Добавляем все эти 20 запросов в группу и отправляем их последовательно, используя отдельные соединения:В Repeater добавьте все 20 этих вкладок в новую группу.

После отправки мы можем увидеть что после двух неудачных попыток входа нас временно заблокировали, как и должно быть:
Отправляем группу запросов еще раз, но уже параллельно. В ответах можно заметить что после трех неверных попыток входа все равно появляется Invalid username or password, это значит что запросы отправляются достаточно быстро:

Exploit

В Repeater выделем значение параметра password в POST /login запросе, отправляем запрос в Turbo Intruder. Меняем параметр username на carlos и в мен. скриптов выбираем шаблон examples/race-single-packet-attack.py.

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

    def queueRequests(target, wordlists):
    
        # as the target supports HTTP/2, use engine=Engine.BURP2 and concurrentConnections=1 for a single-packet attack
        engine = RequestEngine(endpoint=target.endpoint,
                               concurrentConnections=1,
                               engine=Engine.BURP2
                               )
        
        # assign the list of candidate passwords from your clipboard
        passwords = wordlists.clipboard
        
        # queue a login request using each password from the wordlist
        # the 'gate' argument withholds the final part of each request until engine.openGate() is invoked
        for password in passwords:
            engine.queue(target.req, password, gate='1')
        
        # once every request has been queued
        # invoke engine.openGate() to send all requests in the given gate simultaneously
        engine.openGate('1')
    
    
    def handleResponse(req, interesting):
        table.add(req)
В итоге узнаем пароль от учетной записи и выполняем задачу:

Bug Bounty report one

В данном случае уязвимость возникает во время лайка записи в блоге.

Переходим в блог и ставим лайк на запись:
Перехватываем запрос через Burp и отправляем его в Turbo Intruder:
Выбираем в Turbo Intruder скрипт examples/race.py и выполняем атаку:
В результате атаки можно заметить что лайки на запись проставились несколько раз:

Bug Bounty report two

Уязвимость возникает в функциональности импортирования форм, а точнее при клонировании существующих форм.

Перехватываем запрос клонирования и создания новой формы:
Отправляем запрос в Turbo Intruder и также выбираем скрипт examples/race.py и выполняем атаку:
В результате несколько запросов ответили статусом 200 и создалось форм больше чем допустимо: