
HTTP Request Smuggling или контрабанда HTTP-запросов — тип уязвимости, который возникает из-за несоответствий в обработке HTTP-запросов между фронтендом и бэкендом. Каким образом различия в интерпретации заголовков позволяют атакующим использовать эту уязвимость? Как HTTP Request Smuggling можно использован в сочетании с Web Cache Poisoning? И на что обратить внимание, чтобы предотвратить подобные атаки? Разберем вместе на примере лабораторных работ с PortSwigger.
Введение в HTTP и его эволюция
Протокол HTTP (HyperText Transfer Protocol) — основа современного веба. Первая версия HTTP/0.9 была крайне простой и поддерживала только GET-запросы. С появлением HTTP/1.0 клиенты получили возможность отправлять более сложные запросы. Но для каждого из них требовалось устанавливать новое TCP-соединение, что создавало значительную нагрузку на сеть, особенно при загрузке страниц с множеством ресурсов — изображения, скрипты и стили.
В HTTP/1.1 появилась поддержка постоянных соединений (Persistent Connections). Это позволило использовать одно TCP-соединение для нескольких запросов и ответов, что значительно улучшило производительность. Также появилась возможность конвейерной обработки запросов (HTTP Pipelining), но из-за проблем с прокси-серверами и блокировкой очередей эта функция не получила широкого распространения.
С развитием протокола добавлялись новые функции, которые улучшили производительность, но также привели к появлению новых уязвимостей — например, HTTP Request Smuggling. Эта уязвимость возникает из-за различий в обработке запросов между разными компонентами системы — балансировщиками нагрузки и серверами приложений. Усложнение процесса обработки запросов, включая использование заголовков Content-Length и Transfer-Encoding, увеличило риск их неправильной интерпретации.
Современные версии протокола, такие как HTTP/2 и HTTP/3, продолжают развиваться, предлагая новые механизмы для повышения производительности и безопасности. Однако, несмотря на эти улучшения, уязвимости, связанные с неправильной обработкой запросов, остаются актуальными из-за сложности взаимодействия между разными компонентами системы.
Основы HTTP-запросов и особенности их обработки
HTTP-сообщения — это основной способ обмена данными между клиентом и сервером в рамках протокола HTTP. Они делятся на два типа:
запросы, которые инициирует клиент для выполнения действий на сервере;
ответы, которые сервер возвращает в результате обработки запроса.

Структура HTTP-сообщений — как запросов, так и ответов — состоит из нескольких частей:
● Строка запроса (Start Line). В запросе она содержит метод — например, GET, POST — путь к ресурсу и версию HTTP. В ответе указывается версия протокола, код состояния и его текстовое описание.
● Заголовки (Headers). Это набор пар «ключ-значение» которые передают метаданные о запросе или ответе. Например, заголовки могут указывать: тип содержимого (Content-Type), размер данных (Content-Length) или кэширование (Cache-Control).
● Пустая строка (Empty Line). Она разделяет заголовки и тело сообщения, сигнализируя о завершении метаданных.
● Тело запроса (Body). Необязательная часть, которая содержит данные, связанные с запросом или ответом. Например, в POST-запросе тело может включать данные формы, а в ответе — содержимое запрашиваемого ресурса.
Ключевые заголовки — Content-Length и Transfer-Encoding — важны в обработке сообщений. Заголовок Content-Length указывает размер тела в байтах, а Transfer-Encoding: chunked позволяет передавать данные частями (chunks). Однако одновременное использование этих заголовков может привести к конфликтам, например, к уязвимостям типа HTTP Request Smuggling, когда фронтенд и бэкенд по-разному интерпретируют запрос.
Кроме того, HTTP-сообщения используют специальные символы \r\n — возврат каретки и перевод строки — для разделения строк и частей сообщения. Например:
POST / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 10\r\n
\r\n
wr3dmast3r
Концепция HTTP Request Smuggling
HTTP Request Smuggling — это тип уязвимости, который возникает из-за несоответствий в обработке HTTP-запросов между фронтендом, например, балансировщиком нагрузки или reverse proxy, и бэкендом — сервером приложения. Фронтенд выступает в роли посредника, который принимает запросы от клиентов и передает их на бэкенд для дальнейшей обработки.
Однако, если фронтенд и бэкенд по-разному интерпретируют заголовки Content-Length и Transfer-Encoding, это может привести к уязвимостям. Например:
● Фронтенд может использовать заголовок Content-Length для определения размера тела запроса.
● Бэкенд, в свою очередь, может полагаться на заголовок Transfer-Encoding: chunked, который указывает, что тело запроса передается частями.
Если запрос содержит оба заголовка одновременно, это может привести к тому, что часть запроса останется необработанной и «перетечет» в следующий запрос. В результате у атакующего появится возможность внедрить вредоносные данные или манипулировать поведением сервера.

Согласно RFC 2616, если запрос содержит оба заголовка — Content-Length и Transfer-Encoding — сервер должен игнорировать Content-Length и использовать Transfer-Encoding. Однако на практике фронтенд и бэкенд могут не следовать этому правилу, что и приводит к уязвимостям.
Представьте, что фронтенд — это охранник на входе в здание, а бэкенд — сотрудник внутри. Если охранник и сотрудник по-разному интерпретируют инструкции, атакующий может обмануть охранника и пронести в здание запрещенный предмет. Точно так же, при HTTP Request Smuggling, атакующий может «протащить» вредоносный запрос, используя разницу в интерпретации данных между фронтендом и бэкендом.
Важно отметить, что HTTP Request Smuggling не эксплуатирует уязвимости в самом веб-приложении. Вместо этого она использует особенности интерпретации HTTP-сообщений веб-серверами. Это инфраструктурная уязвимость, которая возникает из-за некорректной настройки или взаимодействия между компонентами веб-серверов.
С помощью этой уязвимости при атаке можно достичь такие цели:
● повышение привилегий;
● обход механизмов безопасности;
● доступ или изменение данных;
● кража сессии;
● отравление кэша.
Это лишь некоторые из возможных вариантов использования уязвимости. В зависимости от контекста приложения и особенностей взаимодействия между фронтендом и бэкендом, атакующий может найти и другие способы применения HTTP Request Smuggling.
Разница между CL.TE и TE.CL HTTP Request Smuggling
Рассмотрим два варианта HTTP Request Smuggling, характерных для HTTP/1.1: CL.TE и TE.CL. Но отметим, что подобные уязвимости могут возникать и в других версиях протокола. Ключевой момент — разница в интерпретации заголовков Content-Length (CL) и Transfer-Encoding (TE) между фронтендом и бэкендом. В зависимости от того, какой сервер использует какой заголовок, атакующий может манипулировать запросами, чтобы внедрить вредоносные данные.
Давайте разберем, как это работает в случаях CL.TE и TE.CL, а также, почему важно правильно учитывать символы, особенно при использовании chunked-кодирования. Понимание этих механизмов поможет лучше защитить инфраструктуру от подобных атак.
CL.TE (Frontend: Content-Length, Backend: Transfer-Encoding). В этом сценарии фронтенд использует заголовок Content-Length для определения размера тела запроса, а бэкенд — заголовок Transfer-Encoding: chunked для обработки тела запроса как chunked-данных.
Рассмотрим пример запроса:
POST / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 43\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /admin HTTP/1.1\r\n
Host: example.com
Фронтенд видит Content-Length: 43, считает, что тело запроса составляет 43 байта и передает весь запрос на бэкенд. Бэкенд видит Transfer-Encoding: chunked и начинает обрабатывать тело запроса как chunked-данные — первый chunk: 0\r\n\r\n — это конец chunked-данных. Всё, что идет после — GET /admin HTTP/1.1\r\n Host: example.com — бэкенд интерпретирует как начало нового запроса. В результате бэкенд обрабатывает второй запрос — GET /admin — который «внедрил» атакующий.
В этом случае атакующему нужно убедиться, что размер тела запроса, указанный в Content-Length, соответствует данным, которые фронтенд передаст на бэкенд. Остальные данные «перетекают» в следующий запрос.

TE.CL (Frontend: Transfer-Encoding, Backend: Content-Length). В этом сценарии фронтенд использует заголовок Transfer-Encoding: chunked для обработки тела запроса, а бэкенд использует заголовок Content-Length для определения размера тела запроса.
Рассмотрим пример запроса:
POST / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 4\r\n
Transfer-Encoding: chunked\r\n
\r\n
7e\r\n
GET /admin HTTP/1.1\r\n
Host: example.com\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 20\r\n
\r\n
exploit=exploit\r\n
0\r\n
\r\n
Фронтенд видит Transfer-Encoding: chunked и обрабатывает тело запроса как chunked-данные — первый chunk: 7e (шестнадцатеричное значение, равное 126 байтам). Фронтенд читает следующие 126 байт, включая строку GET /admin HTTP/1.1 и всё, что идет после. Затем видит завершающий chunk (0\r\n\r\n) и завершает обработку запроса.
Бэкенд видит Content-Length: 4 и читает только первые 4 байта тела запроса — 7e\r\n. Оставшиеся данные — GET /admin HTTP/1.1 и так далее — бэкенд не обрабатывает, и они «перетекают» в следующий запрос. Когда следующий пользователь отправляет запрос, бэкенд начинает его обработку с «перетекших» данных, что может привести к неправильной интерпретации запроса.
В этом случае атакующий использует chunked-кодирование, чтобы фронтенд обработал весь запрос, а бэкенд прочитал только часть данных, указанную в Content-Length. Значение 7e (126 в десятичной системе) используется для указания размера chunk. Это позволяет фронтенду обработать большой объем данных, в то время как бэкенд прочитает только первые 4 байта.

Ключевые различия между CL.TE и TE.CL

Важно правильно подсчитывать символы, потому что в CL.TE-атаке необходимо, чтобы размер тела запроса, указанный в Content-Length, соответствовал данным, которые фронтенд передаст на бэкенд. Остальные данные «перетекают» в следующий запрос.
В TE.CL-атаке атакующий использует chunked-кодирование, чтобы фронтенд обработал определенный объем данных, а бэкенд прочитал только часть, указанную в Content-Length. Для этого требуется точный расчета размера chunk и понимание как фронтенд и бэкенд интерпретируют данные.
Классический пример отравления запроса
Рассмотрим пример, где атакующий отправляет запрос с двумя заголовками: Content-Length и Transfer-Encoding. Фронтенд и бэкенд интерпретируют запрос по-разному, что приводит к отравлению следующего запроса.
Вот как это выглядит:

Что здесь происходит:
Атакующий отправляет запрос с заголовками Content-Length: 6 и Transfer-Encoding: chunked. Тело запроса содержит chunked-данные: 0\r\n\r\nG.
Фронтенд — например, балансировщик нагрузки — интерпретирует запрос на основе Content-Length: 6 и считает, что тело запроса составляет 6 байт. Он передает весь запрос на бэкенд, включая символ «G».
Бэкенд, в свою очередь, использует Transfer-Encoding: chunked и ожидает chunked-данные. Он обрабатывает 0\r\n\r\n как конец запроса и игнорирует символ «G». В результате символ «G» остается необработанным и «перетекает» в следующий запрос.
Когда другой пользователь отправляет следующий запрос, бэкенд начинает его обработку с символа «G». Это приводит к тому, что запрос интерпретируется как GPOST вместо POST, из-за чего возникает ошибка.
Практика
Рассмотрим подобные атаки в деле на примере двух уязвимых приложений, в которых уязвимости HTTP Request Smuggling используется в комбинации с Web Cache Poisoning.
Lab: Exploiting HTTP request smuggling to perform web cache poisoning
Эта лабораторная работа включает фронтенд- и бэкенд-серверы, при этом фронтенд-сервер не поддерживает chunked-кодирование. Он настроен на кэширование определенных ответов.
Цель — выполнить атаку с использованием уязвимости HTTP Request Smuggling, которая приведет к отравлению кэша. В результате последующий запрос на загрузку JavaScript-файла должен перенаправляться на сервер атакующего.
Подтверждение уязвимости. На главной странице веб-приложения можно проверить наличие HTTP Request Smuggling. Во-первых, нам нужно определить, какие заголовки использует веб-приложение, например:
● CL.TE — интерфейс использует заголовок Content-Length, серверная часть использует заголовок Transfer-Encoding.
● TE.CL — интерфейс использует заголовок Transfer-Encoding, серверная часть использует заголовок Content-Length.
Обращаем внимание на эти две настройки, при проверке данной уязвимости они важны:

Запрос для проверки атаки:

Следующий после него запрос:

Так как следующий по порядку запрос возвращает код состояния 404, можно понять, что веб-приложение уязвимо для подделки HTTP-запросов CL.TE.
Изучение структуры приложения. При анализе истории HTTP-запросов можно заметить, что всё, что находится в директории /resources/* — кэшируется. Например, JavaScript-файлы, такие как /resources/js/tracking.js:

При просмотре функции отображения постов в блоге можно заметить функцию перехода к следующему посту:

При нажатии на ссылку отправляется GET-запрос на /post/next с параметром postId, после чего происходит перенаправление на следующий пост:

Web Cache Poisoning. Это атака, при которой атакующий манипулирует кэшем сервера, чтобы заставить его сохранить вредоносные данные. Когда другие пользователи запрашивают закэшированный ресурс, они получают подмененные данные, что может привести к перенаправлению на вредоносный сайт или выполнению вредоносного JS-кода.
Цель атаки — заставить сервер кэшировать вредоносный JavaScript-файл, который будут загружать другие пользователи. Для этого можно использовать HTTP Request Smuggling, чтобы подменить содержимое кэшируемого файла.
Попробуем использовать HTTP Request Smuggling для подмены заголовка Host в запросе с перенаправлением:

Когда мы отправили второй запрос, веб-приложение ответило нашим замаскированным запросом (/post/next?postId=5), и перенаправило нас на evil.com. Учитывая эту информацию, если мы сможем использовать перехват HTTP-запросов для заражения веб-кэша, мы сможем перенаправлять пользователей куда угодно.
Размещаем эксплойт:

Для атаки мы сначала отправляем с HTTP Request Smuggling отравленный запрос, который будет выполнять перенаправление на наш веб-сервер. После этого нам нужно отравить кэшируемый файл /resources/js/tracking.js, чтобы в кэш попало наше перенаправление, далее будет происходить импорт нашей полезной нагрузки на сайт.
Запрос на HTTP Request Smuggling:

Следующий отправленный запрос в приложении будет иметь перенаправление:

Результат:

Lab: Exploiting HTTP request smuggling to bypass front-end security controls, TE.CL vulnerability
Эта лабораторная работа включает два сервера: фронтенд, например, балансировщик нагрузки или reverse proxy, и бэкенд — сервер приложения. Важно отметить, что бэкенд-сервер не поддерживает chunked-кодирование. На сервере также есть админ-панель по пути /admin, но доступ к ней блокируется фронтенд-сервером.
Цель — используя уязвимость HTTP Request Smuggling, провести атаку, которая позволит получить доступ к админ-панели на бэкенд-сервере и удалить пользователя carlos.
Подтверждение уязвимости. Здесь мы можем попытаться определить, уязвимо ли веб-приложение для перехвата HTTP-запросов. Во-первых, нам нужно определить, какой тип HTTP-запроса используется, например:
● CL.TE интерфейс использует заголовок Content-Length, серверная часть использует заголовок Transfer-Encoding.
● TE.CL интерфейс использует заголовок Transfer-Encoding, серверная часть использует заголовок Content-Length.
Запрос на проверку HTTP Request Smuggling:

Сервер возвращает код состояния 500. Поэтому мы можем предположить, что веб-приложение уязвимо для подделки HTTP-запросов TE.CL.
Эксплуатация уязвимости
Проверяем админ-панель:

Пробуем обойти ограничение с помощью HTTP Request Smuggling:

Следующий запрос:

Мы смогли обойти блокировку доступа к директории /admin — это уже что-то. Попробуем использовать localhost во втором запросе:

Следующий запрос:

Отлично, мы получили доступ к админ-панели, осталось удалить пользователя:

Следующий запрос:

Рекомендации по защите
HTTP Request Smuggling — серьезная уязвимость, которая может привести к компрометации данных и нарушению работы серверов. Понимание механизмов HTTP и правильная настройка серверов — ключ для предотвращения таких атак. Регулярное тестирование и обновление инфраструктуры помогут минимизировать риски и обеспечить безопасность ваших приложений. Также рекомендуется использовать современные версии протокола — HTTP/2 и HTTP/3 — у которых есть встроенные механизмы для предотвращения подобных атак.
Четыре совета:
Запретить повторное использование соединений с бэкендом. Это исключит возможность «перетекания» данных между запросами, а это — основа для атак типа Smuggling.
Перейти на HTTP/2 для взаимодействия с бэкендом. Этот протокол использует бинарный формат и строгую структуру сообщений, что снижает вероятность ошибок интерпретации.
Унифицировать ПО на фронтенде и бэкенде. Использование одинакового программного обеспечения минимизирует различия в обработке запросов, что снижает риск уязвимостей.
Внедрить WAF (Web Application Firewall) с функцией обнаружения аномалий. Современные WAF способны анализировать трафик и блокировать подозрительные запросы, включая попытки HTTP Request Smuggling.
Дополнительные ссылки:
HTTP Desync Attacks: Smashing into the Cell Next Door — DEF CON