首页
社区
课程
招聘
[原创]SUID提权:CVE-2021-4034漏洞全解析
发表于: 2022-2-22 23:17 9631

[原创]SUID提权:CVE-2021-4034漏洞全解析

2022-2-22 23:17
9631

本篇是SUID提权系列文章的第三篇。建议大家按顺序先阅读前两篇铺垫。

 

第一篇我们讲解了SUID提权的基础知识,linux系统的用户、文件权限与进程凭证。

 

第二篇我们讲解了SUID提权的原理,只要让具有SUID-root权限的程序加载我们的提权so,我们就赢了。

 

第三篇我们来讲解CVE-2021-4034漏洞的原理以及利用过程。本文会按照下面主题进行分享:

  • 什么是CVE?
  • CVE-2021-4034介绍
  • 漏洞分析
  • 漏洞利用
  • 漏洞修复

0x1 什么是CVE?

CVE的英文全称是Common Vulnerabilities & Exposures(公共漏洞和暴露)。

 

官网:
db3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6X3g2Q4x3X3g2E0K9i4c8J5k6g2)9J5k6h3!0J5k6#2)9J5c8W2!0q4x3#2)9^5x3q4)9^5x3R3`.`.

0x2 CVE-2021-4034介绍

CVE-2021-4034是一个SUID提权漏洞,利用具有SUID-root权限的pkexec,精心构造参数及运行环境,使其加载我们准备好提权的so。

 

CVE-2021-4034:
9fdK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6X3g2Q4x3X3g2E0K9i4c8J5k6g2)9J5k6h3!0J5k6#2)9J5c8X3y4Y4K9g2)9J5k6r3u0A6L8W2)9J5c8X3y4$3k6h3&6S2L8h3g2Q4x3X3g2U0k6$3W2Q4x3@1k6F1j5h3#2W2i4K6y4p5b7#2k6q4i4K6u0V1x3U0l9J5x3g2)9J5k6o6b7H3x3K6b7`.

0x3 漏洞分析

看看如何让pkexec加载任意so。我们需要准备一份pkexec源码。

 

pkexec源码:
139K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2X3M7X3g2W2k6r3g2K6K9%4c8G2M7q4)9J5k6h3!0J5k6#2)9J5c8Y4y4G2k6Y4c8%4j5i4u0W2i4K6u0r3M7r3!0D9K9$3W2@1i4K6u0r3M7X3g2D9k6h3q4K6k6i4y4Q4x3V1k6H3L8$3I4C8K9i4c8Q4x3X3b7H3i4K6u0W2x3e0t1H3i4K6u0W2N6r3q4J5i4K6u0W2k6%4Z5`.

 

查看src/programs/pkexec.c文件。部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
435 main (int argc, char *argv[])
436 {
...
534   for (n = 1; n < (guint) argc; n++)
535     {
...
568     }
...
610   path = g_strdup (argv[n]);
...
629   if (path[0] != '/')
630     {
...
632       s = g_find_program_in_path (path);
...
639       argv[n] = path = s;
640     }

我们来分析一下这一段代码,
534-568行:main函数处理命令行参数。
610-640行:pkexec在 PATH 环境变量的目录中搜索要执行的程序,如果其路径不是绝对路径,argv[n] = path = s。

 

不幸的是,如果命令行参数argc的数量为0,那么:

  • 在第 534 行,n赋值为1;
  • 在第 610 行,从argv[n]也就是argv[1]越界读取指针路径;
  • 在第 639 行,指针s被越界写入argv[1]。

但是从这个越界的argv[1]中读取和写入的到底是什么?要回答这个问题,我们必须简短地离题。我们需要了解execve函数,函数原型如下:

1
int execve(const char *filename, char *const argv[], char *const envp[]);

当我们使用execve函数启动一个新程序时,内核将我们的参数、环境变量字符串和指针(argv和envp)复制到新程序堆栈的末尾;
例如execve("/usr/bin/pkexec",{"program","-option",...},{"value","PATH=name",...})的内存布局如下:

1
2
3
4
|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
"program" "-option"           NULL      "value" "PATH=name"          NULL

显然,因为argv和envp指针在内存中是连续的,如果argc为0,那么越界argv[1]实际上是envp[0],指向我们的第一个环境变量“value”的指针。所以:

  • 610行:将要执行的程序的路径从argv[1](即envp[0])中越界读取,并指向“value”;
  • 632行:这个路径“value”被传递给 g_find_program_in_path函数,在我们的 PATH 环境变量的目录中搜索一个名为“value”的可执行文件;如果找到这样的可执行文件,则将其完整路径返回给pkexec第 632 行的指针s;
  • 639行:这个完整路径被越界写入argv[1](即envp[0]),从而覆盖了我们的第一个环境变量。

下面两段话,值得多读几篇,本漏洞的核心:

  • 如果我们的PATH环境变量是“PATH=name”,并且如果目录“name”存在(在当前工作目录中),并且包含一个名为“value”的可执行文件,那么指向字符串“name/value”的指针将被越界写入envp[0];

  • 如果我们的PATH是“PATH=name=.”,并且如果目录“name=.” 存在并包含一个名为“value”的可执行文件,那么指向字符串“name=./value”的指针将被越界写入envp[0]。

0x4 漏洞利用

利用代码:
3b7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6T1k6i4u0V1j5i4k6Q4x3V1k6o6g2V1g2Q4x3X3b7J5x3o6t1I4i4K6u0V1y4o6l9K6y4l9`.`.

 

第639行越界写入后不久,在第702行将完全清除其环境变量。我们需要精心选择一个环境变量进行利用,这里就考验大家的知识广度了,此漏洞很早就被发现,但是成功利用是在最近。

1
2
3
4
5
6
7
8
9
10
11
12
13
639       argv[n] = path = s;
...
657   for (n = 0; environment_variables_to_save[n] != NULL; n++)
658     {
659       const gchar *key = environment_variables_to_save[n];
...
662       value = g_getenv (key);
...
670       if (!validate_environment_variable (key, value))
...
675     }
...
702   if (clearenv () != 0)

为了将错误消息打印到stderr,pkexec调用g_printerr函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
88 log_message (gint     level,
89              gboolean print_to_stderr,
90              const    gchar *format,
91              ...)
92 {
...
125   if (print_to_stderr)
126     g_printerr ("%s\n", s);
...
383 validate_environment_variable (const gchar *key,
384                                const gchar *value)
385 {
...
406           log_message (LOG_CRIT, TRUE,
407                        "The value for the SHELL variable was not found the /etc/shells file");
408           g_printerr ("\n"
409                       "This incident has been reported.\n");

g_printerr函数通常打印UTF-8错误消息,但如果环境变量CHARSET不是UTF-8,它可以在另一个charset中打印消息(注意:CHARSET不是安全敏感的,它不是“不安全”的环境变量)。要将消息从UTF-8转换为另一个字符集,g_printerr函数调用了glibc的函数iconv_open。

 

要将消息从一个字符集转换为另一个字符集,iconv_open函数执行了小型共享库;通常,这些三胞胎(源字符集、目的字符集和库名称)从默认配置文件/usr/lib/gconv/gconv-modules中读取。环境变量GCONV_PATH可以强制iconv_open函数读取另一个配置文件;当然,GCONV_PATH是“不安全”的环境变量之一(因为它会导致执行任意库),但被ld.so从SUID程序的环境变量中移除。

 

不幸的是,CVE-2021-4034允许我们将GCONV_PATH重新引入pkexec的环境,并以root用户身份执行我们自己的共享库。

0x5 漏洞修复

去掉pkexec的SUID-root权限就好了

1
chmod 0755 /usr/bin/pkexec

欢迎大家使用常用聊天软件关注、点赞、评论交流~


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 9113
活跃值: (5969)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark  这个必须支持,好文字值得学习,感谢感谢
2022-2-24 05:10
0
游客
登录 | 注册 方可回帖
返回