Knowledge Base

Host Header Injection — атаки через подмену заголовка Host

Host Header Injection

Категория: Web Application Security · Риск: medium

Host Header Injection

Что такое Host Header Injection

Заголовок `Host` в HTTP-запросе указывает, к какому виртуальному хосту обращается клиент. Если сервер использует значение заголовка `Host` для генерации ссылок (ссылки для сброса пароля, абсолютные URL в письмах) без проверки — атакующий может подменить его.

Сценарии атаки

Перехват ссылки для сброса пароля

POST /password-reset HTTP/1.1
Host: attacker.com          ← подменённый заголовок
Content-Type: application/json

{"email": "victim@example.com"}

Если приложение строит ссылку как `$HOST/reset?token=...`, жертва получит письмо:

Для сброса пароля перейдите: https://attacker.com/reset?token=ABCD

Cache Poisoning

Если CDN кеширует ответ, включающий значение Host в тело (`<link href="https://{HOST}/style.css">`), атакующий может отравить кеш для других пользователей.

SSRF через X-Forwarded-Host

GET / HTTP/1.1
Host: example.com
X-Forwarded-Host: attacker.com
X-Forwarded-For: 127.0.0.1

Проверка уязвимости

# Проверка отражения Host в ответе
curl -si -H "Host: injected.example.com" https://example.com/
# Ищем injected.example.com в Location, Set-Cookie, теле ответа

# Проверка X-Forwarded-Host
curl -si -H "X-Forwarded-Host: injected.example.com" https://example.com/

# Безопасный результат: хост не отражается, или возвращается 400

Как защититься

Явная конфигурация разрешённых хостов

# Django
ALLOWED_HOSTS = ['example.com', 'www.example.com']

# FastAPI — проверка через middleware
from fastapi import Request, HTTPException

@app.middleware("http")
async def check_host(request: Request, call_next):
    allowed = {'example.com', 'www.example.com'}
    host = request.headers.get('host', '').split(':')[0]
    if host not in allowed:
        raise HTTPException(status_code=400, detail="Invalid host")
    return await call_next(request)
// Laravel
// config/app.php нет встроенной защиты — используйте:
if (!in_array($_SERVER['HTTP_HOST'], ['example.com', 'www.example.com'])) {
    http_response_code(400);
    exit;
}

Nginx — явный server_name

server {
    listen 443 ssl;
    server_name example.com www.example.com;

    # Запрещает запросы с другим Host
    if ($host !~* ^(example\.com|www\.example\.com)$) {
        return 444;
    }
}

# Дефолтный блок — отклоняет все остальные Host
server {
    listen 443 ssl default_server;
    ssl_certificate /etc/nginx/ssl/dummy.crt;
    ssl_certificate_key /etc/nginx/ssl/dummy.key;
    return 444;
}

Генерация абсолютных URL без Host заголовка

# Задайте BASE_URL явно в конфиге, не полагайтесь на request.host
BASE_URL = "https://example.com"

def make_reset_link(token: str) -> str:
    return f"{BASE_URL}/reset?token={token}"

Связанные проверки