РАЗБОР Статья

HTTP Request Smuggling эксплуатация: методология 2026

Сергей Попов
Сергей Попов Red Team · 6,7 тыс. сообщений
Подписаться
334
[ обложка статьи ]
Режим чтения
Макросъёмка тёмной печатной платы сетевого прокси-устройства с резкими дорожками и конденсаторами, на миниатюрном OLED-дисплее светится голубая надпись CL.TE DESYNC · T1190 · SMUGGLED. Жёсткий ян...


На пентесте финтех-API за Cloudflare в начале 2025-го я два дня гонял стандартный OWASP-чеклист - ноль findings. WAF блокировал SQLi, XSS, SSRF. На третий день запустил HTTP Request Smuggler в Burp, поймал timeout-аномалию на POST-запросе с двойными CL/TE заголовками - и через четыре часа у меня был рабочий CL.TE PoC с кражей сессионных cookie. Баг ушёл на Critical. Ни один WAF не спасёт, если front-end и back-end по-разному считают байты в теле запроса.

Бизнес-логика desync атаки: зачем это атакующему​

HTTP Request Smuggling (CWE-444 - Inconsistent Interpretation of HTTP Requests) не эксплуатирует баг в коде приложения. Это инфраструктурная уязвимость - front-end back-end рассинхронизация в парсинге границ HTTP-сообщений. Reverse proxy видит один запрос, back-end - два. Остаток первого запроса «приклеивается» к следующему в TCP-потоке. Подробнее - в нашем подробном разборе пентест веб-приложений.

Что это даёт атакующему в терминах MITRE ATT&CK:

ЦельТехника ATT&CKМеханизм через HRS
Проникновение через WAFExploit Public-Facing Application (T1190, Initial Access)Smuggled-запрос обходит все проверки front-end
Отравление кэшаContent Injection (T1659, Initial Access)Подмена ответа в кэше CDN/Varnish для всех пользователей
Перехват сессийSteal Web Session Cookie (T1539, Credential Access)"Поглощение" Cookie-заголовка из чужого запроса

Монетизация прямолинейна: один smuggled GET /admin - ACL перестаёт существовать. Один отравленный кэш - тысячи пользователей получают XSS-payload. Bug bounty выплаты за CWE-444 стабильно высокие, потому что импакт почти всегда Critical. Тема не нова - первая публикация датируется 2005 годом, но второе дыхание открыли работы James Kettle из PortSwigger: "HTTP desync attacks" (2019), "Browser-Powered Desync Attacks" (2022), "HTTP/1.1 must die: the desync endgame" (2025).

Fingerprinting стека: первый шаг методологии пентеста HTTP Request Smuggling​

1793862490801.webp

Прежде чем слать smuggling-payload, нужно понять архитектуру цепочки. Без fingerprinting - стрельба вслепую: payload для CL.TE не сработает на TE.CL-стеке.

Типичные уязвимые связки​

HRS работает только при наличии минимум двух компонентов с разным парсером HTTP. Вот связки, на которых я находил desync в реальных проектах:

Front-endBack-endВероятный вариантПочему
Nginx (proxy_request_buffering off)Gunicorn / uWSGICL.TE, CL.0Nginx передаёт тело без пересборки
HAProxyApache / PHP-FPMTE.CL, CL.TEРазница в приоритете CL vs TE
CDN (Cloudflare, AWS ALB)Любой HTTP/1.1 back-endH2.CL, H2.TEHTTP/2 downgrade при конвертации
VarnishNode.js / ExpressCL.TEVarnish не всегда валидирует TE

Методы определения​

Заголовки ответа. Простой GET / покажет Server, Via, X-Powered-By, X-Cache. Заголовок Via явно указывает промежуточные прокси. Cloudflare добавляет cf-ray, AWS ALB - x-amzn-requestid, HAProxy может выдать X-Haproxy-*. Комбинация Server: nginx в ответе и X-Powered-By: Express - классический маркер двухзвенной цепочки.

HTTP/2 detection. Команда curl --http2 -v https://target.com 2>&1 | grep "ALPN" покажет, принимает ли front-end HTTP/2. Если да, а back-end отвечает по HTTP/1.1 (видно по внутренним ошибкам или по заголовкам) - перед вами сценарий HTTP/2 downgrade smuggling. Самый актуальный вектор 2026 года, и большинство пентестеров его до сих пор пропускают.

Timing-проба. Отправьте запрос с Transfer-Encoding: chunked и намеренно разорванным чанком (без терминирующего 0\r\n\r\n). Задержка 5+ секунд - back-end ждёт завершения chunked-данных и использует TE. Мгновенный ответ - back-end игнорирует TE и опирается на CL. Эта разница - первый индикатор потенциальной desync атаки.

Детектирование desync: timing-based workflow​

1793862643392.webp

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
- конец первого запроса. Но пересылает на back-end всё полученное тело целиком (включая X), поскольку оно уже буферизовано в TCP-потоке. Back-end использует Content-Length: 6, ожидает 6 байт тела. Фактически полученные данные 0\r\n\r\nX - это 5 байт, back-end ждёт ещё один. Timeout. Задержка подтверждает TE.CL. Механика подробно описана в PortSwigger Web Security Academy (TE.CL vulnerabilities).

Differential response как альтернатива​

Когда timing не показателен (back-end быстро дропает malformed-запросы), работает differential response. Smuggled-запрос содержит GET /obviously-nonexistent-path HTTP/1.1. Если следующий легитимный запрос через то же keep-alive соединение возвращает 404 вместо ожидаемого 200 - desync подтверждён. Метод реализован в расширении HTTP Request Smuggler.

Критический момент: в Burp Repeater нужно отключить автоматический пересчёт Content-Length (Repeater -> Uncheck "Update Content-Length"). Без этого Burp "починит" ваш payload, и timing-проба покажет ложноотрицательный результат. Также убедитесь, что запрос уходит по HTTP/1.1, а не HTTP/2 - переключается в Inspector -> Request attributes.

Варианты эксплуатации desync уязвимостей​

1793862660458.webp

CL.TE и TE.CL: классика смаглинга запросов​

Механика CL.TE уязвимости: front-end читает тело по Content-Length (включая smuggled-запрос), передаёт целиком. Back-end парсит chunked, видит нулевой чанк - конец первого запроса. Всё после нулевого чанка интерпретируется как начало нового запроса.

Для TE.CL - зеркально. Front-end парсит chunked до нулевого чанка и передаёт. Back-end читает по Content-Length, обрезает тело раньше - остаток становится следующим запросом.

По данным PortSwigger Web Security Academy, CL.TE - самый распространённый вариант в production. На моей практике подтверждается: из десяти найденных HRS за последний год семь были CL.TE, два - TE.CL, один - H2.CL.

TE.TE рассинхронизация через обфускацию Transfer-Encoding​

Когда оба сервера поддерживают TE, нужно заставить один из них проигнорировать заголовок. Рабочие варианты обфускации:
Код:
Transfer-Encoding: chunkedx
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:
 chunked
Transfer-Encoding: chunked
Transfer-encoding: identity
Каждый вариант эксплуатирует разницу в парсинге: один сервер отбросит невалидное значение и переключится на Content-Length, другой - примет. Результат - та же рассинхронизация, только через obfuscation. HTTP Request Smuggler в Burp автоматически перебирает десятки вариантов обфускации TE-заголовка - руками это делать тоскливо.

HTTP/2 downgrade smuggling: H2.CL и H2.TE​

Ключевой вектор HRS атаки на веб-приложения в 2026 году. HTTP/2 сам по себе иммунен к классическому smuggling - длина сообщения определяется бинарными фреймами, а не текстовыми заголовками. Но end-to-end HTTP/2 - редкость. Обычно CDN или балансировщик принимает HTTP/2 и конвертирует в HTTP/1.1 для back-end (HTTP downgrading).

H2.CL: атакующий отправляет HTTP/2 запрос с явным заголовком content-length, конфликтующим с фактической длиной данных во фрейме. Front-end при конвертации в HTTP/1.1 сохраняет этот Content-Length, а back-end рассчитывает длину по реально полученным данным. Разница - классический primitive для smuggling.

H2.TE: атакующий внедряет transfer-encoding: chunked в HTTP/2 запрос. Спецификация HTTP/2 запрещает TE кроме trailers, но некоторые front-end при downgrade передают заголовок на back-end как есть. Back-end начинает парсить chunked - и дальше цепочка эксплуатации аналогична HTTP/1.1.

Для тестирования H2 вариантов в Burp переключите протокол в Inspector -> Request attributes -> HTTP/2. В curl используйте --http2 с --http1.1 на разных этапах для сравнения поведения.

CL.0 и 0.CL: обход прокси и балансировщиков HTTP без конфликта заголовков​

CL.0 - back-end полностью игнорирует Content-Length и не читает тело запроса для определённых endpoint'ов или методов. Всё, что передано в теле, автоматически становится началом следующего запроса. Конфликт CL/TE не нужен - достаточно найти endpoint, где back-end не обрабатывает body.

0.CL - front-end игнорирует Content-Length и не пересылает тело. Back-end ожидает данные, которые не придут. Если по тому же keep-alive соединению приходит запрос другого пользователя, back-end может интерпретировать его как тело предыдущего запроса.

Оба варианта описаны в исследовании YesWeHack и встречаются реже классических CL.TE/TE.CL, но опасны именно тем, что не детектируются стандартными проверками на конфликт CL и TE заголовков. Сканер пройдёт мимо, а десинк - вот он.

Инструменты для веб пентеста HRS​

HTTP Request Smuggler (Burp-расширение от PortSwigger) - основной инструмент. Запускается через контекстное меню на запросе (Extensions -> HTTP Request Smuggler -> Smuggle probe). Перебирает CL.TE, TE.CL, TE.TE с десятками обфускаций, результаты - в Dashboard с severity и confidence. Начинайте с него.

Turbo Intruder - когда нужен точный контроль над timing. Позволяет слать запросы с заданной частотой и мерить response time с миллисекундной точностью. Незаменим для edge-кейсов, где стандартный таймаут HTTP Request Smuggler не срабатывает.

smuggler.py (defparam/smuggler) - CLI-инструмент на Python 3. Запуск: python3 smuggler.py -u https://target.com. Тестирует набор payload'ов для CL.TE/TE.CL и выводит результат с цветовой индикацией. Удобен для массового сканирования на этапе recon.

h2csmuggler (Python, BishopFox) - заточен под HTTP/2 cleartext (h2c) smuggling сценарии. Запуск: python3 h2csmuggler.py -x https://target.com. Позволяет тестировать обход reverse proxy через h2c upgrade.

Raw TCP через Python. Когда Burp нормализует "грязные" заголовки (удаляет пробелы, фиксит регистр), я переключаюсь на raw sockets. Библиотека h11 позволяет конструировать HTTP/1.1-сообщения побайтово, hyper - работать с HTTP/2 фреймами. Для TE.TE обфускации с obs-fold или нестандартными символами это единственный надёжный способ. Burp тут бессилен - он слишком "вежливый" для таких грязных трюков.

От primitive к impact: цепочки эксплуатации​

Desync сам по себе - primitive, не импакт. Для bug bounty отчёта нужна цепочка до конкретного последствия.

Обход ACL (Broken Access Control, A01:2021)​

Smuggled-запрос содержит GET /admin HTTP/1.1 с Host: target.com. Front-end никогда не пропустил бы - ACL блокирует /admin для внешних IP. Но smuggled-запрос проходит мимо front-end и попадает на back-end напрямую. Back-end не проверяет ACL - он доверяет front-end. Результат: доступ к административной панели, удаление пользователей, чтение конфигурации. ACL как будто и нет.

Cache Poisoning (CPDoS)​

Если перед back-end стоит кэширующий слой (Varnish, CDN), smuggled-запрос отравляет кэш. Smuggled GET / с Host: evil.com заставляет back-end вернуть 301 Redirect на evil.com. Кэш сохраняет этот ответ для URI /. Все последующие пользователи получают редирект. Это Content Injection (T1659) - массовое воздействие через один запрос. Исследователи демонстрировали подобные атаки на инфраструктуре крупных технологических компаний через CL.TE уязвимости в рамках bug bounty программ.

Кража сессий (T1539)​

Smuggled POST-запрос направлен на endpoint, сохраняющий данные (комментарий, профиль, feedback-форма). Тело smuggled-запроса обрезано - back-end ждёт продолжения. Следующий запрос другого пользователя (включая его Cookie-заголовок) "вклеивается" в тело первого. Атакующий читает свой комментарий - и видит чужую сессию.

Работает только на стеках с keep-alive между front-end и back-end и общим пулом соединений. Но когда работает - это самый красивый (и самый пугающий для заказчика) PoC.

Предусловия и ограничения​

ФакторТребованиеЧто проверять
Keep-aliveСоединения между front-end и back-end переиспользуютсяЗаголовок Connection в ответе
Буферизацияproxy_request_buffering off в Nginx расширяет поверхностьКонфиг Nginx или timing-поведение
HTTP/2 front-endFront-end принимает H2 и конвертирует в H1.1ALPN negotiation через curl --http2 -v
Общий пулЗапросы разных пользователей идут через одно back-end соединениеTiming-корреляция между запросами
Версия ПОКонкретные версии прокси/серверов с известными quirksServer заголовок, fingerprinting

Nginx с дефолтной конфигурацией (proxy_request_buffering on) пересобирает запрос с явным Content-Length перед отправкой на back-end. Это разрушает большинство CL.TE/TE.CL схем. Но при отключённой буферизации (часто делают для upload-endpoint'ов) или при HTTP/2 downgrade - вектор открывается. HAProxy с http-buffer-request и HTX-режимом тоже существенно сужает поверхность атаки, но не закрывает её полностью - особенно для H2 downgrade сценариев.

Тестирование HTTP Request Smuggling на production - рискованная операция. Smuggled-запросы влияют на чужой трафик: можно сломать кэш, вызвать рассинхронизацию пайплайна, положить сервис для других пользователей. Всегда начинайте со staging, а на production - только с явным согласованием scope и готовностью к откату. Я однажды на production отравил кэш главной страницы клиента - починили за минуту, но адреналина хватило на неделю.

За последние два года вижу чёткий сдвиг: классические CL.TE/TE.CL на чистом HTTP/1.1 встречаются реже - Nginx и HAProxy в дефолтных конфигурациях стали достаточно строгими. Но количество HTTP/2 downgrade сценариев растёт пропорционально миграции на H2. Каждый CDN, каждый cloud-балансировщик - потенциальная точка конвертации H2 в H1.1 с собственными quirks парсера.

Большинство пентестеров до сих пор не тестируют H2.CL и H2.TE, потому что "HTTP/2 же иммунен к smuggling". По спецификации - да, если end-to-end. На практике end-to-end HTTP/2 до back-end - исключение, не правило.

Формула на бумаге понятна, но desync по-настоящему ощущается только когда сам прогоняешь timing-пробы и наблюдаешь, как пятисекундная задержка превращается в smuggled GET /admin. Хочется руки в крови - лаба с этой техникой ждёт на HackerLab (ХакерЛаб).
Полезно

Комментарии

0