Приветствую! Разберём сегодня подробно лабу Portswigger с уязвимостью "HTTP request smuggling (CL.TE.)"
Наша цель в данном кейсе - получить страницу 404 внутреннего сервера.
Приятного чтения!
Немного важной теории
HTTP request smuggling — это уязвимость в системе безопасности протокола HTTP, которая использует несоответствие между интерпретацией длины содержимого и заголовков кодирования передачи между реализациями HTTP-серверов в цепочке HTTP-прокси-серверов.
Уязвимость позволяет злоумышленнику вводить вредоносный HTTP-запрос на веб-сервер, минуя внутренние меры безопасности. Это может позволить ему:
- получить доступ к защищенным ресурсам, таким как консоли администратора;
- получить доступ к конфиденциальным данным;
- взломать сеансы веб-пользователей;
- запускать атаки межсайтового скриптинга (XSS) без каких-либо действий со стороны пользователя;
- взломать учётные данные.
Современная архитектура веб-приложений выглядит следующим образом:
пользователи отправляют запрос на внешний сервер (сервер-балансировщик для запросов), который распределяет запросы на внутренние сервера. Так веб-сервер представляет из себя цепочку серверов, где внешний сервер играет роль распределительного шлюза, а внутренние выполняют сам запрос пользователя или же логику приложения.
Внешние и внутренние сервера по своему умеют разделять начало и конец HTTP-запроса. Именно этим и пользуются злоумышленники, проходя барьер внешнего сервера с легитимным запросом для него и вредоносным для внутреннего.
С помощью чего сервера понимают где начало и конец запроса:
Есть два типа заголовка, показывающего размер запроса и указывающего, где он будет окончен. Content-Length: - указывает длину всего тела запроса. Если указать после двоеточия - это значение 35, то сервер примет 35 байт. Остальное, что не попало в данный промежуток - отсеется.
Transfer-Encoding: - позволяет порционно передать тело запроса. Для этого нужно указать сколько байт должно быть передано перед каждой порцией данных. Вот небольшой пример:
Значение заголовка chunked позволяет разбить запросы по частям. Значение 0 указывает на то, что запрос передан полностью и больше порций данных серверу ожидать не нужно.
Важно понимать, что Content-Length и Transfer-Encoding обычно взаимоисключающие заголовки.
Внешние и внутренние сервера могут поддерживать один из этих заголовков или же оба будут поддерживать только Transfer-Encoding. По этому принципу и создана классификация контрабанды HTTP-запросов.
CL. TE: внешний сервер использует Content-Length, а внутренний Transfer-Encoding.
TE. CL: внешний вервер использует Transfer-Encoding, а внутренний Content-Length.
TE. TE: внешний и внутренний сервер используют Transfer-Encoding.
В данной задаче мы поэксплуатируем CL. TE уязвимость контрабанды HTTP. Чуть позже мы разберём и другие классы уязвимости.
Как выявить наличие CL.TE
При отправке запроса на внешний сервер с поддержкой Content-Length
Content-Length:7
Transfer-Encoding: chunked
4\r\n (3 байта перенос)
test\r\n (4 байта данные) X\r\n
Дойдёт до сервера только эта часть тела запроса (4\r\n test). Т.к. мы указали 7 байт, то и придёт 7 байт на сервер. Так мы узнаём, что внешний сервер поддерживает CL.
Однако внутренний сервер с поддержкой Transfer-Encoding (TE) получит выделенную часть 3\r\n test, но будет ждать остальную порцию (\r\n X\r\n) до таймаута. Это произойдёт т.к. мы не указали 0 (конец тела запроса).
Таким образом, при вызове таймаута мы можем быть уверены, что цель уязвима к CL.TE .
Практическая задача
Для начала получим легитимный запрос от цели при помощи инструмента Burp Suit.
Для реализации атаки нам нужен HTTP/1. Проверим поддерживает ли приложение такую версию протокола:
Итак, приложение поддерживает такую версию HTTP. Такая уязвимость часто встречается именно в этой версии HTTP, но не исключена во второй.
Теперь поменяем GET-запрос на POST и добавим заголовки Content-Length: 35 и Transfer-Encoding: chunked.
После заголовка Transfer-Encoding делаем пропуск строки и пишем 0, что указывает на то, что передача тела запроса была закончена для внутреннего сервера.
После чего у нас идёт второй запрос GET, который направлен на внутренний сервер для получения страницы 404.
Т.к. внешний сервер поддерживает заголовок Content-Length и мы указали размер тела запроса 35, то внешнему серверу будет передано 35 байт ( всё, что ниже нуля).
Таким образом, сделаем первый запрос - код 200. Сервер получит в теле GET-запрос.
Второй запрос выдаст нам код 404 - страницу, которую мы должны были получить. Т.к для внутреннего сервера chunked говорит о том, что запрос окончен и следующий GET-запрос для него выглядит как отдельный.
Заголовок X-Ignore: X использован для того, чтобы сервер проигнорировал некоторые аспекты обработки второго запроса. Такой заголовок даст больше шансов на успешную эксплуатацию уязвимости.
Конечно, запрос страницы 404 выглядит совсем безобидно, но мы можем такое провернуть с более важными объектами сайта. Например: если страница авторизации админа заблочена для простых пользователей, то можно таким образом обойти такой блок.
Для наглядности я нарисовал схему работы уязвимости в данном кейсе:
Выводы
Такой метод часто применим для обхода внутренних прокси на веб-сервере или WAF. Тут важно учитывать какие заголовки принимает внешний и внутренний сервера. От этого зависит структура вредоносного запроса.
Надеюсь, что вам было интересно также, как и мне узнать новое про уязвимости в веб-приложениях.
Спасибо за внимание! Всех благ!