Dev.to Security 🔐 Cybersecurity 👁 0 📖 2 min read

Defending Multi-Tenant Reverse Proxies Against Host Header Poisoning and Cache Injection

When building multi-tenant SaaS routing or API gateways, reverse proxies (Nginx, Caddy, Envoy, Traefik) map inbound domain names to internal upstream services. However, if your proxy blindly passes untrusted client hea

When building multi-tenant SaaS routing or API gateways, reverse proxies (Nginx, Caddy, Envoy, Traefik) map inbound domain names to internal upstream services.

However, if your proxy blindly passes untrusted client headers upstream without strict hostname validation, you open the door to Host Header Poisoning, password reset URL hijacking, and web cache poisoning.

Here are the four defensive patterns to eliminate host header vulnerabilities at the edge.

1. The Vulnerability: Blind Header Passthrough

Consider a typical naive Nginx reverse proxy configuration:

server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://internal_app_upstream;
        proxy_set_header Host $http_host; # DANGEROUS
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

If an attacker sends:

GET /auth/password-reset HTTP/1.1
Host: evil-attacker-domain.com
X-Forwarded-Host: evil-attacker-domain.com

The downstream application generates password reset links using the client-supplied Host header:
https://evil-attacker-domain.com/reset?token=xyz

When the user clicks the link, their reset token is logged on the attacker's server.

2. Pattern 1: Catch-All Default Server Drop

Never let a catch-all virtual host (server_name _) proxy requests downstream. Reject unmatched Host headers immediately with HTTP 444 (close connection) or 400 Bad Request.

# Reject any connection where Host does not match known domains
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    ssl_reject_handshake on; # Disconnect TLS before SNI negotiation
    return 444;
}

3. Pattern 2: Strict Tenant Host Allowlisting

For dynamic multi-tenant platforms where tenants bring custom vanity domains, validate the host against an in-memory Redis or SQLite allowlist before proxying.

In Nginx with the njs scripting module or Lua:

# Map incoming host against known tenant domains
map $http_host $valid_tenant {
    default 0;
    app.example.com 1;
    api.example.com 1;
    include /etc/nginx/conf.d/allowed_custom_domains.map;
}

server {
    listen 443 ssl;
    server_name ~^(?<domain>.+)$;

    if ($valid_tenant = 0) {
        return 403 "Unknown or unverified domain host";
    }

    location / {
        proxy_pass http://upstream_tenant_backend;
        proxy_set_header Host $domain; # Explicitly set to validated variable
        proxy_set_header X-Forwarded-Host $domain;
    }
}

4. Pattern 3: Strip Spoofed Forwarded Headers

Internal microservices often inspect X-Forwarded-Host, X-Forwarded-Proto, or RFC 7239 Forwarded headers to determine the public-facing URL.

If your edge proxy does not sanitize inbound client values, external attackers can inject conflicting headers:

# Strip client-supplied forward headers and regenerate authoritative ones
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;

# Strip RFC 7239 header unless explicitly normalized
proxy_set_header Forwarded "";

5. Pattern 4: Web Cache Key Partitioning

If your edge layers cache dynamic responses, an attacker can poison the cache for all legitimate users by sending a request with a poisoned X-Forwarded-Host. If the cache key only includes the request path ($request_uri), subsequent users get the poisoned HTML.

Always ensure your cache key incorporates the fully qualified, verified scheme and host:

proxy_cache_key "$scheme$domain$request_uri";

Summary Checklist

  • [ ] Does your reverse proxy reject unmatched SNI / Host headers with a default drop?
  • [ ] Are custom tenant domains verified against an explicit allowlist before traffic hits upstream handlers?
  • [ ] Are inbound Forwarded and X-Forwarded-Host headers stripped and regenerated?
  • [ ] Does the web cache key include the validated domain?

Edge security begins by treating every inbound HTTP header as untrusted user input.

Run an instant conversion, SEO & leak audit on your website free: https://leaksift.pages.dev/?src=devto

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.