Что такое Host Header Injection и как один HTTP-заголовок ломает доверие к домену

Что такое Host Header Injection и как один HTTP-заголовок ломает доверие к домену

Host Header Injection — уязвимость, при которой приложение слишком доверяет HTTP-заголовку Host и использует значение из запроса как источник правды о собственном домене. На первый взгляд заголовок выглядит служебной мелочью: браузер сообщает серверу, к какому сайту обращается. На практике многие приложения берут Host для генерации ссылок, писем сброса пароля, перенаправлений, канонических адресов, проверки доступа, работы кэша и маршрутизации.

Проблема начинается в момент, когда сервер принимает произвольный Host от клиента и без проверки вставляет значение в важную логику. Атакующий не «взламывает домен» напрямую, а подсовывает приложению чужое имя хоста. Дальше все зависит от архитектуры: иногда атака ограничится некрасивой ссылкой, а иногда приведет к краже токена сброса пароля, отравлению кэша или обходу внутренних проверок.

Зачем HTTP вообще нужен заголовок Host

В HTTP/1.1 заголовок Host нужен серверу, чтобы понять, какой виртуальный сайт обслуживать на одном IP-адресе. На одном сервере могут жить десятки доменов, и без Host веб-сервер не поймет, отдать сайт example.com или shop.example.com. Поэтому нормальный запрос выглядит примерно так:

GET /account HTTP/1.1
 Host: example.com
 User-Agent: Mozilla/5.0

Заголовок отправляет клиент, а клиенту по определению нельзя доверять. Браузер обычно ставит корректное значение, но любой человек с прокси, консольным клиентом или тестовым инструментом может отправить запрос с другим Host. Именно на этом строится проверка уязвимости.

GET /account HTTP/1.1
 Host: attacker.example
 User-Agent: test-client

Сам по себе такой запрос еще не означает уязвимость. Уязвимость появляется, когда приложение принимает attacker.example как легитимное имя сайта и использует в ответе, письме, перенаправлении или проверке безопасности.

Где ломается логика приложения

Host Header Injection редко живет в одном конкретном месте кода. Чаще уязвимость рождается на стыке веб-сервера, обратного прокси, фреймворка и бизнес-логики. Nginx может принять один набор заголовков, приложение увидеть другой, а почтовый шаблон взять домен из третьего источника. Чем больше прослоек между пользователем и приложением, тем выше шанс ошибиться.

Классический сценарий выглядит просто: сайт формирует абсолютную ссылку из текущего запроса. Разработчик хочет получить адрес вроде https://example.com/reset?token=... и берет домен из Host. Пока запросы идут от обычных браузеров, все кажется нормальным. Но атакующий отправляет запрос с подмененным заголовком, и приложение собирает ссылку уже на чужой домен.

Host: attacker.example
 
 https://attacker.example/reset?token=secret-token

Подробно такие сценарии разбирает PortSwigger, причем главный вывод там не про экзотику, а про базовую дисциплину: нельзя строить безопасность вокруг значения, которое пришло от клиента.

Чем опасна Host Header Injection на практике

Самый неприятный вариант связан со сбросом пароля. Пользователь вводит почту, приложение генерирует одноразовый токен и отправляет письмо со ссылкой. Если домен в ссылке берется из Host, злоумышленник может попытаться заставить приложение отправить жертве ссылку на внешний домен. Дальше жертва открывает письмо, видит знакомый текст сервиса, но кликает по адресу атакующего. Если токен оказался в ссылке, злоумышленник получает шанс перехватить сброс.

Второй сценарий — отравление кэша. Если прокси или CDN кэширует ответ без корректного учета Host, злоумышленник может подсунуть ответ, в котором абсолютные ссылки или скрипты указывают на чужой домен. Потом такой ответ получат обычные пользователи. Подобная атака зависит от конкретной настройки кэша, поэтому нельзя автоматически считать каждый отраженный Host критической дырой. Но проверять кэширование нужно всегда.

Третий сценарий — обход внутренних проверок. Иногда приложение решает, что запрос к localhost, admin.example.com или внутреннему имени должен получить особые права. Если проверка смотрит только на заголовок, а не на реальную сетевую схему и доверенные прокси, атакующий может подставить нужное имя. В зрелых системах такой баг встречается реже, но в самописных панелях, старых админках и микросервисах до сих пор попадается.

Сценарий Что происходит Риск
Сброс пароля Ссылка в письме строится из подмененного Host Перехват токена и захват учетной записи
Перенаправление Приложение отправляет пользователя на домен из заголовка Фишинг и потеря доверия к сервису
Кэш Прокси сохраняет ответ с чужими абсолютными ссылками Массовая подмена контента для пользователей
Внутренний доступ Логика доверяет Host как признаку внутреннего домена Обход ограничений и доступ к служебным функциям

Почему простая проверка «отразился ли Host» не доказывает критичность

В отчетах по безопасности часто встречается слабое доказательство: тестировщик меняет Host, видит новое значение в HTML и сразу пишет «критическая уязвимость». Такой вывод слишком быстрый. Отражение заголовка в ответе может быть багом, но реальный риск зависит от контекста. Нужно понять, попадает ли подмененный домен в письма, токены, кэшируемые ответы, редиректы, настройки CORS, OAuth-цепочки, вебхуки или внутреннюю маршрутизацию.

Есть и обратная ошибка. Команда видит, что на главной странице подмена не сработала, и закрывает вопрос. Но опасная логика может жить не на главной странице, а в форме восстановления пароля, приглашении пользователя в команду, генерации счета, подтверждении почты или административной панели. Host Header Injection почти всегда нужно искать по функциям, где приложение само формирует абсолютные URL.

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

Как безопасно проверить Host Header Injection

Проверять такую уязвимость можно только на своих системах, в рамках договора на тестирование или в разрешенной программе поиска ошибок. Для чужих сайтов подмена заголовков, проверка сброса пароля и эксперименты с кэшем могут нарушать закон и правила сервиса, особенно в России. Ответственное тестирование означает минимальное воздействие, отсутствие попыток получить чужие токены и аккуратную фиксацию результата.

Базовая проверка начинается с безвредного запроса к тестовому окружению. Меняют Host на контролируемое значение и смотрят, где значение появляется. Дальше проверяют функции, которые создают внешние ссылки. Хороший признак уязвимости — письмо восстановления пароля или приглашения, где домен заменился на посторонний. Плохой признак для отчета — только отражение в диагностическом блоке без влияния на безопасность.

GET /password-reset HTTP/1.1
 Host: test-attacker.example
 X-Forwarded-Host: test-attacker.example

Кроме Host, проверяют заголовки X-Forwarded-Host, X-Host, Forwarded и похожие прокси-заголовки. Многие приложения стоят за балансировщиком, поэтому фреймворк может брать «исходный хост» не из Host, а из заголовка, который должен был выставлять только доверенный прокси. Если внешний клиент может управлять таким заголовком, риск становится выше.

Как закрыть Host Header Injection без косметики

Правильная защита начинается не в шаблоне письма, а на входе. Веб-сервер и приложение должны принимать только ожидаемые имена хостов. Для публичного сайта таким списком обычно становятся основной домен, поддомены, технические домены окружений и ничего больше. Любой неизвестный Host лучше отклонять до попадания в приложение.

На уровне приложения не стоит собирать критические ссылки из текущего запроса. Базовый публичный адрес сервиса должен лежать в конфигурации, например PUBLIC_BASE_URL=https://example.com. Тогда ссылка восстановления пароля не зависит от того, какой заголовок прислал клиент. Такой подход скучный, зато надежный.

Для прокси-заголовков нужна отдельная дисциплина. Приложение может доверять X-Forwarded-Host только от конкретного обратного прокси, а внешний трафик должен проходить через очистку лишних заголовков. Если инфраструктура принимает клиентские X-Forwarded-* без фильтрации, приложение видит не реальную схему запроса, а то, что захотел написать пользователь.

  • Настройте белый список допустимых доменов на уровне веб-сервера и приложения.
  • Отклоняйте неизвестный Host, а не исправляйте молча.
  • Храните публичный адрес сайта в конфигурации, а не берите из запроса.
  • Очищайте внешние X-Forwarded-Host, Forwarded и похожие заголовки на границе.
  • Проверяйте письма, редиректы, OAuth, кэш и генерацию абсолютных ссылок после каждого изменения прокси-схемы.

Типичные ошибки разработчиков и администраторов

Первая ошибка — считать, что браузер всегда отправляет правильный Host. Браузер действительно ведет себя предсказуемо, но атакующий не обязан пользоваться обычным браузером. Сервер видит HTTP-запрос, а не намерения пользователя.

Вторая ошибка — чинить только один эндпоинт. Команда находит подмену в письме сброса пароля, жестко прописывает домен в одном шаблоне и забывает про приглашения, подтверждения почты, счета, уведомления и административные ссылки. Уязвимость при таком подходе просто переезжает в соседнюю функцию.

Третья ошибка — доверять CDN или балансировщику без проверки реального поведения. На схеме все выглядит красиво: пользователь идет в CDN, CDN идет в Nginx, Nginx идет в приложение. В реальности один лишний заголовок может пройти через всю цепочку. Документация OWASP полезна именно тем, что заставляет проверять ввод и доверенные границы, а не только код бизнес-функции.

Когда Host Header Injection превращается в серьезный инцидент

Сама возможность подставить Host еще не делает баг критическим. Критичность появляется, когда подмена влияет на секреты, учетные записи или массовую выдачу контента. Токен сброса пароля в ссылке на чужой домен — серьезный риск. Кэшируемая страница с подмененными абсолютными ссылками — тоже. Простая 404-страница, где в тексте отразилось чужое имя хоста, чаще тянет на низкий или средний уровень, если нет дополнительного влияния.

При разборе отчета полезно задавать один вопрос: что атакующий получает после подмены? Если ответ «только видит свое значение в HTML», отчет слабый. Если ответ «может получить токен, заставить пользователей уйти на чужой домен, подменить кэш или обойти проверку внутреннего хоста», проблему нужно чинить быстро.

FAQ по Host Header Injection

Host Header Injection и open redirect — одно и то же?

Нет. Open redirect обычно связан с параметром URL, который управляет перенаправлением. Host Header Injection использует заголовок Host или похожие прокси-заголовки. Но последствия могут пересекаться, потому что подмененный хост тоже способен привести к переходу на чужой домен.

Если сайт работает только по HTTPS, Host Header Injection невозможна?

HTTPS защищает канал, но не делает пользовательские заголовки доверенными. После расшифровки запроса сервер все равно получает Host от клиента. Нужны проверка домена и правильная настройка прокси.

Достаточно ли настроить редирект на основной домен?

Редирект помогает, но не всегда закрывает уязвимость. Опасная логика может сработать до редиректа: письмо уже отправилось, кэш уже сохранил ответ, приложение уже сформировало ссылку. Неизвестный Host лучше отклонять как некорректный запрос.

Почему проблема часто всплывает после переезда за прокси?

Прокси добавляет новые заголовки и меняет то, как приложение видит исходный запрос. Если приложение начинает доверять X-Forwarded-Host от внешнего клиента, старая безопасная схема превращается в уязвимую.

Какой самый надежный способ защиты?

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

Короткий вывод без лишней мистики

Host Header Injection опасна не потому, что заголовок Host сам по себе дает доступ к серверу. Опасность появляется, когда приложение верит заголовку сильнее, чем собственной конфигурации. Домен сайта — часть доверенной модели, а не произвольная строка из запроса.

Защита сводится к простой инженерной дисциплине: принимать только ожидаемые хосты, чистить прокси-заголовки на границе, хранить публичный адрес в настройках и проверять все функции, где приложение формирует абсолютные ссылки. Тестировать такие сценарии нужно только легально, на своих ресурсах или по явному разрешению владельца системы, без обхода ограничений, вмешательства в чужие аккаунты и попыток получить чужие данные.

host header injection уязвимость безопасность прокси кэш домен
Alt text
Обращаем внимание, что все материалы в этом блоге представляют личное мнение их авторов. Редакция SecurityLab.ru не несет ответственности за точность, полноту и достоверность опубликованных данных. Вся информация предоставлена «как есть» и может не соответствовать официальной позиции компании.
VO2
MAX
КУЛЬТ
Антипов жжет
5 УТРА. СМУЗИ. МАРАФОНЫ. ВЫ СГОРАЕТЕ, ДУМАЯ, ЧТО ЖИВЕТЕ.
Вместо бога — трекер Oura. Человек просто меняет литургию и платит за неё по подписке. Вы — землеройка в кроссовках.

Юрий Кочетов

Здесь я делюсь своими не самыми полезными, но крайне забавными мыслями о том, как устроен этот мир. Если вы устали от скучных советов и правильных решений, то вам точно сюда.