
إثبات مفهوم وظيفي لـ CVE-2026-17106 (CopyEscape): سباق TOCTOU في docker cp يتيح الكتابة الاعتباطية على مضيف Docker. مختبر Docker + مراقب inotify + LD_PRELOAD. نسخة macOS غير ضارة ونسخة Linux مدمرة.
إثبات مفهوم تعليمي لـ CopyEscape (CVE-2026-17106)، ثغرة سباق TOCTOU في
docker cpتسمح لحاوية خبيثة بكتابة ملفات عشوائية على مضيف Docker. مستوحاة من العمل الأصلي لـ masasron، الذي اكتشف ووثّق الثغرة في مدونة "CopyEscape: Taking Over Docker Hosts with docker cp".
CVE-2026-17106 هو ثغرة سباق (TOCTOU) في استخراج ملف tar الذي يولّده برنامج Docker الخفي أثناء docker cp <contenedor>:<ruta> <destino>. تؤثر على Docker CLI 29.6.1 والإصدارات الأقدم؛ وتم إصلاحها بدءًا من 29.7.0. يقع الخلل في CLI (استخراج tar على المضيف)، وليس في البرنامج الخفي.
السلسلة المسماة CopyEscape تبيّن كيف يمكن لمهاجم، انطلاقًا فقط من حاوية قيد التشغيل دون صلاحيات إضافية، أن:
/watched/file.txt/ كملف عادي أمام أي عملية تفتحه داخل الحاوية، عبر مكتبة LD_PRELOAD تعترض open, openat, openat2, fopen, stat, lstat, fstatat, statx وإصدارات 64.aaa.txt بحجم 16 MiB يجبر البرنامج الخفي على التأخر بما يكفي في إنتاج tar ليتمكن المراقب من الفوز بالسباق.inotify، وفي نافذة السباق، يستبدل دليلًا فرعيًا escape/ باختصار رمزي (symlink) مطلق إلى هدف المضيف ( على macOS أو على Linux) عبر استدعاءَي ذرّيين مع تراجع (rollback).النتيجة هي كتابة ملفات عشوائية على المضيف بصلاحيات العملية التي تنفّذ docker cp. بديل Linux يكتب فوق /usr/bin/runc ويحقق تنفيذ كود بصلاحيات root على المضيف عند أول docker run / exec لاحق.
يحتوي هذا المستودع على مواد تعليمية ودفاعية بشكل صارم.
runc على المضيف: استخدمه فقط في جهاز افتراضي (VM) قابل للتخلص مع نسخة احتياطية موثّقة.يُسلَّم إثبات المفهوم مع بديل macOS غير ضار (يكتب ~/pwnd كعلامة) هدفه الوحيد أن يكون بمثابة عرض توضيحي قابل لإعادة الإنتاج.
┌─────────────────────────────────────────────────────────────────────┐
│ 1. LD_PRELOAD camufla /watched/file.txt como archivo regular │
│ open/openat/openat2/fopen/stat/lstat/fstatat/statx → decoy │
│ docker exec ... cat /watched/file.txt → "top-level file" │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 2. Layout real dentro del contenedor │
│ /watched/file.txt/ │
│ ├── aaa.txt (16 MiB de 'B', cebo lento) │
│ ├── escape/ (directorio real inicial) │
│ ├── escape/<basename> (payload: runc o pwnd) │
│ └── .swap-escape (symlink → /Users/<usr> o /usr/bin) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 3. docker cp pide tar al daemon │
│ daemon recorre /watched/file.txt/ y abre aaa.txt │
│ inotify detecta IN_OPEN sobre aaa.txt │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 4. Monitor pivot: dos rename(2) atomicos con rollback │
│ escape/ → .old-escape/ (backup) │
│ .swap-escape → escape/ (symlink al target del host) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 5. Tar resultante contiene: │
│ escape → /Users/<usr> (symlink absoluto) │
│ escape/<basename> (entrada hija que atraviesa el symlink) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 6. CLI vulnerable extrae symlink + hija │
│ crea escape → /Users/<usr> en el destino local │
│ extrae escape/<basename> A TRAVES del symlink │
│ escribe /Users/<usr>/<basename> en el host │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ macOS: ~/pwnd con "COPYESCAPE_MACOS_DEMO" (demo inocua) │
│ Linux: /usr/bin/runc sobrescrito (RCE como root) │
└─────────────────────────────────────────────────────────────────────┘
تم بناء إثبات المفهوم ليُشغَّل على macOS مع:
curl, tar, git و docker في PATH.alpine:3.21.HOME بالصيغة /Users/<usuario>.لا حاجة لصلاحيات root لبديل macOS. يتطلب بديل Linux صلاحيات root وجهازًا افتراضيًا (VM) قابلًا للتخلص.
يقوم المُشغِّل run-poc.sh بتنزيل ثنائي CLI المعيبة 29.6.1 تلقائيًا من الموقع الرسمي لـ Docker (download.docker.com). لا يعدّل تثبيت Docker Desktop على النظام: يستخدم البرنامج الخفي الحالي لكنه يستدعي الثنائي المُنزَّل لعملية docker cp.
POC CopyEscape (CVE-2026-17106)/
├── README.md ← Este archivo
├── LICENSE ← Licencia MIT
├── .gitignore
├── Dockerfile ← Dockerfile unificado (macos | linux)
├── entrypoint.sh ← Arranque del monitor sin LD_PRELOAD
├── monitor.c ← Monitor inotify unificado (una sola fuente)
├── watched_preload.c ← LD_PRELOAD con cobertura ampliada
└── run-poc.sh ← Runner robusto con reintentos
يتم استبعاد دليل
cli-vulnerable/(CLI 29.6.1 المُنزَّلة) من المستودع عبر.gitignore. يقوم المُشغِّل بتنزيلها تلقائيًا إذا لم تكن موجودة.
cd poc-v2
./run-poc.sh
يقوم المُشغِّل بـ:
copyescape-macos-pocv2:local.COPYESCAPE_HOST_HOME=$HOME./watched/file.txt قابلًا للقراءة داخل الحاوية.docker cp باستخدام CLI المعيبة.~/pwnd../run-poc.sh 10 # 10 intentos (la carrera es probabilistica)
./run-poc.sh --cleanup # 5 intentos y borra la imagen al final
[run-poc] CLI vulnerable: 29.6.1
[run-poc] Intento 1/5 (contenedor copyescape-pocv2-NNNN-RRRR)
[run-poc] /watched/file.txt lee: top-level file
[run-poc] docker cp (CLI 29.6.1)...
[run-poc] EXITO en intento 1: se creo /Users/<tu-usuario>/pwnd
Contenido del marcador: COPYESCAPE_MACOS_DEMO
[run-poc] Logs del monitor:
[monitor] plataforma=macos target_dir=/Users/<tu-usuario> target_file=/Users/<tu-usuario>/pwnd quiet_ms=400 trigger=aaa.txt
[monitor] pivoted /watched/file.txt/escape -> /Users/<tu-usuario>
[run-poc] Resumen: 1 exito en 1 intento(s).
rm -f -- "$HOME/pwnd"
rm -rf -- poc-v2/file.txt
docker image rm copyescape-macos-pocv2:local
بمجرد اكتمال السلسلة، تُعدّ العناصر التالية دليلًا:
الفكرة والاستراتيجية تعودان إلى masasron: سباق TOCTOU ضد مُنتِج tar، التمويه عبر LD_PRELOAD، طُعم 16 MiB، الالتفاف باستدعاءَي rename(2)، والبنية escape/ + .swap-escape. بدون ذلك التصميم، لما وُجدت إعادة الكتابة هذه. المخطط المفاهيمي مطابق.
تقريبًا 15-20 % هي أسطر متطابقة حرفيًا (أدوات مساعدة قصيرة وثوابت التخطيط). والباقي كود جديد.
docker cp من حاوية لا يُوثَق بها، وأبدًا بصلاحيات root على Linux.docker cp أثناء الاستخراج، أو حظر ملفات tar على مستوى البرنامج الخفي إذا احتوت إدخالات فرعية تحت اختصار رمزي.موزَّع بموجب رخصة MIT. راجع ملف LICENSE لمزيد من التفاصيل.
/Users/<usuario>/usr/binrename(2)escape متبوعًا بإدخال فرعي escape/<basename>؛ CLI المعيبة تنشئ الاختصار الرمزي على العميل وتستخرج الإدخال الفرعي من خلاله، فتُكتب خارج الوجهة التي اختارها المستخدم.| المورد | الدليل المتوقع |
|---|
~/pwnd | ملف بمحتوى COPYESCAPE_MACOS_DEMO\n |
poc-v2/file.txt/ | دليل (ليس ملفًا) يحتوي escape -> /Users/<tu-usuario> (اختصار رمزي) و aaa.txt (16 MiB) |
docker logs <contenedor> | سطر pivoted /watched/file.txt/escape -> /Users/<tu-usuario> |
CLI 29.7.2 (docker cp مع CLI مصحَّحة) | العلامة لا تُنشأ؛ يفشل إثبات المفهوم |
| الملف | الأسطر الأصلية | الأسطر الجديدة | التغيير |
|---|
watched_preload.c | 89 | 257 | أُعيدت كتابته: أغلفة openat2, fstatat, statx, stat64, lstat64, open64, fopen64؛ مُنشئ آمن للخيوط؛ حارس ضد dlsym == NULL؛ O_TMPFILE |
monitor.c (macos + linux) | 259 + 233 = 492 (مكررة) | 344 (موحَّد) | أُعيدت كتابته وتوحيده مع -DCOPYESCAPE_TARGET_*؛ تراجع في try_pivot؛ exit_quiet_ms و trigger قابلان للضبط؛ كشف قوي |
entrypoint.sh | 3 | 5 | env -u LD_PRELOAD بدلًا من unset |
Dockerfile | 22 | 43 | موحَّد؛ تثبيت digest؛ --platform؛ بدون -Werror؛ -static؛ LABEL |
demo-macos.sh → run-poc.sh | 88 | 166 | أُعيدت كتابته: إعادة محاولات السباق؛ سجلات عند الفشل؛ --cleanup؛ تحقق مُوكَل إلى الثنائي |