
CVE-2021-3156 POC و Docker وكتابة تقرير التحليل
[toc]
رقم الثغرة: CVE-2021-3156
درجة الثغرة:
المنتج المتأثر: Linux sudo
النطاق المتأثر: 1.8.2-1.8.31sp12; 1.9.0-1.9.5sp1
شروط الاستغلال: محلي على Linux؛ sudo مع SUID وقابل للتشغيل
تأثير الاستغلال: رفع الامتيازات محليًا
الحصول على المصدر: https://www.sudo.ws/getting/source/
بيئة Docker: chenaotian/cve-2021-3156
لقد قمت ببناء Docker الخاص بي، والذي يوفر:
كل شيء موجود في الدليل /root:
image-20220124223312224
اختبار exp:``` cd exp su test ./exp whoami
انظر محتوى التصحيح في [بعض أوامر التصحيح](#一些调试命令)
## مبدأ الثغرة
تشغيل الثغرة payload```shell
sudoedit -s '\' `python3 -c "print('A'*80)"`
تحليل الشفرة المصدرية (sudo-1.8.21): أولاً، الدالة main في sudo.c (sudo.c: 133):```c int main(int argc, char *argv[], char *envp[]) { int nargc, ok, status = 0; char **nargv, **env_add; char **user_info, **command_info, **argv_out, **user_env_out; struct sudo_settings *settings; struct plugin_container *plugin, *next; sigset_t mask; debug_decl_vars(main, SUDO_DEBUG_MAIN)
··· ···
··· ···
/* Parse command line arguments. */
//在这里处理输入参数,设置sudo_mode
sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add);
··· ···
··· ···
switch (sudo_mode & MODE_MASK) {
··· ···
··· ···
case MODE_EDIT:
case MODE_RUN:
ok = policy_check(&policy_plugin, nargc, nargv, env_add,
&command_info, &argv_out, &user_env_out);
··· ···
··· ···
}
··· ···
··· ···
}
- أولاً، يتم استدعاء دالة `parse_args` لمعالجة المعاملات التي أدخلناها. في الواقع، هنا أدخلنا فقط `-s` ولا يوجد شيء يمكن تعيينه، حيث يتم تعيين `sudo_mode` إلى `MODE_EDIT` و `MODE_SHELL`.
- ثم، بناءً على اختلاف `sudo_mode`، يقوم `MODE_EDIT` باستدعاء `policy_check`
بعد ذلك، دالة `policy_check` في `sudo.c` (sudo.c: 1136):```c
static int
policy_check(struct plugin_container *plugin, int argc, char * const argv[],
char *env_add[], char **command_info[], char **argv_out[],
char **user_env_out[])
{
··· ···
··· ···
ret = plugin->u.policy->check_policy(argc, argv, env_add, command_info,
argv_out, user_env_out);
···
}
تم استدعاء دالة الاستدعاء plugin->u.policy->check_policy، ويمكن تصحيح الأخطاء لرؤية الدالة الحقيقية لهذه الدالة:
image-20220123113326096
الدالة المستدعاة هي sudoers_policy_check في policy.c (policy.c: 760):```c
static int
sudoers_policy_check(int argc, char * const argv[], char *env_add[],
char **command_infop[], char **argv_out[], char **user_env_out[])
{
··· ···
exec_args.argv = argv_out;
exec_args.envp = user_env_out;
exec_args.info = command_infop;
ret = sudoers_policy_main(argc, argv, 0, env_add, &exec_args);
··· ···
··· ···
}
ثم تم استدعاء دالة `sudoers_policy_main` في `sudoers.c` (`sudoers.c: 224`):```c
int
sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
void *closure)
{
··· ···
··· ···
/*
* Make a local copy of argc/argv, with special handling
* for pseudo-commands and the '-i' option.
*/
if (argc == 0) {
··· ···
} else {
/* Must leave an extra slot before NewArgv for bash's --login */
NewArgc = argc;
NewArgv = reallocarray(NULL, NewArgc + 2, sizeof(char *));
··· ···
}
memcpy(++NewArgv, argv, argc * sizeof(char *));
NewArgv[NewArgc] = NULL;
··· ···
}
}
··· ···
cmnd_status = set_cmnd();
··· ···
··· ···
··· ···
}
هنا تم تعيين بعض المتغيرات العامة، NewArgc و NewArgv كما يلي، إنها في الواقع المعاملات المُدخلة.
image-20220123113819116
ثم الدخول إلى دالة set_cmnd في sudoers.c (sudoers.c: 796):```c static int set_cmnd(void) { ··· ··· ··· ···
/* set user_args */
if (NewArgc > 1) {
char *to, *from, **av;
size_t size, n;
/* Alloc and build up user_args. */
//根据参数总长度计算size, 后续malloc 申请,没有问题
for (size = 0, av = NewArgv + 1; *av; av++)
size += strlen(*av) + 1;
if (size == 0 || (user_args = malloc(size)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_int(-1);
}
if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
/*
* When running a command via a shell, the sudo front-end
* escapes potential meta chars. We unescape non-spaces
* for sudoers matching and logging purposes.
*/
//将所有参数拷贝到一起放到堆中,逻辑是遇到'\'加非空格类型字符则只拷贝非空格字符
//但这里\x00 并不算空格类型字符
//他没有考虑参数如果只有一个'\'或以'\'结尾并且下两个字符后就是另一个字符串情况
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
if (from[0] == '\\' && !isspace((unsigned char)from[1]))
from++;
*to++ = *from++;
}
*to++ = ' ';
}
*--to = '\0';
}
··· ···
}
}
··· ···
··· ···
}
يحدث التجاوز هنا أيضًا، كما يتضح من التعليقات في الكود، يحدث تجاوز سعة الكومة عند النسخ إلى الكومة. المعنى الأصلي لهذا الكود ليس من الصعب فهمه: نسخ جميع الوسائط من `NewArgv` إلى الكومة، مفصولة بمسافات، وعند مواجهة `\ + حرف غير مسافات`، يتم نسخ ذلك الحرف فقط.
**لكنها لم تأخذ في الاعتبار حالة أن عنصرًا من `NewArgv` ينتهي بـ `\`، أي بنية `\+\x00`، و `\x00` لا ينتمي إلى الأحرف المسافات (غريب)! أي أنه بعد نسخ `\x00` إلى الكومة، يزداد المتغير `from` بمقدار (يزيد مرتين في حلقة واحدة) ويتجاوز فرصة جملة `while` التي تتحقق من علامة النهاية `\x00`، معتبرًا أن الوسائط لم يتم نسخها بعد، ويواصل النسخ إلى الخلف حتى يصادف `\x00` التالي.**
في هذا السيناريو، يمكن رؤية أن `\+\x00` يتبعه مباشرة الوسيط التالي `A*80`، لذا سيستمر النسخ إلى نهاية `A*80`. لكن لا تنسَ أنه سيتم لاحقًا معالجة الوسيط `A*80` بشكل فعلي، وسيتم نسخه مرة أخرى، لذلك تم نسخ `A*80` مرتين هنا، ولكن تم تخصيص القطعة (chunk) بناءً على حجم سلسلة واحدة من `A*80` فقط، وهو ما يتجاوز بكثير الطول المخصص للقطعة.
image-20220123113907744
ثم يحدث التجاوز، قبل النسخ:
image-20220123114036691
بعد النسخ:
image-20220123114137794
مسار تشغيل الثغرة الكلي هو (يمكنك ببساطة تعيين نقاط توقف عند هذه الدوال أثناء التصحيح):
- sudo.c : main
- sudo.c : policy_check
- policy.c : sudoerrs_policy_check
- sudoers.c : sudoers_policy_main
- sudoers.c : set_cmnd
- sudoers.c : 859
## مبدأ استغلال الثغرة
تمت الإشارة إلى [blasty/CVE-2021-3156](https://github.com/blasty/CVE-2021-3156)، **لكن طريقة تخطيط الكومة الخاصة به نادرة الحدوث؛ هنا تم تحليل طريقة تخطيط الكومة بالتفصيل**. من خلال تمرير متغيرات البيئة `LC_*` لتخطيط الكومة، ثم جعل القطعة الفائضة (vuln chunk) تغطي تمامًا الهيكل `service_user` الذي تحتاج دالة `nss_load_library` إلى تحميله كـ `.so`، واستبدال سلسلة اسم `.so` داخل الهيكل، ثم جعل البرنامج يقوم بتحميل الـ `.so` المحدد من قبلنا لتنفيذ تعليمات برمجية عشوائية.
على الرغم من أن المنطق يبدو واضحًا، إلا أن التفاصيل التي يجب التعامل معها لا تزال صعبة نوعًا ما:
1. هياكل البيانات والآليات ذات الصلة في `nss_load_library`
2. كيف يقوم `setlocale` بتخطيط الكومة عبر متغيرات البيئة `LC_*`
بعد ذلك، سنشير إلى القطعة التي يمكن أن يحدث فيها التجاوز عند حدوث الثغرة باسم `vuln chunk`، والهدف من التجاوز باسم `target chunk`.
### مبدأ `nss`
أولاً، لننظر إلى الكود الأساسي للاستغلال: