système : OPÉRATIONNEL
← retour à tous les hacks
INFRASTRUCTURE MEDIUM NEW

SSRF vLLM : quand le correctif d'allowlist reproduit le même bug de parsing

Deux avis vLLM montrent deux fois la même faille : une allowlist d'hôtes validée par un parseur d'URL et la requête envoyée par un autre. Le correctif a changé de parseurs et rouvert le contournement.

2026-06-20 // 6 min affects: vllm, vllm-<0.14.1, vllm-0.15.1-to-0.16.x, llm-d, multimodal-models

De quoi s’agit-il ?

Les modèles multimodaux de vLLM peuvent récupérer images, audio et vidéo depuis des URL fournies dans une requête. Pour éviter que cette fonctionnalité ne devienne une primitive de Server-Side Request Forgery (SSRF), vLLM vérifie l’hôte de chaque URL contre une allowlist avant de l’aller chercher. Deux avis montrent que ce contrôle a été contourné deux fois de la même manière.

Le premier, CVE-2026-24779 (GHSA-qh4c-xf7m-gxfc, publié le 27 janvier 2026, CVSS 7.1 « élevé »), est une SSRF dans la classe MediaConnector : l’hôte est validé par un parseur d’URL et récupéré par un autre. Le second, CVE-2026-25960 (GHSA-v359-jj2v-j536, publié le 9 mars 2026, CVSS 5.4 « modéré »), est le contournement du correctif : le patch a déplacé la validation vers un nouveau parseur, mais le chemin de récupération asynchrone utilisait encore un troisième parseur. Même classe de bug, rouverte par la correction. Les deux relèvent de CWE-918, et la chaîne est entièrement corrigée dans vLLM 0.17.0.

Comment ça marche

Le motif est un parser differential : valider l’URL avec le parseur A, envoyer la requête avec le parseur B, et compter sur le fait que les deux s’accordent sur l’hôte visé. Quand ils divergent, l’allowlist protège un nom d’hôte que le client HTTP ne contacte jamais réellement.

Dans la faille initiale de MediaConnector, la validation utilisait urllib (urlparse) de Python tandis que la récupération passait par requests (qui s’appuie sur urllib3). Les deux suivent des grammaires d’URL différentes — l’une plus proche de la RFC 3986, l’autre du standard WHATWG — et ne traitent pas l’antislash de la même façon, ce qui suffit à glisser un autre hôte au travers de l’allowlist.

Le correctif (PR #32746) a uniformisé la validation sur urllib3.util.parse_url(). Mais le chemin de récupération asynchrone, load_from_url_async dans vllm/connections.py, envoie la requête via aiohttp, qui analyse les URL avec la bibliothèque yarl. L’avis documente la divergence avec précision :

URL en entrée : https://httpbin.org\@evil.com/

urllib3.parse_url()  -> host = httpbin.org   (encode "\" en %5C, traite "\@evil.com/" comme le chemin)
yarl (via aiohttp)   -> host = evil.com      (traite "httpbin.org\" comme userinfo, "@" comme séparateur)

Le validateur voit donc httpbin.org et approuve la requête ; aiohttp se connecte ensuite à evil.com. L’allowlist passe, la requête part ailleurs. Aucun payload exotique n’est nécessaire — l’URL d’exemple canonique figure dans l’avis public, et la technique sous-jacente (confusion entre parseurs sur l’antislash et le @/userinfo) est une classe connue de longue date de contournement de filtre SSRF, pas une nouvelle attaque.

Pourquoi c’est important

Une SSRF sur un serveur d’inférence, c’est plus qu’un simple curl sortant. vLLM est souvent déployé au sein de clusters — l’avis cite des topologies de type llm-d — où le pod se trouve sur un réseau interne de confiance sans filtrage de sortie. Une récupération pilotée par la requête permet alors d’atteindre des services internes visibles depuis le pod : endpoints de métadonnées, pods voisins, API d’administration, bases de données. Le premier avis souligne qu’un attaquant pourrait viser un endpoint d’administration interne et y remonter de fausses métriques (par exemple un état erroné du cache KV), déstabilisant la flotte de service.

La leçon plus large est celle que les deux CVE répètent. Valider-puis-récupérer n’est sûr que si le même composant résout l’hôte aux deux moments. Remplacer urlliburllib3 par urllib3yarl a corrigé une divergence précise et en a introduit une autre, parce que l’architecture — « parser deux fois et croire qu’elles s’accordent » — est restée intacte. Les contournements d’allowlist tiennent rarement à une seule mauvaise regex ; ils tiennent à deux bouts de code en désaccord sur le sens d’une chaîne.

Défenses

Si vous exploitez vLLM avec la récupération d’URL multimodale, passez à la version 0.17.0 ou ultérieure, qui corrige le chemin asynchrone (PR #34743).

Au-delà du patch, traitez la récupération d’URL pour ce qu’elle est : un point d’injection SSRF. Désactivez la récupération de médias distants si votre déploiement n’en a pas besoin, et fournissez plutôt aux modèles des octets pré-téléchargés. Lorsque la récupération est indispensable, posez la frontière au niveau réseau plutôt que dans l’analyse de chaînes : interdisez au pod d’inférence toute sortie vers les plages d’adresses internes et vers l’IP de métadonnées du cloud, afin qu’un contournement d’allowlist n’atteigne rien d’utile.

Pour la classe « parser differential » en particulier, ne validez pas une représentation d’une URL pour ensuite transmettre la chaîne brute à un client différent. Analysez une fois, résolvez l’hôte en adresse IP, vérifiez cette IP contre votre politique, puis connectez-vous à cette adresse figée — de sorte que l’hôte approuvé soit l’hôte contacté. Bloquez les redirections sur les récupérations de médias (vLLM les conditionne via VLLM_MEDIA_URL_ALLOW_REDIRECTS), car une réponse 30x est un autre moyen de déplacer la destination réelle après le contrôle.

État

ÉlémentDétail
CVE-2026-24779 (SSRF MediaConnector)Publié le 27 janv. 2026 · CVSS 7.1 élevé · correctif PR #32746
CVE-2026-25960 (contournement de la protection SSRF)Publié le 9 mars 2026 · CVSS 5.4 modéré · affecte vLLM ≥ 0.15.1 · correctif PR #34743
Corrigé dansvLLM 0.17.0
FaiblesseCWE-918 (Server-Side Request Forgery)

Sources