HTTP Request Smuggling (HRS, «десинхронизация запросов») — это класс атак, возникающий из‑за различий в том, как фронт‑прокси и бекенд парсят границы HTTP‑сообщений. Чаще всего уязвимости проявляются на стыке HTTP/1.1 и HTTP/2 (h2, h2c), в обработке заголовков Transfer-Encoding (TE) и Content-Length, в передаче/запрете hop‑by‑hop заголовков через Connection, а также при допущениях о безопасной конкатенации и повторном использовании соединений. Ниже — практическое руководство для Nginx, HAProxy и Apache: как понять риск и как закрыть его конфигурацией.
Коротко о причинах http request smuggling
Корень проблемы — нестыковка трактов. Фронт (CDN/балансировщик/реверс‑прокси) принимает запрос и пересобирает его для бекенда, а бекенд может интерпретировать границы тела иначе. Отсюда — «разъезд» потока: префикс одного запроса трактуется как следующий, что позволяет атакующему влиять на ответы к чужим запросам, обходить ACL, кэш и аутентификацию.
- CL.TE: фронт выбирает
Content-Length, бекенд —Transfer-Encoding: chunked. - TE.CL: обратная комбинация выбора.
- H2→H1 конверсия: HTTP/2 запрещает
TE, кромеTE: trailers, но при преобразовании в H1 некоторые прокси ошибочно переносят или подставляют TE, провоцируя десинхронизацию. - h2c (HTTP/2 без TLS) и
Upgrade: смешение протоколов и промежуточных преобразований. - «Грязные» заголовки: повторяющиеся
Content-Length, нестандартные пробелы, табы, нижние подчёркивания в именах, дублиConnection,Proxy-Connection,Trailer.
Ключевая цель защиты — чтобы на границе прокси тело запроса было однозначно прочитано, нормализовано и отправлено бекенду в предсказуемом виде (желательно с
Content-Length), а все hop‑by‑hop заголовки были отброшены.
Модель угроз и где искать риск в стеке
Типичный стек: публичный фронт (Nginx или HAProxy) завершает TLS и HTTP/2, конвертирует в HTTP/1.1 и проксирует к внутренним сервисам (иногда к Apache/PHP‑FPM, иногда к приложению напрямую). Риск растёт при:
- включённом keep‑alive и повторном использовании соединений к бекенду;
- стриминговых режимах без буферизации (для больших аплоадов/проксирования), когда прокси не пересобирает запрос;
- использовании h2c/Upgrade, смешении протоколов и нестандартных клиентов;
- наличии внешнего CDN перед вашим фронтом с собственными нормализациями заголовков.
Для TLS‑терминации на периметре используйте актуальные сертификаты и отключайте h2c; при необходимости обновить сертификат посмотрите наши SSL-сертификаты.

Nginx: безопасные настройки против десинхронизации
Почему Nginx чаще в выигрыше
По умолчанию Nginx читает запрос клиента полностью (с proxy_request_buffering on) и формирует новый запрос к апстриму с явным Content-Length. Так разрушаются типичные схемы CL.TE/TE.CL. Риски появляются при отключении буферизации или при агрессивных апстрим‑оптимизациях.
Рекомендации по http‑блоку
http {
# Запрет «грязных» заголовков и подчёркиваний
underscores_in_headers off;
ignore_invalid_headers on;
# Контроль размеров заголовков и H2 фреймов
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
http2_max_field_size 8k;
http2_max_header_size 32k;
# Ограничения по телу и таймаутам
client_max_body_size 20m;
client_body_timeout 20s;
# По умолчанию безопаснее буферизовать запросы
proxy_request_buffering on;
}
Реверс‑прокси сервер с HTTP/2 на входе
server {
listen 443 ssl http2;
server_name example.test;
# ... ssl_* настройки, не опускаем таймауты ниже разумных
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
# Убираем hop-by-hop заголовки, чтобы они не «протекали» к апстриму
proxy_set_header Connection "";
proxy_set_header Proxy-Connection "";
proxy_set_header TE "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Trailer "";
proxy_set_header Upgrade "";
# Явно пробрасываем нужные end-to-end заголовки
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}
upstream app_backend {
server 127.0.0.1:8080;
keepalive 64;
}
Комментарии:
proxy_request_buffering on— основной барьер от HRS. При необходимости стриминга (загрузки больших файлов) отключайте буферизацию точечно и компенсируйте: закрывайте соединение к апстриму (proxy_set_header Connection close) или проксируйте на эндпоинт, изолированный от общего пула.- Удаление
Connection,TE,Transfer-Encoding,Proxy-Connection,Upgrade,Trailerубирает почву для смещения границ тела на стороне бекенда. - Nginx не поддерживает универсальный HTTP/2 апстрим (кроме gRPC). Поэтому типичная связка — h2 на фронте, H1.1 на беке; тем важнее нормализация.
Диагностика и логи
log_format smuggle '$remote_addr "$request" $status $body_bytes_sent '
'$request_length $request_time '
'cl="$http_content_length" te="$http_transfer_encoding" conn="$http_connection"';
access_log /var/log/nginx/access_smuggle.log smuggle;
Поля $request_length, $http_transfer_encoding и $http_connection помогают быстро увидеть подозрительные запросы (TE, попытки протолкнуть hop‑by‑hop).
HAProxy: строгий парсинг и HTX
Ключевые опции
- HTX‑режим нормализует HTTP представление и защищает от ряда неочевидных кейсов. В новых ветках он включён по умолчанию, но лучше задать явно.
option http-buffer-request— заставляет фронт прочитать запрос целиком до отправки к беку, снижая риск десинхронизации.- Удаление hop‑by‑hop заголовков и явный запрет на дубликаты
Content-Length. - Отключение приёма устаревших символов H1:
no option h1-accept-obs-chars.
global
log 127.0.0.1 local0
defaults
mode http
option httplog
option http-use-htx
option http-buffer-request
no option h1-accept-obs-chars
timeout client 30s
timeout connect 5s
timeout server 30s
frontend fe_https
bind :443 ssl alpn h2,http/1.1 crt /etc/haproxy/certs/bundle.pem
# Уничтожаем hop-by-hop заголовки на входе
http-request del-header Proxy-Connection
http-request del-header Connection
http-request del-header TE
http-request del-header Transfer-Encoding
http-request del-header Trailer
http-request del-header Upgrade
# Рубим подозрительные комбинации длины
http-request deny if { req.hdr_cnt(content-length) gt 1 }
http-request deny if { req.hdr(content-length) -m found } { req.hdr(transfer-encoding) -m found }
default_backend be_app
backend be_app
balance roundrobin
# Для особо критичных путей можно закрывать соединение к беку:
# option httpclose
server s1 127.0.0.1:8080 check
Примечания:
- Политика «удаляем TE/Transfer-Encoding» безопасна для большинства приложений. Если у вас действительно нужны трейлеры, добавьте валидирующее правило, разрешающее только
TE: trailers, и всё равно удаляйте заголовок на границе перед бэком. - При терминаторе h2 на фронте и H1.1 к бэку HTX и
http-buffer-requestзаметно снижают поверхность атаки.
Логи и метрики
log-format %ci:%cp [%t] %ft %b %s %TR/%Tw/%Tc/%Tr/%Ta bytes=%B cl:%[req.hdr(content-length)] te:%[req.hdr(transfer-encoding)]
Следите за частотой отказов по ACL и аномальной долей запросов с TE.
Apache: моды, строгий протокол и прокси
В Apache важны три направления: строгий парсинг HTTP, корректные лимиты и аккуратный mod_proxy к бэку. В сочетаниях с mod_http2 и mod_proxy_http ранее встречались уязвимости, поэтому держите актуальные минорные версии.
Строгий HTTP и размеры
# httpd.conf или внутри VirtualHost
HttpProtocolOptions Strict
RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500
LimitRequestFieldSize 8190
LimitRequestFields 100
Отключаем h2c и настраиваем протоколы
# Во избежание лишних Upgrade-переходов
H2Upgrade off
# Разрешаем h2 по TLS и H1.1
Protocols h2 http/1.1
Чистим hop-by-hop заголовки на входе
# mod_headers
RequestHeader unset Proxy-Connection
RequestHeader unset Connection
RequestHeader unset TE
RequestHeader unset Transfer-Encoding
RequestHeader unset Trailer
RequestHeader unset Upgrade
Безопасный reverse proxy в Apache
# Пример VirtualHost (угловые скобки экранированы)
<VirtualHost *:443>
ServerName example.test
# ... SSL настройки
ProxyPreserveHost On
ProxyPass "/" "http://127.0.0.1:8080/" disablereuse=on
ProxyPassReverse "/" "http://127.0.0.1:8080/"
# Для особо рисковых локаций можно отключать keep-alive к бэку:
# ProxyPass "/upload/" "http://127.0.0.1:8080/upload/" disablereuse=on
# Логи
LogFormat "%h \"%r\" %>s %I %O cl:%{Content-Length}i te:%{Transfer-Encoding}i" vhostsmuggle
CustomLog logs/smuggle_access.log vhostsmuggle
</VirtualHost>
Флаг disablereuse=on закрывает соединение к бэку после ответа — это снижает риск десинхронизации ценой небольшой потери производительности. Применяйте точечно на опасных путях (загрузки, нестандартные клиенты).
Политика заголовков: что точно не должно попадать к бэку
Connectionи всё, что перечислено внутри него (включаяProxy-Connection,TE,Trailer,Upgrade,Keep-Alive).Transfer-Encodingприёмлем только на стороне клиента к фронту; к бэку — лучше всегдаContent-Lengthили заранее прочитанное тело.- Повторяющиеся или конфликтующие
Content-Lengthдолжны приводить к 400. - Подчёркивания в именах заголовков, табы и нестандартные пробелы — отклонять.
Про связанные политики см. разбор заголовков защиты: Базовые HTTP Security Headers для Nginx и Apache. Для статики и кэша полезно также настроить Cache-Control и ETag.
H2, h2c и конверсия в H1.1: где подвох
HTTP/2 не допускает TE, кроме TE: trailers, и использует бинарные фреймы вместо чанков. При конверсии в H1.1 фронт должен:
- отбрасывать любые
TEот клиента; - самостоятельно сформировать
Content-Lengthк бэку (после полного чтения тела) или закрыть соединение к бэку после передачи, если идёт поток; - не использовать h2c без крайней необходимости;
Upgradeна публичных порталах — источник сложности.

Когда буферизация «нельзя, но очень хочется»
Потоковые аплоады, веб‑хуки и большие тела иногда требуют стримить запрос на бэк без полной буферизации. Как уменьшить риск:
- Nginx: только для конкретных локаций ставьте
proxy_request_buffering offи одновременноproxy_set_header Connection closeк бэку; используйте отдельный апстрим‑пул. - HAProxy: включите
option http-buffer-requestхотя бы на контрольных путях; при острой необходимости —option httpcloseна бэке для этих путей. - Apache: для потоковых путей используйте
disablereuse=onвProxyPass, а также строгие лимиты и таймауты чтения.
Чек‑лист быстрого аудита
- На фронте удаляются
Connection,TE,Transfer-Encoding,Proxy-Connection,Trailer,Upgrade. - Дубликаты
Content-Lengthи конфликт сTransfer-Encodingприводят к 400. - Буферизация запросов включена по умолчанию; стриминг — только точечно и с закрытием соединения к бэку.
- HTTP/2 включён только по TLS (h2),
H2Upgrade off, h2c не используется на периметре. - Ограничены размеры заголовков и тела; включены таймауты чтения.
- Логи фиксируют
cl,te, размеры и время.
Безопасное тестирование (в стейджинге)
Тестируйте только в изолированной среде и с согласия ответственных. Цель — убедиться, что фронт не пропускает «грязные» заголовки и всегда нормализует запрос к бэку. Если стенда нет — поднимите отдельную VM на VDS и повторите проверки там.
- Проверьте, что запросы с
Transfer-Encoding: chunkedне доходят в неизменном виде до бэка (по логам приложения). - Отправьте два
Content-Lengthи убедитесь в 400 от фронта. - Снимите tcpdump/pcap между фронтом и бэком и проверьте, что тело к бэку идёт с явным
Content-Length, а не с чанками. - С HTTP/2 на входе убедитесь, что при конверсии в H1.1 к бэку нет
TEи иных hop‑by‑hop.
Наблюдаемость и алерты
Добавьте в алерты долю запросов с TE, частоту 400 по «bad request», всплески ошибок бэка после релизов прокси. Логи на фронте и бэке синхронизируйте по времени и коррелируйте по request-id, чтобы быстро ловить десинхронизацию.
Частые анти‑паттерны
- «Отключили буферизацию глобально, чтобы ускорить загрузки» — делайте только точечно и с закрытием соединений к бэку.
- «Передаём клиентские заголовки как есть» — hop‑by‑hop никогда не должен уходить к бэку.
- «Включили h2c ради эксперимента» — на периметре это увеличивает сложность и риск.
- «Слишком большие лимиты заголовков» — облегчает smuggling через необычные конструкции; держите лимиты в разумных пределах.
Итоги
http request smuggling — это не один баг, а класс ошибок на границе парсинга и нормализации HTTP. Защита сводится к простым принципам: буферизовать и пересобирать запрос, удалять hop‑by‑hop заголовки, избегать сомнительных режимов (h2c, стриминг без закрытия соединений), держать строгие лимиты и актуальные версии. В Nginx, HAProxy и Apache всё это достигается несколькими опциями — внедрите их один раз и закрепите проверками в CI.


