// Engineering Log

HTTP, HTTPS и TLS: Часть 12 — Атаки на уровне HTTP: smuggling, подмена Host, CSRF, XSS, SSRF и отравление кеша

Опубликовано 21.10.2026

// Быстрый маршрут

Эта статья относится к теме Серверы и инфраструктура.

HTTPS защищает данные по пути, но ничего не знает о том, что внутри запроса. Большая часть атак на сайты идёт по полностью зашифрованному соединению и использует особенности самого HTTP: то, как прокси и приложение по-разному разбирают один запрос, как браузер автоматически прикладывает cookie, как приложение доверяет заголовкам. В этой части разобраны основные классы таких атак — как они работают и что настроить, чтобы от них защититься. Проверять описанное можно только на своих системах или с письменного разрешения владельца.

Request smuggling: рассинхронизация прокси и приложения

Перед приложением почти всегда стоит прокси — Nginx, HAProxy, CDN. Если прокси и приложение по-разному определяют, где кончается один запрос и начинается следующий, злоумышленник может «пронести» внутри своего запроса начало чужого.

В HTTP/1.1 длину тела задают два заголовка: Content-Length и Transfer-Encoding: chunked. Стандарт требует, чтобы при наличии обоих использовался Transfer-Encoding, но реализации ошибаются:

POST / HTTP/1.1
Host: example.ru
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

Прокси, который смотрит на Content-Length, передаёт приложению всё тело. Приложение, которое смотрит на Transfer-Encoding, видит конец запроса на 0, а SMUGGLED считает началом следующего запроса в том же соединении. Этот «следующий» запрос склеивается с запросом другого пользователя, пришедшим по тому же соединению между прокси и приложением. Итог — чужой ответ, кража cookie, обход правил доступа на прокси.

Вариации: Transfer-Encoding: xchunked или с пробелом перед двоеточием, который одна сторона игнорирует, а другая нет; расхождения при переводе HTTP/2 в HTTP/1.1 на прокси (H2.CL, H2.TE); Upgrade: h2c, который прокси пропускает к приложению.

Защита:

  • держите прокси и серверы приложений обновлёнными — эти ошибки исправляют в каждой версии;
  • прокси должен отвергать неоднозначные запросы с обоими заголовками или хотя бы удалять Content-Length и не передавать его дальше; проверьте, как ведёт себя ваша связка, а не только прокси;
  • между прокси и приложением по возможности используйте HTTP/2 — в нём длина задаётся кадрами, и неоднозначности нет;
  • не пропускайте к приложению заголовки Upgrade: h2c.

Подмена заголовка Host

Host задаёт клиент, и приложение, которое строит по нему ссылки, доверяет пользовательскому вводу. Классический пример — сброс пароля: приложение отправляет письмо со ссылкой https://{Host}/reset?token=…. Злоумышленник запрашивает сброс пароля жертвы с Host: attacker.example, жертва получает настоящее письмо со ссылкой на чужой сайт, переходит — и токен сброса уходит злоумышленнику.

Защита:

  • в Nginx заведите default_server, который отвечает на неизвестные имена кодом 444 (закрыть соединение), а приложению передавайте только запросы с известными server_name;
  • в приложении задайте разрешённые имена (в Django — ALLOWED_HOSTS) и стройте абсолютные ссылки из настроек, а не из заголовка;
  • то же относится к X-Forwarded-Host: не принимайте его от клиентов.
nginx
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    ssl_reject_handshake on;
    return 444;
}

Отравление кеша и обман кеша

Отравление кеша (cache poisoning). CDN или прокси кеширует ответ по ключу — обычно схема, хост и путь. Если ответ зависит от заголовка, не входящего в ключ (например, X-Forwarded-Host попадает в адреса скриптов на странице), злоумышленник отправляет один запрос с поддельным заголовком, кеш сохраняет испорченную страницу и раздаёт её всем посетителям.

Обман кеша (cache deception). Обратная ситуация: кеш решает, что /account/profile.css — статический файл, по расширению. Приложение игнорирует «лишнее» окончание и отдаёт личную страницу пользователя. Злоумышленник присылает жертве ссылку, её личная страница попадает в общий кеш, и злоумышленник читает её оттуда.

Защита:

  • на персональных страницах — Cache-Control: private или no-store; CDN должен уважать заголовки приложения, а не кешировать по расширению;
  • если ответ зависит от заголовка, добавляйте его в Vary или в ключ кеша;
  • приложение должно отвечать 404 на несуществующие пути вроде /account/profile.css, а не отдавать страницу /account/profile.

CSRF: запрос от имени пользователя

Браузер автоматически прикладывает cookie сайта к запросам, откуда бы они ни шли. Если пользователь вошёл в bank.example.ru, а потом открыл страницу злоумышленника с формой, которая сама отправляется на https://bank.example.ru/transfer, запрос уйдёт с cookie сессии пользователя. Это межсайтовая подделка запроса (CSRF).

Защита:

  • SameSite у cookie. При SameSite=Lax (значение по умолчанию в современных браузерах) cookie не отправляются с POST-запросами с других сайтов. Не ставьте SameSite=None без необходимости;
  • CSRF-токен — случайное значение в форме, которое сервер сверяет с сессией. Есть во всех популярных фреймворках;
  • проверка Origin на изменяющих запросах: он должен совпадать с вашим сайтом;
  • не меняйте данные по GET: SameSite=Lax пропускает переходы по ссылкам, и GET-запрос с изменением сработает.

CORS от CSRF не защищает: он запрещает странице читать ответ, но запрос с формы уходит и выполняется.

XSS и Content-Security-Policy

Межсайтовый скриптинг (XSS) — внедрение чужого JavaScript в страницу вашего сайта: через комментарий, имя пользователя, параметр поиска, выведенный без экранирования. Скрипт работает от имени сайта: читает страницу, отправляет запросы с cookie пользователя, подменяет формы.

Защита — в несколько слоёв:

  • экранирование всего пользовательского ввода при выводе; шаблонизаторы делают это по умолчанию, опасны места, где экранирование отключено (|safe, v-html, dangerouslySetInnerHTML, innerHTML);
  • cookie сессии с HttpOnly — скрипт не сможет её прочитать;
  • Content-Security-Policy — даже если скрипт внедрён, браузер не выполнит его, если источник не разрешён:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'

Встроенные скрипты разрешаются только с одноразовым значением nonce, которое сервер генерирует для каждого ответа и ставит в <script nonce="r4nd0m">. 'unsafe-inline' в script-src практически отменяет защиту от XSS. Внедряйте CSP через Content-Security-Policy-Report-Only, собирая отчёты о нарушениях, и включайте блокировку, когда отчёты перестанут приходить.

SSRF: запросы от имени сервера

Если приложение по адресу от пользователя само скачивает что-то — картинку для превью, вебхук, импорт по ссылке, — злоумышленник может указать внутренний адрес: http://127.0.0.1:8080/admin, http://10.0.0.5:9200/, адрес метаданных облака http://169.254.169.254/. Запрос уйдёт изнутри вашей сети, где нет межсетевого экрана и часто нет авторизации. Через метаданные облака можно получить временные ключи доступа к облачному аккаунту.

Защита:

  • проверяйте адрес после разрешения имени: запрещайте частные, локальные и служебные диапазоны (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, fc00::/7);
  • проверяйте и каждый редирект: внешний адрес может перенаправить на внутренний;
  • защищайтесь от смены DNS между проверкой и запросом (DNS rebinding): подключайтесь к тому IP, который проверили;
  • в облаках включайте защищённый режим метаданных (в AWS — IMDSv2, требующий токен в заголовке);
  • лучше всего — выделить такие запросы в отдельный сервис или прокси без доступа во внутреннюю сеть.

Внедрение CRLF и разбиение ответа

Заголовки HTTP/1.1 разделяются символами \r\n. Если приложение подставляет ввод пользователя в заголовок без проверки — например, в Location при редиректе, — злоумышленник передаёт %0d%0a и дописывает свои заголовки или даже тело ответа: установить cookie, внедрить скрипт, отравить кеш. Современные фреймворки запрещают переводы строк в заголовках, но самописный код и старые библиотеки уязвимы.

В Nginx есть своя ловушка: $uri раскодирован, и в return 302 https://$host$uri; через %0d%0a можно внедрить заголовок. Используйте $request_uri — он передаётся в исходном, закодированном виде.

Открытые редиректы

Адрес вида https://example.ru/login?next=https://attacker.example после входа перенаправляет пользователя на чужой сайт. Сам по себе это не взлом, но ссылка начинается с вашего домена и вызывает доверие в фишинговых письмах, а в OAuth такой редирект может увести токен.

Защита: разрешайте в next только относительные пути на вашем сайте или адреса из списка. Проверка «начинается с /» недостаточна: //attacker.example — это адрес другого сайта.

Кликджекинг

Злоумышленник встраивает ваш сайт в прозрачный фрейм поверх своей страницы, и пользователь, думая, что нажимает кнопку на чужой странице, нажимает «Удалить аккаунт» или «Подтвердить платёж» на вашей. Защита — запрет встраивания:

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Самопроверка за 10 минут

Команды ниже проверяют самые частые проблемы. Запускайте их только против своих сайтов; example.ru — ваш домен, 192.0.2.10 — IP-адрес вашего сервера.

Неизвестный Host. Запрос с чужим именем на IP вашего сервера не должен попадать в приложение:

bash
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: evil.example' http://192.0.2.10/
# хорошо: 000 (Nginx закрыл соединение по return 444) или 4xx; плохо: 200 со страницей сайта

curl -sk -o /dev/null -w '%{http_code}\n' --resolve evil.example:443:192.0.2.10 https://evil.example/
# хорошо: 000 — рукопожатие отклонено (ssl_reject_handshake)

Отражение X-Forwarded-Host. Поддельное имя не должно попадать в ссылки на странице:

bash
curl -s -H 'X-Forwarded-Host: evil.example' https://example.ru/ | grep -c evil.example   # должно быть 0

Флаги cookie. У cookie сессии должны быть Secure, HttpOnly и SameSite:

bash
curl -sI https://example.ru/login | grep -i '^set-cookie'

Заголовки безопасности:

bash
for h in strict-transport-security content-security-policy x-content-type-options \
         referrer-policy x-frame-options permissions-policy; do
  printf '%-28s ' $h; curl -sI https://example.ru/ | grep -qi "^$h:" && echo есть || echo НЕТ
done

Открытый редирект. Подставьте параметры, которые ваш сайт использует для возврата после входа (next, return, redirect, url):

bash
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' 'https://example.ru/login?next=//evil.example'
# плохо: редирект на evil.example

Внедрение CRLF:

bash
curl -sI 'https://example.ru/%0d%0aX-Injected:%20yes' | grep -ci '^x-injected'   # должно быть 0

Метод TRACE должен быть выключен:

bash
curl -si -X TRACE https://example.ru/ | head -1   # 405 или 403

Личные страницы и кеш. Откройте страницу, доступную только после входа, дважды с cookie сессии и один раз без неё:

bash
curl -sI -b 'session=ВАША_СЕССИЯ' https://example.ru/account | grep -iE '^(cache-control|x-cache|cf-cache-status|age)'

В ответе должно быть Cache-Control: private или no-store, а у CDN — промах или обход кеша (MISS, BYPASS, DYNAMIC), без заголовка Age.

SSRF. Если у сайта есть функции загрузки по ссылке (аватар по адресу, импорт, вебхуки), укажите в них адрес http://127.0.0.1:22/ и http://169.254.169.254/latest/meta-data/. Приложение должно отказаться, а не вернуть содержимое или ошибку соединения с подробностями.

Request smuggling вручную проверить сложно. Для этого есть расширение HTTP Request Smuggler для Burp Suite и утилита smuggler.py; запускайте их на тестовом стенде с той же цепочкой прокси, что и в работе.

Что проверять

  • Прокси и серверы приложений обновлены; запросы с одновременными Content-Length и Transfer-Encoding не доходят до приложения в неоднозначном виде.
  • Есть default_server, который закрывает запросы с неизвестным Host.
  • Персональные страницы не кешируются CDN.
  • Cookie сессии: Secure; HttpOnly; SameSite=Lax.
  • Есть CSP без 'unsafe-inline' для скриптов и frame-ancestors.
  • Серверные запросы по пользовательским адресам не достают до внутренней сети.
  • Параметры редиректов проверяются по списку.

Для автоматической проверки подходят OWASP ZAP и Burp Suite: они содержат сканеры для всех перечисленных классов атак. Запускайте их на тестовом стенде — активное сканирование создаёт записи, отправляет формы и может повредить данные.

// Похожая задача

Если у вас похожая ситуация

Эта статья относится к одной из рабочих тем. Можно продолжить чтение по теме, перейти на главную, чтобы понять, чем я занимаюсь, или сразу открыть услуги.

Тема статьи

Серверы и инфраструктура

VPS, Linux, веб-стек, миграции, хостинг, базы данных и базовая эксплуатация.

Часто с этим приходят

  • Перенести сайт или сервис на новый сервер
  • Настроить Linux, Nginx, базу данных и бэкапы
  • Разобраться, почему всё работает нестабильно

// Следующий шаг

Если вам нужна не только статья, а помощь по этой теме, удобнее сразу перейти в услугу. Главная и подборка материалов остаются рядом.

Открыть услуги

// Reviews

Отзывы по теме

Спасибо Михаилу за отзывчивость. Созвонились, объяснил как сделать самому. Обращаюсь уже второй раз, все супер и оперативно.

Спасибо Михаилу за отзывчивость. Созвонились, объяснил как сделать самому. Обращаюсь уже второй раз, все супер и оперативно.

kireevk

kireevk

Консультация по nginx proxy manager и portainer

25.02.2025 · ★ 5/5

Освоившийся покупатель
apande

apande

Настройка nginx и opencart

07.09.2024 · ★ 5/5

Очень мощный покупатель
kireevk

kireevk

Диагностика Nginx Proxy Manager в docker контейнере и решение проблемы

15.03.2024 · ★ 5/5

Освоившийся покупатель

// Contact

Нужна помощь?

Свяжись со мной и я помогу решить проблему

Написать в Telegram

Отвечаю в течение рабочего дня (03:00–13:00 GMT)

Или оставьте заявку здесь:

Подтвердите, что вы не бот.

Написать и получить быстрый ответ