17
При атаке на веб‑приложение иногда нужно многократно выполнить цепочку каких‑то действий. Самый яркий пример — перебор паролей или второго фактора аутентификации либо многократное использование ресурсов. Для этого есть разные инструменты. Какой из них выбрать, если, например, нам потребуется тысячу раз подряд выполнить пять запросов через HTTP, поддерживая одну и ту же сессию? Я выберу Burp Suite, и вот почему.
Для автоматизации многошаговых атак отлично подходят скриптовые языки, но не всем и не всегда удобно тратить лишний час на написание и отладку кода, когда рядом лежит готовое решение, требующее минимальной настройки. Что не менее важно, для достижения высокой скорости отправки и обработки запросов, а также для параллельного исполнения нужно знать правильные стеки, которые не тормозят параллельное исполнение и не выполняют лишних действий, усложняющих исполнение.
Если тебе трудно реализовать подобные задачи при помощи языков программирования или ты считаешь, что на это уйдет много времени, можно воспользоваться Burp Suite. Этот инструмент предоставляет сразу несколько способов автоматизации:
- макросы;
- плагин Stepper от сторонних разработчиков;
- плагин Turbo Intruder от создателей Burp Suite.
Мы поговорим о том, что дают эти подходы, об их возможностях и ограничениях.
Рассматривать работу этих трех подходов мы будем на примере задачи, которую приходится решать очень часто: перебор четырехзначных одноразовых паролей, которые используются... Да почти везде. Кстати, на bug bounty за эксплуатацию таких уязвимостей можно получить немалое вознаграждение.
В качестве испытательного стенда прекрасно подойдет задание с образовательного ресурса PortSwigger Academy, требующее от нас выполнения сотен многошаговых повторяющихся действий.
Описание задачи¶
Вот как сформулирована тестовая задача на сайте PortSwigger Academy:
Двухфакторная аутентификация в этой лаборатории уязвима перед брутфорсом. Ты уже получил имя пользователя и пароль, но не имеешь доступа к верификационному коду пользователя 2FA. Чтобы решить эту проблему, перебором найди код 2FA и получи доступ к странице аккаунта Карлоса.
Учетные данные жертвы:
carlos:montoya
.
Особенность этой задачи состоит в том, что здесь недостаточно просто перебрать код одноразового пароля (One Time Password — далее OTP) с существующей сессией, потому что после двух неправильных попыток приложение перестает считать сессию валидной. Для решения задания нам предстоит выполнять предаутентификацию при помощи учетных данных, а после этого попытаться предсказать OTP-код.
Подробнее о задании¶
Нам дана страница аутентификации, которая выглядит следующим образом.
[
Страница аутентификации
При вводе учетных данных приложение отправляет следующий запрос на сервер:
POST /login HTTP/1.1 Host: ace61ff51f4557d880dbab96004f009d.web-security-academy.net Cookie: session=rcnBF1vzBD00ZSjcoswRzttRrEPIQNj2 Content-Type: application/x-www-form-urlencoded Content-Length: 70 csrf=AxCZcrNQ1Y7x8xTI9odKun0alLM34a9a&username=carlos&password=montoya
Если мы введем учетные данные корректно, на экране появляется следующая страница ввода OTP-кода.
[
Страница ввода OTP
После ввода случайного OTP-кода приложение отправит следующий запрос:
POST /login2 HTTP/1.1 Host: ace61ff51f4557d880dbab96004f009d.web-security-academy.net Cookie: session=2gt4P1gFqzyxZJIonAlFv9czYetD5pm0 Content-Type: application/x-www-form-urlencoded Content-Length: 51 csrf=W9Nei8NhTXl5usVKeynuZ3kbjRHaVjW7&mfa-code=1234
Если мы сможем угадать OTP-код, мы решим задание. Шанс угадать, по сути, не так уж и мал: 1 к 10 000. С учетом того что количество попыток у нас не ограничено, пусть и требует дополнительных действий, результат гарантирован на 100%.
Что важно знать, прежде чем мы приступим к решению этой задачи?
- Приложение использует сессионный идентификатор, который мы получаем при входе на сайт. Он изменяется после первого этапа аутентификации при помощи корректных учетных данных.
- После аутентификации у нас есть только две попытки ввода OTP-кода. После двух неудачных попыток наша сессия инвалидируется и приходится начинать весь процесс с начала.
- Приложение использует CSRF-токены, которые меняются при каждом запросе. Их необходимо подхватывать и подменять для каждого нашего POST-запроса.
Осталось автоматизировать процесс получения сессии, ввода первичных учетных данных, подхвата CSRF-токенов и попыток предсказания OTP-кода. Приступим!
Способы решения¶
Способ 1. Использование макросов¶
Макросы в Burp Suite — это механизм для автоматизации предопределенных последовательностей действий. Ты можешь использовать макросы внутри правил обработки сеансов для решения различных задач. Научиться ими пользоваться несложно, особенно на примере нашей задачи.
- С запущенным Burp Suite входим в систему как Карлос (учетные данные указаны в задаче) и собираем пакеты HTTP-аутентификации вплоть до проверки 2FA. Они пригодятся нам для настройки макроса.
- Поскольку сессия постоянно изменятся и инвалидируется при неуспешных попытках ввода кода, нам понадобится каким‑то образом ее поддерживать. Для этого мы будем использовать возможности обработки сеансов Burp. Перейдем к их настройке.
- В меню Burp перейди в Project Options → Sessions. На панели Session Handling Rules нажми кнопку Add. Откроется диалог Session handling rule editor. Здесь мы будем добавлять правила поддержания сессии и ее возобновления.
- В окне диалога во вкладке Scope в разделе URL Scope выберем значение Include All URLs, чтобы не утруждать себя тонкой настройкой. Сессия будет поддерживаться для любых URL.
- Вернемся на вкладку Details и начнем создавать макрос. В разделе Rule Actions нажмем кнопку Add и в открывшемся окошке выберем Run a macro. Здесь и начнется процесс создания макроса, который будет повторяться при отправке каждого нашего запроса.
- В открывшемся окне настройки макроса выберем запросы для автоматизации (это будут запросы первичного входа, получения идентификатора сессии и токена CSRF для отправки первой формы). Для этого в разделе Select macro нажимаем кнопку Add. Здесь мы выбираем пакет входа на страницу
/login
(GET-запрос), пакет отправки учетных данных с POST-запросом к странице/login
и пакет входа на страницу/login2
, где Burp подхватит токен CSRF для ввода OTP-кода. - В этой же вкладке в нижнем правом углу можно протестировать созданный нами макрос, нажав кнопку Test macro. Если мы нажмем эту кнопку, то увидим, что Burp выполнит три запроса подряд, подхватит выданные ему Cookie-данные и получит токен CSRF, который надо было использовать для отправки формы. Уже сейчас нам остается только автоматизировать ввод OTP-кода, и дело сделано.
- Нажимаем ОK и закрываем диалоговые окна. Теперь при отправке каждого запроса Burp будет выполнять данный макрос для получения новой сессии, а потом подставлять значение сессии и значение токена CSRF в исходящий запрос для их обновления.
- Теперь возьмем запрос с отправкой OTP-кода (POST-запрос к странице
/login2
) и отправим его в Intruder для автоматизации. - Во вкладке Intruder оставляем для нагрузки только поле
mfa-code
(вот так:mfa-code=§1234§
) и переходим во вкладку Payloads. Здесь выбираем для Payload Type значение Numbers и указываем цифры, которые мы хотим генерировать:From: 0, To: 9999, Step: 1, Min integer digits: 4
. - Переходим во вкладку Options и настраиваем Number of threads в значение 1 (это нужно потому, что Burp не умеет одновременно поддерживать идентификаторы сессии для двух и более потоков, только для одного).
- После этого запускаем Intruder и включаем режим «ждуна».
На угадывание кода Intruder понадобилось порядка десяти минут (мой код был 0643
). Это супердолго! Даже не десятая часть всех попыток. Почему нельзя быстрее? Потому что Session Handling не может поддерживать сессию у двух потоков одновременно.
Как работает это решение, можно посмотреть на следующем видеоролике.
Подведем итог того, что нам дают макросы и что они умеют.
Возможности:
- удобно поддерживать сеанс сессии, постоянно захватывая идентификатор сессии самостоятельно, даже если он обновляется;
- подхватываются не только значения сессии, но и все значения, которые используются для выполнения нового запроса: переменные, токены CSRF и прочее.
Проблемы:
- работа только в одном потоке;
- если нужны «перекрестные атаки», использующие сессию двух пользователей одновременно, то реализовать это невозможно, так как поддерживается только одна сессия.
- достаточно неочевидная настройка, очень легко запутаться в многочисленных меню.
Способ 2. Использование плагина Stepper¶
Плагин Stepper — это бесплатный плагин, доступный в модуле Burp Suite Extender, который помогает автоматизировать последовательности действий. Найти его можно на GitHub.
Разработчики рассказывают о Stepper следующее:
Stepper разработан как естественное развитие инструмента Repeater из Burp Suite и предоставляет возможность создавать последовательности шагов и определять регулярные выражения для извлечения значений из ответов, которые затем могут быть использованы в последующих шагах.
Установим его и воспользуемся им для решения нашей задачи.
warning¶
Очень важно! Если ты делаешь это после предыдущего эксперимента, отключи созданные ранее правила обработки сессий и удали макросы!
Модуль Stepper позволяет выбрать ряд запросов и объявить в каждом из них переменные, которые запрос получает от предыдущего шага. Затем он подставляет их, а также переменные, полученные из тела ответа при помощи регулярных выражений, и передает следующему запросу. Такая простая и понятная связка.
-
Во вкладке Proxy выбираем три запроса, которые нам нужны для получения сессии, первичной аутентификации и извлечения CSRF-токена. Вот они:
GET /login
,POST /login
,GET /login2
. Выделив данные запросы, щелкаем на них правой кнопкой мыши и в подразделе Extensions нажимаем кнопку Add 3 items to Stepper → New Sequence. Нам предложат выбрать для этой последовательности название. Я назову ееevil
.Важно: следи, чтобы пакеты были перенесены в правильном порядке! Для этого нужно, чтобы первые пакеты были выше последних при сортировке во вкладке Proxy (это решается сортировкой по номерам пакетов от меньших к большим).
-
Переходим в модуль Stepper, который появился во вкладках с остальными модулями.
-
Здесь мы увидим нашу последовательность и три пакета, пронумерованные от 1 до 3. Каждый из пакетов мы можем повторно отправить, нажав на кнопку Execute Step, чтобы получить пример тела ответа, и протестировать каждый шаг.
-
Выполним первый шаг, нажав кнопку Execute Step. Создадим первую переменную для хранения и передачи идентификатора сессии, нажав на кнопку Add Variable в нижнем правом углу модуля. Назовем переменную
session
и добавим ей условие поиска в поле Condition:session=([\d\w]+)
. Таким образом у нас появится первая переменная сессии, которую мы будем пробрасывать по остальным запросам и использовать повторно. -
Также добавим вторую переменную токена CSRF, которую пробросим на следующий запрос отправки учетных данных. Нажмем кнопку Add Variable, назовем переменную
csrf
и добавим условие нахождения ее в теле ответа в поле Condition со следующим значением:name="csrf" value="([\w\d]+)"
.Вот что получилось у меня после выполнения этих шагов.
[
Первичная настройка Stepper
-
Теперь можно перейти к следующему запросу отправки учетных данных и использовать в нем переменные
session
иcsrf
. Для этого переходим к следующему шагу (Step 2) и вместо существующих там значений сессии и CSRF-токена подставим обращение к переменным в следующем виде:$VAR:session$
и$VAR:csrf$
. Получится что‑то вроде этого:POST /login HTTP/1.1 Host: ac3f1f861fe209fb80374867009900fe.web-security-academy.net Cookie: session=$VAR:session$ Content-Type: application/x-www-form-urlencoded Content-Length: 70 csrf=$VAR:csrf$&username=carlos&password=montoya
7. Выполним этот второй шаг нажатием на кнопку Execute Step и получим ответ, где нас попытаются перенаправить на страницу/login2
и выдадут нам новый идентификатор сессии, который нам снова нужно захватить при помощи регулярных выражений и передать на следующий шаг № 3. Создаем такую же переменнуюsession
как и в пункте 4, и переходим к шагу № 3. -
На шаге № 3 не забываем снова изменить значение сессии на переменную
$VAR:session$
и выполняем запрос, так как нам просто нужно получить токен CSRF для последнего шага. После выполнения запроса снова добавляем парсинг токена CSRF в виде переменнойcsrf
, как мы это делали в пункте 5 ранее. -
Теперь мы можем попробовать выполнить всю последовательность и проверить ее работоспособность. Нажмем на кнопку Execute Sequence в самом низу окна модуля. Видим, что последовательность выполнилась корректно и на последнем шаге мы получаем ответ с предложением ввести OTP-код.
Теперь наша задача — запустить данную последовательность 10 тысяч раз. Для этого переносим POST-запрос
/login2
из вкладки Proxy в Intruder. -
В панели Intruder нам нужно убрать символы подстановок
§
в полях сессии и CSRF-токена и оставить подстановку только в полеmfa-code
вот так:mfa-code=§1337§
. -
Для того чтобы шаги нашей последовательности из модуля Stepper выполнялись для каждого запроса из Intruder, добавим в заголовки запроса следующее:
X-Stepper-Execute-Before: [Название твоей последовательности]
. -
Также подставим имена наших переменных
$VAR:session$
и$VAR:csrf$
в пакет Intruder, только исправим их на$VAR:[Название твоей последовательности]:session$
и$VAR:[Здесь тоже]:csrf$
. В Intruder у меня получился следующий пакет запроса:POST /login2 HTTP/1.1 Host: ac311f2c1f2abcbd807689da0068009a.web-security-academy.net Cookie: session=$VAR:evil:session$ Content-Type: application/x-www-form-urlencoded X-Stepper-Execute-Before: evil Content-Length: 51 csrf=$VAR:evil:csrf$&mfa-code=§1337§
В данном примере имя моей последовательности —
evil
.Теперь перед каждым запросом из Intruder будет выполняться последовательность ранее подготовленных запросов, которые передадут в пакет полученные значения сессии и CSRF-токена.
-
Последний шаг, настраиваем нагрузку во вкладке Payloads, точно так же, как мы это делали в предыдущем разделе. Выбираем в Payload Type значение Numbers и указываем цифры, которые мы хотим генерировать:
From: 0, To: 9999, Step: 1, Min integer digits: 4
. -
Запускаем нашу атаку! Отслеживать отправленные пакеты можно в появившейся вкладке Logger или при помощи модуля Logger++.
В этот раз мне повезло больше, мой код был 0261
. Что важного можно заметить? В отличие от предыдущего варианта мы не ограничены одним потоком и создали пять потоков, а самые умные могли отключить галочку Set Connection: close в опциях нагрузки и удалить этот заголовок из пакетов в Stepper и Intruder, чтобы увеличить скорость работы.
Сделаем выводы.
Возможности:
- из‑за того что модуль Stepper поддерживает сессии, передавая значение сессии и токена от запроса к запросу, мы можем использовать многопоточное исполнение запросов и наши переменные не будут конфликтовать в потоках;
- нам становятся доступны перекрестные атаки, когда мы можем запускать работу нескольких последовательностей параллельно;
- нативно понятная настройка переноса состояний от запроса к запросу и легко добавляемый заголовок
X-Stepper-Execute-Before:
, который запускает Stepper для любого модуля.
Проблемы:
- на самом деле Stepper не позволяет использовать так много потоков, как хотелось бы. Около трех потоков действительно успевают работать вместе, но из‑за особенностей кода модуля большее их количество только тормозит выполнение;
- приходится вручную настраивать переменные для каждого запроса, что может выглядеть изнуряюще скучно.
Данный плагин подходит скорее для использования его с модулем Repeater, о чем разработчики предупреждали нас в приветственном сообщении.
Способ 3. Использование плагина Turbo Intruder¶
Turbo Intruder — один из самых мощных инструментов Burp Suite, и его должен освоить каждый уважающий себя пользователь Burp. Его также можно скачать с GitHub.
Turbo Intruder представляет собой расширение Burp Suite для отправки большого количества запросов HTTP и анализа результатов. Оно призвано дополнить Burp Intruder, обрабатывая атаки, требующие исключительной скорости, продолжительности или сложности. Этот модуль отличают следующие особенности.
- Быстрота: Turbo Intruder использует стек HTTP, созданный вручную с нуля с учетом скорости. В результате на многих целях он может серьезно обогнать даже модные асинхронные скрипты на Go (на самом деле стек можно выбрать, и большинство из них будут тебе знакомы).
- Масштабируемость: Turbo Intruder может достичь плоского использования памяти, что позволяет проводить надежные многодневные атаки. Его также можно запускать в headless-окружении через командную строку.
- Гибкость: атаки конфигурируются с помощью Python. Это позволяет выполнять сложные требования, например подписанные запросы и многоступенчатые последовательности атак. Кроме того, пользовательский стек HTTP позволяет обрабатывать неправильно сформированные запросы, которые ломают другие библиотеки.
- Удобство: скучные результаты могут быть автоматически отфильтрованы с помощью усовершенствованного алгоритма дифов, адаптированного из Backslash Powered Scanner. Это означает, что ты можешь запустить атаку и получить полезные результаты в два клика.
Для использования Turbo Intruder необходимо знание основ Python. Тем не менее все, что нам нужно для начала, — это установить Turbo Intruder из модуля Extender.
После установки сразу же перейдем к решению задачи.
- Выберем самый первый пакет в последовательности во вкладке Proxy (пакет
GET /login
) и нажмем на него правой кнопкой мыши. А дальше выберем пункт Extensions → Send to turbo intruder. - В открывшейся панели Turbo Intruder появится запрос и примеры скриптов, которые можно выбрать для использования и модификации. В данном случае все, что нам нужно для победы, — это написать скрипт, который позволит решить задание. Ниже я приведу свой пример кода и объясню логику скрипта (сделай мне скидку на качество, вспомнив о том, что пентестеры не умеют кодить):
import re import time # Регулярки для вытаскивания идентификаторов сессии и CSRF-токенов re_csrf = 'name="csrf" value="([\w\d]+)"' re_session = 'session=([\d\w]+)' iterable = 0 def queueRequests(target, wordlists): global engine # Включаем один запрос на один коннект, чтобы не нарушать логику выполнения, коннекты в соответствии с тем, что выдержит приложение. # Все эти значения придется калибровать от сервера к серверу. Сервер задачи не очень хорошо держит высокую нагрузку, поэтому ограничимся пятью параллельными соединениями engine = RequestEngine(endpoint='https://ac051f441e762a3780359cb6002300a2.web-security-academy.net:443',concurrentConnections=5,requestsPerConnection=1) # Запускаем первые запросы, которые спровоцируют запуск последующих запросов. # Делаем задержку в одну секунду, чтобы потоки не исполнялись синхронно, а чередовались. for x in xrange(1,6): print '1. GET /login Request' engine.queue(target.req,'') time.sleep(1) def handleResponse(req, interesting): global engine global iterable if 'Location: /my-account' in req.response: # Если мы в ответе получили данный заголовок, значит, мы победили table.add(req) print 'You Win!' return None if 'Incorrect security code' in req.response: # Если мы получили в ответе сообщение о неправильно введенном коде, значит, мы использовали одну попытку, и тогда мы запускаем новую итерацию запросов table.add(req) print '1. GET /login Request' engine.queue(target.req,'') return None if 'Please enter your 4-digit security code' in req.response: # Если в ответе мы получаем предложение ввести ОТР, то отправляем запрос с попыткой ввода ОТР match_csrf = re.search(re_csrf, req.response) match_session = re.search(re_session, req.getRequest()) req = '''POST /login2 HTTP/1.1\r\nHost: ac051f441e762a3780359cb6002300a2.web-security-academy.net\r\nCookie: session=%s\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 51\r\n\r\ncsrf=%s&mfa-code=%s''' print '4. POST /login2 Request' engine.queue(req, [match_session.group(1),match_csrf.group(1),str(iterable).zfill(4)]) iterable += 1 print 'Iterable: ' + str(iterable) return None if 'Location: /login2' in req.response: # Если в ответе мы получаем сообщение о переходе на страницу /login2, значит, мы ранее ввели корректные креды и теперь получаем новый идентификатор сессии и переходим на страницу для взятия CSRF для запроса с OTP match_session = re.search(re_session, req.response) req = '''GET /login2 HTTP/1.1\r\nHost: ac051f441e762a3780359cb6002300a2.web-security-academy.net\r\nCookie: session=%s\r\n\r\n''' print '3. GET /login2 Request' engine.queue(req, match_session.group(1)) return None if '<form class=login-form method=POST action=/login>' in req.response: # Если первый запрос был выполнен успешно, значит, мы получим страницу с предложением ввода логина и пароля, вводим логин и пароль match_session = re.search(re_session, req.response) match_csrf = re.search(re_csrf, req.response) req = '''POST /login HTTP/1.1\r\nHost: ac051f441e762a3780359cb6002300a2.web-security-academy.net\r\nCookie: session=%s\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 70\r\n\r\ncsrf=%s&username=carlos&password=montoya''' print '2. POST /login Request' engine.queue(req, [match_session.group(1),match_csrf.group(1)]) return None
Так как в Turbo Intruder нет возможности удобно поддерживать сессию между запросами, приходится делать это руками, создавая новые запросы на основе идентификаторов сессии, полученных из предыдущих запросов.
В первом приближении логика работы скрипта такова. Я запускаю пять первичных запросов, которые выполняются в пяти параллельных соединениях. Далее ответ на каждый запрос обрабатывается. Обработчик ответов ставит условие на то, что получил ожидаемый ответ, и дальше выполняет следующий по логике запрос. Например, после получения ответа с приглашением ввода пароля выполняется запрос ввода логина и пароля и так далее.
При помощи данного скрипта мне удалось выполнить 400 попыток (~1500 запросов) за 30 секунд и решить задание примерно в 20 раз быстрее, чем в прошлых примерах. Если быть честным, то можно было потратить чуть больше времени на калибровку параметров concurrentConnections
, requestsPerConnection
и pipeline
и решить задачу еще быстрее, но мне хватило и этого.
Подведем итоги для этого примера.
Возможности:
- Turbo Intruder может выжать из приложения максимальную скорость;
- поскольку мы пишем код на Python, можно заложить в инструмент логику почти любой сложности;
- крайне удобно фильтровать выполненные запросы в таблице результатов, а также можно задавать свои поля к запросам для сортировки и фильтрации.
Проблемы:
- нужно писать код, и это мало отличается от написания скриптов с нуля, хоть и дает большое преимущество в использовании подготовленных абстракций для многопоточного и параллельного выполнения запросов;
- нет документации к инструменту, кроме ряда примеров, которых тебе должно хватить;
- в наиболее быстрых движках запросов для Intruder запросы и ответы не логируются в модули Logger или Logger++, что не позволяет удобно просматривать происходящее в сети. Приходится использовать способы отладки, встроенные в сам Turbo Intruder и его абстракции.
Заключение¶
Лично мне импонирует инструмент Turbo Intruder, но для новичков модуль Stepper или встроенные макросы могут оказаться проще в использовании. Тем не менее для реальных задач макросы и Stepper могут не подойти из‑за своей медлительности.
Также стоит сказать, что в каждом примере я оставил несколько путей улучшения скорости работы или увеличения количества попыток примерно в два раза при незначительном увеличении количества запросов. Если ты придумаешь улучшения, поделись ими в комментариях. Кроме того, будет здорово, если ты расскажешь нам о других вариантах решения этой задачи.