
تحليل تقني مفصل واستغلال عملي لثغرة CVE-2022-0492 في نواة لينكس للهروب من الحاويات عبر cgroup release_agent، مع إعداد مختبري خطوة بخطوة وإرشادات للتخفيف من المخاطر.
[toc]
رقم الثغرة: CVE-2022-0492
المنتج المتأثر: linux kernel - cgroup
الإصدارات المتأثرة: ~linux kernel 5.17-rc3
خطورة الثغرة: عندما لا يتم تفعيل إجراءات أمنية إضافية في الحاوية، يمكن الحصول على صلاحيات الجذر داخل الحاوية والهروب إلى المضيف.
على نظام لينكس بنواة تحتوي على الإصدار المتأثر، يمكن استخدام docker.
#关闭所有安全防护启动docker
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
تستخدم هذه المقالة docker كبيئة تجريبية.
طريقة استغلال هذه الثغرة أصبحت مألوفة بالفعل، لكن نقطة حدوث الثغرة هي غياب التحقق من الصلاحيات عند تعديل release_agent الخاص بـ cgroup، مما يؤدي إلى خفض عتبة استغلال الهروب بشكل أكبر (سابقًا كانت تتطلب صلاحية CAP_SYS_ADMIN، وهذه الثغرة لا تتطلب CAP_SYS_ADMIN). لمزيد من التفاصيل حول اختلاف شروط الاستغلال، انظر "شروط الاستغلال" أدناه.
بتحليل التصحيح، تم تعديل الدالة cgroup_release_agent_write وإضافة التحقق من الهوية. وهذا يعني أن release_agent الخاص بـ cgroup لم يعد مسموحًا للمستخدمين غير المصرح لهم بتعديله:

لذلك تم تحديد أن هذه الثغرة هي فشل في التحكم بالوصول.
cgroup هي اختصار لـ Linux Control Group، وهي إحدى وظائف نواة لينكس، تُستخدم لتقييد موارد مجموعة من العمليات والتحكم فيها وفصلها (مثل CPU، الذاكرة، الإدخال والإخراج للأقراص، إلخ).
تحتوي cgroup على الأنظمة الفرعية التالية:
devices صلاحيات الأجهزة على مستوى العملياتcpuset تخصيص عدد وحدات المعالجة (CPU) وعقد الذاكرة التي يمكن للعمليات استخدامهاcpu التحكم في نسبة استخدام المعالجcpuacct إحصائيات استخدام المعالج، مثل وقت التشغيل ووقت الخنق (throttled time)freezer إيقاف العمليات في Cgroup مؤقتًاnet_cls مع tc (التحكم في حركة المرور) لتقييد عرض النطاق الترددي للشبكةnet_prio تعيين أولوية حركة مرور الشبكة للعملياتhuge_tlb تقييد استخدام HugeTLBperf_event السماح لأداة Perf بإجراء فحوصات الأداء بناءً على مجموعات Cgroupأنظمة cgroup على المضيف موجودة تحت /sys/fs/cgroup، ويمكن رؤية أنظمة cgroup الفرعية المختلفة:

نظام cgroup الفرعي المقابل في docker هو عقدة فرعية لهذا cgroup على المضيف. عرض memory cgroup داخل docker:

العقدة المقابلة لاسم الحاوية في دليل docker على المضيف متطابقة تمامًا:

يُستخدم cgroup من خلال نظام الملفات، حيث يتم تركيب cgroup إلى دليل باستخدام mount، ويتفاعل cgroup معنا عبر نظام الملفات الافتراضي VFS. يتم عرض واجهة cgroup على شكل ملفات، ويمكن استخدام عمليات الملفات مباشرة لتعيين بعض معلمات cgroup.
mount -t cgroup -o memory cgroup /tmp/testcgroup

يمكن إنشاء عقدة فرعية من cgroup عن طريق إنشاء دليل فرعي تحت الدليل: mkdir /tmp/testcgroup/x.
كل نظام فرعي (subsystem) في cgroup يحتوي على معامل notify_on_release، وقيمة هذا المعامل من النوع Boolean، إما 1 أو 0، ويمكنهما تشغيل أمر وكيل التحرير (release agent) أو تعطيله على التوالي. إذا تم تفعيل notify_on_release (بقيمة 1)، فعندما لا يعد cgroup يحتوي على أي مهام (أي عندما يخرج آخر عملية في cgroup ويصبح PID في ملف tasks فارغًا)، سيقوم نواة النظام بتنفيذ محتوى الملف المحدد بواسطة معامل release_agent. ويتم تعديل قيمة notify_on_release عن طريق تعديل ملف notify_on_release.

تقع الثغرة عند تعديل release_agent. في الأصل، كان يكفي أن تتمكن من التعامل مع cgroup لتعديل release_agent، وكان استخدام cgroup يتطلب صلاحية CAP_SYS_ADMIN. لكن لاحقًا اكتشف الباحثون أنه يمكن الحصول على جميع الصلاحيات (capabilities) من خلال إنشاء namespace جديد باستخدام أمر unshare، وبذلك لم يعد تقييد CAP_SYS_ADMIN قائمًا، وانخفضت عتبة استغلال الثغرة بشكل كبير.
وظيفة أمر unshare هي إلغاء مشاركة namespaces المحددة الخاصة بالعملية الأب المشتركة، ثم تنفيذ البرنامج المحدد والانضمام إلى namespace الجديد. وما يهمنا في استغلال الثغرة هو أن namespace الجديد الذي ينشئه unshare يمتلك جميع الصلاحيات (capabilities) بما في ذلك CAP_SYS_ADMIN.

استغلال هذه الثغرة هو نفس طريقة الهروب التقليدية عبر CAP_SYS_ADMIN + cgroup release_agent، لكن شروط الاستغلال تختلف.
الفرق بين شروط استغلال هذه الثغرة وشروط الهروب التقليدي عبر release_agent هو:
release_agent التقليدي: يجب أن تمتلك الحاوية صلاحية CAP_SYS_ADMIN وألا يكون apparmor أو selinux مفعلًا.
cve-2022-0492: الحاوية بدون أي حماية (بتفصيل أكثر: ألا يمنع seccomp استخدام unshare، وألا يفعل apparmor وضع القراءة فقط لـ cgroup، وتعطيل selinux)، مع الحصول على صلاحيات الجذر داخل الحاوية. لا حاجة للحصول على CAP_SYS_ADMIN.
تجدر الإشارة إلى أن apparmor في docker يفعّل وضع القراءة فقط لـ cgroup افتراضيًا، وأن seccomp في docker يعطّل unshare بدون صلاحية CAP_SYS_ADMIN افتراضيًا. أما k8s فعادة ما تكون حاوياته بدون أي حماية افتراضيًا. عمومًا، نظرًا لأن الاستغلال سهل نسبيًا، يمكن تجربته في السيناريوهات المحددة.
بعد إصلاح الثغرة: وفقًا لشفرة التصحيح:

لتعديل ملف release_agent يجب توفر شرطين:
لذلك بعد إصلاح الثغرة، لم يعد بإمكان صلاحية CAP_SYS_ADMIN التي تم الحصول عليها عبر unshare تعديل release_agent، لأن namespace الجديد الذي تم الحصول عليه عبر unshare ليس namespace الجذر. لكن إذا كانت الحاوية تمتلك صلاحية CAP_SYS_ADMIN في الأصل، فلا يزال من الممكن استخدام هذه الطريقة للهروب.
إذا تم تشغيل docker مع المعامل --cap-add=SYS_ADMIN أو --privileged (حاوية مميزة)، فإنها تمتلك صلاحية CAP_SYS_ADMIN ولا نحتاج إلى الحصول عليها بشكل إضافي، مثل أمر التشغيل التالي:
#带有sys_admin 启动docker, 关闭apparmor(否则无法mount)
docker run --rm -it --cap-add=SYS_ADMIN --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
يمكن لـ docker الذي يمتلك صلاحية CAP_SYS_ADMIN الانتقال مباشرة إلى الخطوة التالية "تعديل release_agent". أما أمر تشغيل docker بدون صلاحية CAP_SYS_ADMIN وإعادة إنتاج الثغرة:
#关闭所有安全防护启动docker
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
بدون CAP_SYS_ADMIN، يمكن الحصول على صلاحية CAP_SYS_ADMIN عبر أمر unshare التالي:
unshare -UrmC --propagation=unchanged bash
namespace الجديد الذي تم الحصول عليه يمتلك جميع صلاحيات capabilities.
