critical Threat analysis

Starlette CVE-2026-48710: BadHost Authentication Bypass

Starlette CVE-2026-48710 (nicknamed 'BadHost') is a critical authentication bypass vulnerability affecting foundational Python web libraries like FastAPI, vLLM, and LiteLLM. An attacker can inject path boundary characters into the Host header to bypass path-based security middleware; this article provides dependency audits and HTTP log hunting scripts.

#starlette#fastapi#zero-day#security-bypass#cisa-kev
On this page 0% read

    Executive Summary

    A critical authentication bypass vulnerability, designated CVE-2026-48710 and nicknamed “BadHost”, has been disclosed in the Starlette web framework (affecting all versions prior to 1.0.1) Ostif.

    Because Starlette serves as the foundational ASGI toolkit for widely used Python frameworks and applications—including FastAPI, vLLM, and LiteLLM—the blast radius of this vulnerability is exceptionally large, exposing millions of production web services and AI agents to pre-authenticated security bypasses CyberKendra. This article provides a comprehensive impact determination, key facts, and a complete Python audit script to identify vulnerable versions and log indicators of active exploitation.

    Key Facts

    cve: "CVE-2026-48710"
    alias: "BadHost"
    vendor: "Starlette"
    product: "Starlette (ASGI toolkit)"
    disclosed_date: "2026-05-20"
    vulnerability: "Authentication bypass via Host header URL reconstruction injection"
    cwe: ["CWE-346", "CWE-284"]
    affected_products: ["Starlette", "FastAPI", "vLLM", "LiteLLM"]
    affected_versions: ["Starlette < 1.0.1"]
    fixed_versions: ["1.0.1"]
    nvd_cvss_v31: "9.8 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
    exploitation_status: "active_exploit_publicly_available"
    zero_day_status: "confirmed_active_zero_day_exploitation"

    Source Confidence & Evidence Mapping

    • confirmed: Ostif security report details the exact root-cause URL reconstruction bug inside Starlette’s request.url implementation, confirming authentication bypass Ostif.
    • confirmed: CyberKendra publishes a complete proof-of-concept concept showing how injected path boundaries (e.g. /, ?) inside the Host header trick Starlette’s routing middleware CyberKendra.
    • confirmed: Downstream vendor alerts from FastAPI and LiteLLM recommend immediate upgrades of their underlying Starlette dependency to version 1.0.1 or higher.

    Impact Determination

    ClassificationCriteriaRequired evidenceRemediation triggerClosure condition
    Confirmed compromiseWeb/WAF logs show HTTP requests with path-delimiter characters (e.g. /, ?, #) in the Host header, followed by unauthorized access to administrative or restricted paths.Logs showing Host header values like example.com/health?x= targeting /admin with 200 OK status.Revoke all session keys, isolate internal API services, and run backend audits.Upgrade Starlette to 1.0.1 or later and verify backend logs for no further anomalous Host headers.
    Presumed exposedThe web application is built on FastAPI or Starlette with version < 1.0.1, exposing path-based middleware authorization gates.requirements.txt or poetry.lock lockfile showing starlette < 1.0.1 in active production environments.Upgrade the library dependency immediately to a patched release.Dependency verification outputs showing starlette >= 1.0.1 installed.
    Potentially exposedA Python project is running but lockfiles or backend web log records are not audited.Asset register indicating Python-based ASGI servers (FastAPI/Uvicorn) without version check.Execute the dependency and log audit script.Confirm if the asset is confirmed compromised, presumed exposed, or not exposed.
    Not exposedThe system utilizes non-affected framework stacks, or Starlette version is verified >= 1.0.1.Verified dependency files showing version 1.0.1 or later.None for this CVE.Version verification bundle is archived.

    Timeline

    • 2026-05-20: Public disclosure of CVE-2026-48710 (“BadHost”) following coordinate disclosures and accidental Chromium bug-tracker leaks CyberKendra.
    • 2026-05-22: Public release of working proof-of-concept exploits showing complete middleware bypasses Ostif.
    • 2026-05-26: Large-scale scanning observed across public cloud subnets targeting FastAPI/vLLM endpoints.

    What Happened

    Starlette handles URL reconstruction dynamically by concatenating the raw HTTP Host header with the request path to build a unified URI string. Because the Host header was not validated against standard RFC grammar, attackers could inject custom query delimiters (/health?x=) directly into the Host field:

    • The Request: GET /admin with Host: example.com/health?x=
    • The Reconstruction: Starlette builds the internal URL as http://example.com/health?x=/admin.
    • The Bypass: The underlying ASGI server processes the request path /admin, but Starlette’s routing/auth middleware inspects the reconstructed request.url.path which returns /health. If /health is whitelisted, the authentication check is bypassed entirely, granting unauthenticated access to the restricted /admin resources.

    Technical Analysis

    The fundamental error occurs during URL parsing inside Starlette’s utility functions. By failing to validate that the host contains only valid hostname characters, re-parsing the reconstructed URL causes the injection parameters to overwrite the target path, shifting the path boundary forward.

    Affected Assets and Blast Radius

    asset_selectors:
      - "starlette"
      - "fastapi"
      - "litellm"
      - "vllm"
    highest_value_assets:
      - "Internet-facing FastAPI applications utilizing path-based middleware"
      - "vLLM or LiteLLM AI endpoints exposed with API key authentication"
      - "Internal corporate admin dashboards written in Python ASGI frameworks"
    credentials_and_data_at_risk:
      - "Internal LLM access and model weights (via vLLM/LiteLLM bypass)"
      - "Backend database records accessible through administrative endpoints"
      - "Administrative session credentials and user data"

    Indicators And Detection Selectors

    vulnerabilities: ["CVE-2026-48710", "BadHost"]
    packages: ["starlette", "fastapi"]
    telemetry_selectors:
      - "starlette"
      - "fastapi"
      - "Host:"
      - "/health"

    Detection and Hunting

    This hunting script audits Python dependencies (requirements.txt, poetry.lock, Pipfile.lock) for vulnerable Starlette versions, and scans web application log files (e.g. Nginx or Uvicorn logs) for exploit attempts featuring path delimiters in the Host field:

    #!/usr/bin/env python3
    import json
    import os
    import re
    import sys
    from pathlib import Path
    
    ROOT = Path(os.environ.get("ROOT", sys.argv[1] if len(sys.argv) > 1 else ".")).resolve()
    TELEMETRY_DIR = Path(os.environ.get("TELEMETRY_DIR", "telemetry-export")).resolve()
    OUT = Path(os.environ.get("OUT", "hp-starlette-cve-2026-48710-scope")).resolve()
    
    CVE = "CVE-2026-48710"
    VULN_ID = "BadHost"
    FIXED_VERSION = "1.0.1"
    
    def read_text(path):
        try:
            return path.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            return ""
    
    def is_vulnerable_version(ver_str):
        ver = re.findall(r"\d+", ver_str)
        if not ver:
            return False
        v_ints = tuple(int(x) for x in ver[:3])
        # Vulnerable: starlette < 1.0.1
        if v_ints < (1, 0, 1):
            return True
        return False
    
    OUT.mkdir(parents=True, exist_ok=True)
    findings = {
        "vulnerable_dependencies": [],
        "exploit_attempts": []
    }
    
    # 1. Audit Python Dependency Files
    for lockfile in ROOT.rglob("*"):
        if not lockfile.is_file() or any(part in {".git", "node_modules"} for part in lockfile.parts):
            continue
        
        # Audit requirements.txt
        if lockfile.name == "requirements.txt":
            content = read_text(lockfile)
            matches = re.findall(r"starlette\s*==\s*([0-9a-zA-Z\.]+)", content, re.IGNORECASE)
            for version in matches:
                if is_vulnerable_version(version):
                    findings["vulnerable_dependencies"].append({
                        "file": str(lockfile),
                        "package": "starlette",
                        "version_found": version,
                        "vulnerable": True,
                        "remediation": "Update starlette to 1.0.1 or later"
                    })
    
        # Audit poetry.lock / poetry configs
        elif lockfile.name == "poetry.lock":
            content = read_text(lockfile)
            # Search for package starlette block
            blocks = re.findall(r'name\s*=\s*"starlette"\s*\n\s*version\s*=\s*"([^"]+)"', content)
            for version in blocks:
                if is_vulnerable_version(version):
                    findings["vulnerable_dependencies"].append({
                        "file": str(lockfile),
                        "package": "starlette",
                        "version_found": version,
                        "vulnerable": True,
                        "remediation": "poetry update starlette"
                    })
    
    # 2. Audit Web Server / WAF Log Exports for Injected Host Headers
    # Indicator: Host headers containing characters: '/', '?', '#'
    for path in ROOT.rglob("*"):
        if not path.is_file() or any(part in {".git", "node_modules"} for part in path.parts):
            continue
        
        if path.suffix in {".log", ".txt", ".json", ".csv"}:
            body = read_text(path)
            # Scan log lines for Host headers containing injection delimiters (e.g. host="example.com/health")
            # In typical logs: search for Host values containing slashes, question marks, or hashes
            host_inject_matches = re.finditer(r'(?:host|host_header)["\s:]+([^"\s]+[\/\?\#][^"\s]*)', body, re.IGNORECASE)
            
            for match in host_inject_matches:
                injected_host = match.group(1)
                findings["exploit_attempts"].append({
                    "file": str(path),
                    "indicator": "Host header injection pattern matching BadHost RCE",
                    "injected_host_value": injected_host,
                    "context_snippet": body[max(0, match.start() - 60):min(len(body), match.end() + 60)].replace("\n", " ")
                })
    
    with open(OUT / "findings.json", "w") as f:
        json.dump(findings, f, indent=2)
    
    print(f"[{CVE} Audit Complete] Findings saved to: {OUT / 'findings.json'}")

    Remediation & Credential Rotation Plan

    Containment & Mitigation

    1. Host Header Validation: Ensure that front-facing reverse proxies (like Nginx, Apache, or AWS ALB) are explicitly configured to validate and normalize the Host header before passing the request downstream to Uvicorn/Starlette.
    2. Block Redirections: Add WAF rules to drop any incoming requests where the Host header contains non-standard URI characters such as /, ?, or #.

    Eradication & Recovery

    1. Upgrade Starlette Dependency: Mandate immediate dependency upgrades of Starlette to version 1.0.1 or later:
      • Command: pip install --upgrade starlette or poetry update starlette
    2. Rotate Exposed Secrets: If web logs show successful requests (200 OK) featuring a poisoned Host header targeting restricted administration directories, immediately revoke and rotate all backend API keys and session credentials accessible through the compromised services.

    Sources

    1. Ostif Threat Advisory: Starlette BadHost CVE-2026-48710
    2. CyberKendra Security Analysis: Starlette Host Injection Bypass