What is Host Header Spoofing?
Host header spoofing occurs when an attacker manipulates the HTTP Host header to trick the application into generating URLs, redirects, or cached content pointing to a malicious domain. This is possible when NGINX passes user-controlled Host headers to backend applications.
Real-World Impact
Host header attacks have been used for:
- Password reset poisoning - Reset links pointing to attacker's domain
- Web cache poisoning - Caching malicious content for all users
- Server-side request forgery - Accessing internal services
- Virtual host confusion - Accessing other sites on the same server
How the Attack Works
Normal Request:
GET /reset-password HTTP/1.1
Host: legitimate-site.com
Host: legitimate-site.com
Spoofed Request:
GET /reset-password HTTP/1.1
Host: attacker-site.com
Host: attacker-site.com
If the backend uses the Host header to generate URLs, the reset link will point to attacker-site.com
The Dangerous Variables
NGINX has several variables related to the Host header:
$http_host- The raw Host header from the client request (user-controlled!)$host- Hostname from request line, Host header, or server_name (in that order)$server_name- The first server_name in the configuration (safe)
Key Insight:
$http_host is always user-controlled. $host can be user-controlled in some cases. $server_name is always server-defined.
Vulnerable Patterns
Pattern 1: Passing $http_host to Backend
# Passes raw user-controlled Host header to backend
location / {
proxy_pass http://backend;
proxy_set_header Host $http_host;
}
# Attacker can send: Host: evil.com
# Backend receives: Host: evil.com
Pattern 2: Using $host Without server_name Validation
# Default server catches all requests
server {
listen 80 default_server;
# No server_name defined!
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
}
}
# $host falls back to the request's Host header
Pattern 3: X-Forwarded-Host with User Input
# Passes user Host header as X-Forwarded-Host
location / {
proxy_pass http://backend;
proxy_set_header X-Forwarded-Host $http_host;
}
# If backend trusts X-Forwarded-Host for URL generation...
Secure Patterns
Use Hardcoded Host Header
# Always use the expected hostname
location / {
proxy_pass http://backend;
proxy_set_header Host "backend.internal";
}
# Backend always receives the correct host
Use $server_name
server {
listen 80;
server_name www.example.com example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $server_name;
}
}
# Always uses the configured server_name
Define server_name and Reject Unknown Hosts
# Catch-all server that rejects unknown hosts
server {
listen 80 default_server;
server_name _;
return 444; # Close connection without response
}
# Actual server with explicit server_name
server {
listen 80;
server_name www.example.com example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host; # Safe because server_name is defined
}
}
Validate Host Header in Application
# Even with safe NGINX config, validate in your application
# Django example:
ALLOWED_HOSTS = ['www.example.com', 'example.com']
# This is defense-in-depth
When $host is Safe
The $host variable is safe to use when ALL of these conditions are met:
- Your server block has an explicit
server_namedirective - You have a catch-all default server that rejects unknown hosts
- The request matches your server_name (NGINX validated it)
Detecting with Gixy
Gixy automatically detects host header spoofing vulnerabilities:
$ gixy /etc/nginx/nginx.conf
==================== Results ====================
[host_spoofing] The proxied Host header may be spoofed.
Severity: MEDIUM
Description: Using $http_host or user-controlled variables for
the Host header when proxying is insecure.
Reason: Using "$http_host" for the "Host" header allows clients
to control the value.
Pseudo config:
server {
location / {
proxy_pass http://backend;
proxy_set_header Host $http_host;
}
}
File: /etc/nginx/conf.d/proxy.conf
Line: 8
==================== Summary ====================
Total issues: 1 (Medium: 1)
Testing for Vulnerability
# Test with curl
curl -H "Host: evil.com" https://target.com/password-reset
# If the response contains "evil.com" in any URLs, it's vulnerable
# Test X-Forwarded-Host
curl -H "X-Forwarded-Host: evil.com" https://target.com/password-reset
Prevention Checklist
- Never use
$http_hostinproxy_set_header Host - Always define explicit
server_namein server blocks - Create a catch-all default_server that rejects unknown hosts
- Use
$server_nameor hardcoded hostnames for proxied Host headers - Configure ALLOWED_HOSTS or equivalent in your backend application
- Be careful with X-Forwarded-Host headers too
- Run Gixy to automatically detect these issues