CVE-2021-4034(PwnKit)深度解析:pkexec本地提权漏洞原理与修复实践

发布时间:2026/9/20 3:39:26
CVE-2021-4034(PwnKit)深度解析:pkexec本地提权漏洞原理与修复实践 2022年1月底CVE-2021-4034 在安全圈里炸开的时候我第一反应不是去找公开的利用代码而是先把发行版安全公告从头到尾翻了一遍。原因很简单这个被叫做 PwnKit 的漏洞影响面实在太吓人——只要系统里存在 pkexec 这个默认安装的 setuid 程序几乎所有主流 Linux 发行版都处在风险之中而且一个普通本地用户不需要任何特殊条件就能把权限直接抬到 root。这篇文章我会从 polkit 和 pkexec 的职责讲起把 CVE-2021-4034 的原理、影响范围、自查方法、修复方案和排查经验完整过一遍。系统管理员、安全运维、还有对 Linux 底层机制感兴趣的读者都能从里面找到可以直接落地的内容。我不会在文章里贴完整的攻击 payload也不会教你怎么拿现成工具去打生产环境而是把重点放在“怎么发现自己中招了”“怎么补上这个洞”“补完之后怎么确认没留下后患”上。1. 漏洞全景CVE-2021-4034 不是什么“普通本地提权”1.1 polkit 和 pkexec 在系统里扮演什么角色polkit 是一个系统授权框架名字来自 PolicyKit功能是协调非特权进程和特权进程之间的权限访问。很多桌面用户对它的印象就是“弹窗输密码”比如在图形界面里修改网络配置、添加用户、更新系统时系统会弹出一个授权框要求输入管理员密码。这个弹窗背后的核心工具之一就是 pkexec。pkexec 是一个以小权限位辅助程序方式安装的二进制默认路径在/usr/bin/pkexec它允许普通用户在符合 polkit 策略的前提下以 root 身份执行某个命令。它和 sudo 经常被放在一起比较但两者定位不同。sudo 的授权规则主要看用户组和命令白名单而 pkexec 会给桌面环境和系统服务提供更灵活的 DBus 策略判断。很多服务启动脚本、桌面组件、甚至是某些安装器都会直接调用 pkexec所以它几乎是 Linux 桌面发行版和部分服务器变体上的常驻组件。真正要紧的地方在于pkexec 是一个带有 setuid 位的二进制文件也就是说当普通用户执行它时内核会临时把运行权限抬高到文件所有者 root。这类程序一旦存在可被利用的缺陷后果就是本地提权。CVE-2021-4034 恰恰就是这样一个缺陷它藏得非常深影响时间也延续了十几年。1.2 漏洞影响面与危害评估CVE-2021-4034 由 Qualys 研究团队在 2021 年末发现2022 年 1 月对外披露随后社区给它起了个形象的名字叫作 PwnKit。根据公开分析这个漏洞影响所有在修复补丁之前的 polkit 版本也就是说从 2009 年引入相关代码开始几乎所有主流 Linux 发行版全都在范围内。Ubuntu、Debian、CentOS、RHEL、Fedora、Arch Linux、openSUSE、Alpine 等发行版都发布了对应的安全公告和更新包。官方对它的 CVSS 评分是 7.8从数字上看不算最高的“满分洞”但实际危害要严重得多。原因很简单利用条件太低了。攻击者只需要在目标机器上拥有一个本地低权限账号或者通过 Web 漏洞、服务漏洞等方式拿到一个低权限 shell就能尝试提权到 root整个过程不需要密码、不需要用户交互、不需要额外的内核漏洞配合。这也是我在处置这个漏洞时最担心的一点很多团队会重点排查 sudo、内核、Web 中间件却忽略 pkexec 这类“小工具”。而这些小工具一旦是 setuid root往往就是攻击者最喜欢的那块跳板。2. 漏洞原理拆解一个“空参数列表”如何撬动root权限2.1 关键前提execve 允许参数为空的“畸形调用”要理解 CVE-2021-4034首先要重新认识 Linux 的系统调用 execve。标准用法是传入三个参数要执行的程序路径、参数数组 argv、环境变量数组 envp。按照 C 语言规范argv 数组的第一个元素通常是程序名argc 是参数个数正常来说 argc 至少为 1。但这是“约定”不是“强制”。Linux 内核在 execve 时并不会检查参数个数是否大于 0也不会强制要求 argv 不能为 NULL。你可以用类似execve(/usr/bin/pkexec, NULL, envp)的方式去启动一个程序内核照样会把它加载起来然后进入目标程序的 main 函数。大多数程序会自己在入口处判断 argc 和 argv如果没有参数就退出或打印帮助信息。问题在于pkexec 的 main 函数在早期实现里把环境变量和参数数组之间的边界当成了默认存在的安全假设。当 argc 为 0 时它仍然继续访问argv[1]、argv[0]这些位置而 C 语言数组越界访问是直接读写内存地址的。于是本应该访问参数数组的位置实际访问到了紧贴着参数数组的环境变量数组头上。打个比方这就像快递站默认每个包裹袋上一定贴了收件人名单结果遇到一个空袋子工作人员不去检查袋子是否为空而是直接从旁边上一个包裹袋上撕了一张名字下来后面整个分拣流程都被这张错误的名字带着跑偏了。2.2 从越界读写到“可控环境变量”的完整链路pkexec 的漏洞链条比单纯越界读要复杂一点但核心就是“越界读写”四个字。由于 argc 为 0 时 argv 数组越界攻击者可以精心布置环境变量的顺序和内容使得 pkexec 在访问不存在的参数时实际上读到的是攻击者控制的环境变量字符串。公开的技术分析显示这条攻击链最终会走到两个方向。第一个方向是让 pkexec 把这个攻击者控制的“伪参数”当成要执行的命令名然后通过 PATH 查找机制找到并执行攻击者放在指定目录下的恶意程序。第二个方向是利用 GLib 在处理字符集转换时会读取GCONV_PATH、CHARSET这类环境变量的特性诱导 pkexec 去加载攻击者提前放置好的动态共享库。这两种方向最终都会导致同一个结果setuid root 进程内执行了攻击者提供的代码。权限提升就是顺理成章的事情了。补丁修复的核心也非常直接在进入任何参数处理逻辑之前先检查 argc 是否小于 1如果是就直接报错退出。这样就不再给攻击者留下越界读写的窗口。2.3 为什么公开性高不需要任何特殊权限和条件CVE-2021-4034 真正让防守方头疼的地方不是它的技术门槛有多高而是攻击门槛低到离谱。它不需要物理接触不需要属于某个特殊用户组不需要知道 root 密码甚至不需要目标系统有图形桌面。只要 pkexec 存在并且 setuid 位没有被人为移除理论上任何一个本地代码执行能力都可能是通往 root 的通道。很多文章喜欢强调它“潜伏了 12 年”我觉得这个说法其实是在提醒我们另一件事代码审计不能只看热门的攻击面还要关注那些长期被忽略的基础组件。pkexec 这种程序平时没有人会主动去执行但它一直在那里以 root 权限待命。攻击者只要找到一个入口就能用它完成最后一击。我还记得当时团队内部讨论的时候有人问“既然利用这么简单是不是所有内网服务器都要立刻停机整改”。我的建议是不要慌乱按风险顺序推进暴露面大、被攻破可能性高、又无法立刻升级的机器先做临时缓解可以升级的机器走变更流程尽快打补丁同时安排人把暴露在公网或信任边界上的系统先排查一遍。3. 自查、复现与验证不把线上环境当靶场3.1 快速自查当前系统是否受影响如果你现在拿到一台 Linux 机器想知道它有没有暴露在 CVE-2021-4034 下最快的方法是看 pkexec 是否存在以及它的包版本是否已经包含了修复补丁。先确认 pkexec 是否安装ls -l /usr/bin/pkexec正常情况下会看到/usr/bin/pkexec的权限位是-rwsr-xr-x其中第一个s表示 setuid 位存在。如果连这个文件都没有那至少这台机器没有直接暴露 pkexec 这个攻击面。但要注意有些发行版可能把 pkexec 放在其他路径最好用which pkexec或者command -v pkexec确认一下。再看版本pkexec --version不过版本号只能作为参考不能只靠它下结论。比如 polkit 版本显示 0.105不代表一定没补过发行版可能打了 backport 补丁版本号没变但代码已经修复。最可靠的方法是按发行版查安全公告并对比当前包版本与公告中的修复版本。在 Ubuntu/Debian 系上apt-cache policy policykit-1 apt changelog policykit-1在 RHEL/CentOS/Fedora 系上yum info polkit yum --showduplicates list polkit另外现在很多漏洞管理平台都有 CVE 到软件包版本的对应关系可以直接导入资产清单扫描比人工比对高效得多。3.2 隔离环境中的验证思路如果你真的想确认一台机器是否存在漏洞不建议直接在生产环境跑公开的 POC。公开 POC 虽然能快速证明漏洞存在但它本质上是攻击行为可能造成系统崩溃、数据损坏还会在审计日志里留下敏感痕迹。正确的做法是在隔离环境里复现。找一台虚拟机或者一次性容器使用未修复的旧系统镜像搭建目标环境创建两个用户一个普通低权限用户一个 root 用户。然后以低权限用户的身份写一个小程序通过 execve 调用/usr/bin/pkexec并且故意传一个空参数数组。这里我不准备贴完整的测试代码一是为了避免给不熟悉安全边界的人提供可执行的攻击脚本二是这类利用代码在公开漏洞库中很容易找到真正需要你关心的不是怎么打而是打完之后怎么看结果。修复前的系统pkexec 可能在异常调用后出现段错误、非预期输出或者成功创建了攻击者控制的文件修复后的系统通常会直接打印一条错误类似pkexec must be called with at least one argument然后退出。验证的最终目的不是证明“我能提权”而是确认“这台设备的补丁状态到底是什么”。如果只是想判断风险用漏洞扫描器或厂商安全公告足够了。3.3 补丁前后行为对比与判定补丁前后最直观的区别其实在不加任何参数直接执行 pkexec 时就能看出来。修复后的版本会第一时间拒绝执行并提示参数数量不足修复前的版本可能会被带入后续逻辑行为表现跟具体发行版、GLib 版本强相关。但这里有一个坑你不能因为某台机器执行pkexec什么都没发生就认定它没有漏洞。不同发行版源码打了不同的补丁甚至有的版本在早期就加了其他参数校验行为差异会很大。所以我的建议是把行为判断当成辅助手段真正用来兜底的还是包版本对比和 CVE 扫描结果。尤其是大规模资产盘点时用脚本统一收集/usr/bin/pkexec的包名、版本、发行版信息再和官方公告对照才是效率最高的方式。4. 修复与加固拿回主权的具体操作4.1 按发行版升级polkit的完整命令修复 CVE-2021-4034 最标准的手段就是升级 polkit 包升级完成后pkexec 的代码里就会包含 argc 检查逻辑。下面是主流发行版的实际操作命令都在 root 权限下执行。Debian / Ubuntuapt update apt install --only-upgrade policykit-1RHEL / CentOS 7yum update polkitRHEL / CentOS 8/9、Fedoradnf update polkitArch Linuxpacman -Syu polkitopenSUSEzypper update polkitAlpineapk update apk upgrade polkit升级完成后并不一定需要立刻重启系统但建议重启一次或者至少重启 polkit 相关服务、重新登录桌面会话。因为旧的 pkexec 进程可能还在内存里运行如果它在升级前的状态已经被污染重启是让所有新进程都使用修复后二进制的最稳妥方式。4.2 无法立即升级时的临时缓解措施总会有不能马上升级的情况比如变更窗口没到、业务依赖旧版本、或者生产环境有严格的审批流程。这时候可以先做临时缓解核心思路就是让 pkexec 失去 setuid root 的能力。执行chmod 0755 /usr/bin/pkexec执行完用ls -l /usr/bin/pkexec确认权限位从-rwsr-xr-x变成-rwxr-xr-xsetuid 位被移除。这样普通用户执行 pkexec 时就只是以普通用户身份运行无法借它获取 root 权限。副作用是依赖 pkexec 的图形授权功能会失效比如桌面环境里修改系统设置时无法弹出授权框。这个操作适合作为止损手段不代表长期解决方案。等升级窗口到来后记得把权限恢复回去chmod 4755 /usr/bin/pkexec恢复后继续确认权限位是-rwsr-xr-x。要特别提醒的一点是如果系统里存在其他带有 setuid 位的 pkexec 副本或者攻击者已经在漏洞利用期间放置了恶意版本单纯恢复权限是不够的。应急时宁可多花十分钟比对二进制哈希也不要为了省事直接chmod 4755收工。4.3 长期安全加固实践CVE-2021-4034 这类漏洞给我们的警示是系统中的 setuid 程序都是潜在的高价值攻击面。长期来看我建议把它纳入常规安全运维动作。第一建立 setuid 程序清单。用find / -perm -4000 -type f 2/dev/null把系统里所有带 setuid 位的二进制找出来逐一确认是否必要不必要的就移除 setuid 位。第二部署 SELinux 或 AppArmor。这类安全模块不能阻止漏洞本身被利用但能限制漏洞利用后攻击者的活动范围增加横向移动和持久化的成本。第三严格限制本地代码执行。对服务器来说尽量不给普通业务用户开放交互式 shell所有操作审计留痕。对终端来说禁用用户写入临时目录后自动执行的常见目录也能降低部分利用链的成功率。第四订阅漏洞情报。不要只盯着 CVE 数据库最好直接订阅各发行版的安全公告邮件列表有重大安全更新时第一时间评估影响面。5. 踩坑记录与排查经验5.1 升级后pkexec“罢工”的排查我在实际处置中遇到过升级完 policykit-1 之后图形界面无法弹授权框的情况。当时第一反应是补丁冲突后来排查下来发现问题出在系统里手动改过/etc/polkit-1/localauthority下的规则文件新版 polkit 对文件格式的解析更严格之前老版本能容忍的写法现在直接报错。排查流程可以按下面几步走systemctl status polkit journalctl -u polkit --since 10 minutes ago重点看有没有Failed to parse、Authorization、Rule之类的关键字。如果是规则文件格式问题就对照新版本文档逐行检查。改之前一定要备份随手cp -a /etc/polkit-1 /etc/polkit-1.bak能让你少掉很多头发。还有个容易踩的坑CentOS 上执行完yum update polkit之后旧版本的 pkexec 进程还驻留在内存中。如果业务系统通过远程桌面或者常驻服务调用了 pkexec最好直接重启机器别指望所有进程内存里的依赖都能自动切换到新文件。5.2 日志和审计里能留下什么线索如果攻击者真的尝试利用 CVE-2021-4034系统日志里通常能找到一些蛛丝马迹。比如在 Debian/Ubuntu 上/var/log/auth.log可能出现 pkexec 相关的异常记录在 RHEL/CentOS 上重点看/var/log/secure。另外如果系统开了 auditd 审计可以用下面命令查 execve 调用ausearch -m EXECVE --start today畸形调用往往表现为参数数组为空或参数个数异常。虽然很多公开利用代码会在执行后清理临时文件和环境变量但内存审计、临时目录残留还是可能留下痕迹可以检查/tmp、/var/tmp里是否有可疑的新增目录比如名字看起来像gconv-modules或随机字符串的目录。日志和审计永远只是辅助不能当唯一证据。最稳妥的应急思路是先隔离受影响主机再做内存镜像和磁盘快照最后才开始排查和补丁升级。5.3 现场应急的几件小事还有几个在应急现场特别容易忽略的小动作写在这里提醒你。第一升级补丁之前先拍照保存现场。起码要执行一次date、last、who把当前登录用户记下来避免之后说不清是谁在什么时间做了操作。第二确认影响范围不要只看一台机器。把同镜像、同版本、同分发渠道的机器全部拉出来统一排查一遍。第三升级之后立即更换所有特权账户的密码和 SSH 密钥尤其是那些长期没轮换过的 root 密钥。万一提权过程中已经拿到过 root shell你不把后门清干净补丁打得再快也没用。第四如果业务允许重启是最好的收尾。重启可以让所有进程都加载修复后的二进制也能顺便验证系统在冷启动后是否还能正常工作。第五复盘的时候不要只写“补丁已升级”。要把这次漏洞为什么没提前发现、资产清单为什么不全、包版本为什么没有自动更新这些问题一起写进去。否则下次遇到同样的洞你还得再熬一个通宵。这次 PwnKit 的处置让我印象最深的不是漏洞本身有多聪明而是很多系统管理员习惯了给内核、数据库、Web 应用打补丁却常常忽略 pkexec 这种无人问津的小组件。但恰恰是这些带 setuid 的小程序因为长期拥有 root 身份反而成为提权攻击最精准的目标。我在后续安全基线里加了一条硬性要求所有 setuid 程序必须建立清单由专人负责维护并且订阅对应的漏洞情报。这一次踩了不少坑但好在 polkit 的补丁没有引起大面积业务中断算是万幸。希望这篇复盘能帮你少走一点弯路。