2022年年初安全圈最炸裂的那条通告,CVE-2021-4034,也就是大家常说的 pkexec 本地提权漏洞。只要是任何允许普通用户登录的 Linux 机器,都存在被直接提权到 root 的风险,而且不需要你输入密码。这已经不是“高危”两个字能概括的了,对跑着业务的 CentOS 8.5 服务器来说,修复动作慢一天,风险就多一天。这篇文章把我实际处理这个漏洞的完整过程写出来,从漏洞原理、修复方案选择,到 CentOS 8.5 上的具体操作和踩坑记录,一次性说清楚。
1. 先说清楚:这个漏洞到底狠在哪
1.1 一句话讲明白漏洞原理
要理解这个漏洞,先得弄清楚 pkexec 是什么。pkexec 是 polkit 授权框架里的一个命令,作用是让普通用户临时以其他用户的身份执行某个程序,最常见的用法就是在图形界面里弹出授权框,让你输入管理员密码完成系统设置操作。它本身是个 setuid 程序,也就是说,执行它的进程能够临时获得 root 权限,这是它的设计功能。
CVE-2021-4034 的问题出在 pkexec 处理命令行参数时的一个越界写漏洞。安全研究员发现,pkexec 在解析参数时错误地处理了 argv[0] 为空的情况,导致程序发生越界写入,攻击者可以利用这个异常行为,把精心构造的环境变量写入到进程内存中,最终诱导系统加载恶意共享库,从而以 root 身份执行任意命令。整个过程不需要用户密码,也不需要交互,一个普通用户在自己的 shell 里敲几条命令就能完成提权。
1.2 影响范围比想象的大
这个漏洞被称为 PwnKit,重点在于“默认受影响”这四个字。polkit 是几乎所有主流 Linux 发行版的基础组件,从桌面版到服务器版都会预装。服务器上即使没有图形界面,也可能会因为某些管理工具、虚拟化组件或打印服务等依赖链而安装了 polkit。这意味着 CentOS 8.5、其他 Linux 发行版,甚至一些容器镜像,只要里面有 pkexec 且版本在受影响范围内,都存在风险。
更要命的是,触发这个漏洞的权限门槛极低。只要有任意一个本地用户账号,哪怕是那些被设置成 nologin、不能正常登录的账户,都能参与攻击。因为利用的是 pkexec 本身的解析逻辑,不依赖系统里的任何服务配置。所以判断一台机器是否要紧危处理,标准很简单:看看有没有本地用户账号,有没有 pkexec 二进制文件。有,就赶紧修。
2. 动手前先确认环境,别闭眼就敲命令
2.1 确认系统版本和 polkit 包状态
修复的前提是先搞清楚自己待的环境。使用以下命令确认系统版本和 polkit 安装情况:
cat /etc/redhat-release uname -a rpm -qa | grep -i polkit执行后通常会看到类似这样输出:CentOS Linux release 8.5.2111,以及polkit-0.116-11.el8、polkit-libs-0.116-11.el8、polkit-pkla-0.116-11.el8这几个关键包。0.116 这个版本就在受影响范围内,需要升级到打了补丁的版本。在 CentOS 8 的修复渠道里,修复版本通常以0.116-11.el8_5.4或更高版本号的形式出现,最终以仓库实际推送的版本为准。
2.2 检查依赖关系,评估升级影响
升级前最怕的是改出新的问题。先花一分钟看看谁依赖 polkit,避免升级完导致别的服务起不来。用下面两条命令检查:
rpm -q --whatrequires polkit rpm -q --whatrequires pkexec我处理过的环境里,最常见的依赖方是libvirt、gnome-settings-daemon、udisks2这类组件。一般情况升级 polkit 不会有破坏性影响,但如果输出里有某些私有软件直接硬编码调用了旧版本的接口,那升级后就要额外做验证。这一步不能省,尤其是生产环境。
2.3 准备好回滚手段
无论网络环境多好,都要想好失败怎么退回来。最稳妥的方式是对虚机先打快照,云主机就用控制台的磁盘快照功能,耗时基本可以忽略。如果是物理机或者不方便打快照,至少把当前版本的 rpm 包留底:
mkdir -p /root/polkit-backup rpm -qa | grep -i polkit | while read pkg; do rpm -ql "$pkg" > /root/polkit-backup/$pkg.filelist done yumdownloader polkit polkit-libs polkit-pkla --destdir=/root/polkit-backup这个目录保留好,万一升级后出现兼容性问题,可以直接rpm -Uvh /root/polkit-backup/polkit-*.rpm --oldpackage回滚。备份不占多少空间,却能在紧急时候救你一命。
3. 修复方案怎么选:补丁升级和临时缓解
3.1 首选方案:升级到修复版本的 polkit
最理想的修复方法是直接安装官方修复版本。现在各个发行版都已经发布了打了补丁的 polkit 包,CentOS 8.5 环境也适用。
不过要特别提醒一点:CentOS Linux 8 官方仓库的生命周期在 2021 年底结束,而 CVE-2021-4034 的公开和修复通告在 2022 年 1 月。这导致如果你只保留了原始官方源,可能刷不到这个更新。有几种应对方式:
- 如果服务器之前就配置了国内镜像源,并且镜像源保留了 RHEL 8.5 对应的安全更新仓库,直接
dnf update polkit -y通常能拉到修复包。 - 如果刷不到,可以把 BaseOS 和 AppStream 源指向仍在维护的兼容仓库,或者直接使用由社区/第三方提供的 rebuild 修复包。
- 部分云厂商的镜像源会同步推送安全更新,我记得在当时的实际操作中,切换到云厂商内网源后很快就刷到了
0.116-11.el8_5.4.el8版本。
注意升级时把polkit-libs、polkit-pkla一起升级,这几个包版本号必须匹配,只升级主包会导致依赖检查失败。
3.2 应急方案:先去掉 pkexec 的 setuid 位
如果仓库一时拿不到修复包,又或者出于某种原因必须把更新窗口推到后面,应急缓解措施也很简单。漏洞利用依赖 pkexec 的 setuid 特殊权限位,只要把这个权限去掉,pkexec 在执行时就不会切换到 root 身份,攻击链就断了。执行一步命令:
chmod 0755 /usr/bin/pkexec操作后用ls -l /usr/bin/pkexec确认权限显示为-rwxr-xr-x而不是正常的-rwsr-xr-x,就说明缓解生效了。
需要清楚这个操作的代价:系统里那些依赖 pkexec 的授权弹窗和提权操作会一并失效。比如桌面环境里打开管理界面输入密码授权时,可能会提示认证失败或无法执行提权命令。对于纯命令行服务器,影响相对小一些,因为很少有普通用户日常用到 pkexec,管理员都用 sudo。这个方案适合作为临时阻断手段,不能作为长期状态,更不要忘了后续还要升级补丁,升级完成后确认权限位回到 4755。
3.3 关于移除 pkexec 的补充思路
有部分人问:既然 pkexec 有漏洞,能不能直接把整个 polkit 包卸载?理论上能从系统里删除它,但代价往往超出预期。polkit 是很多桌面组件、服务管理工具的基础依赖,直接卸载极有可能让某些功能报错,严重时连桌面都起不来。
还有一个可行思路是结合 SELinux 进行加固。虽然 SELinux 不能单独拦住 CVE-2021-4034 的利用链,但作为纵深防御的一部分,保持 SELinux 开启(Enforcing)状态能在漏洞被利用后显著增加攻击者的操作难度。我习惯的做法是:漏洞修复为主,SELinux 策略加固为辅,两层都做到位,这台机器才算真的放心。
4. 完整实操:从验证到修复到回归
4.1 如何安全地确认漏洞是否存在
首先是验证本地环境的漏洞状态。这里建议不要直接在业务环境运行公开的攻击验证程序,避免产生不可控的副作用。运维人员要的只是一个可靠的判断依据,用版本检查就足够:当前 rpm 包版本如果在受影响范围内,就认为存在漏洞,按未修复处理。这个判断逻辑简单且不会给生产环境带来风险。
如果实在想在测试机上做行为验证,可以在非 root 用户下直接执行pkexec不带任何参数,观察是否出现段错误或者异常退出。但我要多提醒一句:这类行为本质上已经接近触发漏洞的边界,绝不要在没有隔离的测试环境里做,更不能拿它去生产环境试探。确认版本在受影响范围内就已经够声明这台机器需要修复了。
4.2 在线升级 polkit 的完整命令流程
在能够获取修复包的环境里,操作顺序如下。先刷新缓存:
dnf clean all dnf makecache然后直接升级:
dnf update polkit polkit-libs polkit-pkla -y如果命令提示没有任何可更新的包,说明当前仓库源里的包版本未变,或者源已经失效。这时按我上面说的,把源切到仍维护兼容仓库的镜像站,再执行一次。升级完成后立刻确认三件事:
rpm -q polkit polkit-libs polkit-pkla ls -l /usr/bin/pkexec systemctl restart polkit正常情况下,版本号会显示为修复后的版本,pkexec 权限位恢复为-rwsr-xr-x(4755),polkit 服务重启后保持在运行状态。这里的 systemctl restart 非常重要,因为 polkit 的一部分工作由守护进程完成,不重启的话,旧进程体内可能依然保留着旧版本的缺陷逻辑。
4.3 离线环境的升级思路
很多服务器出于安全要求不能连外网,升级得走离线流程。具体做法是在另一台能够访问软件源的主机上,把需要升级的包和依赖都下载下来:
dnf download polkit polkit-libs polkit-pkla --resolve --alldeps --destdir=/tmp/polkit-rpms然后把/tmp/polkit-rpms里的文件拷贝到离线服务器的某个目录,比如/root/polkit-rpms,在目标机器上执行:
dnf install /root/polkit-rpms/*.rpm -y使用dnf install而不是直接rpm -Uvh的好处是它会自动做依赖解析,避免漏包。安装结束后同样做版本、权限位和服务状态三个检查。离线环境最怕的是下载的 rpm 包和系统的架构或版本不匹配,比如用的是 8.6 的包给 8.5 装,很可能出现依赖版本过高的问题。所以在下载阶段就要确认好软件源和你目标系统是同一个大版本。
4.4 修复后的功能回归测试
补丁装上以后,验证不是说版本号对了就完事,还得看功能是否正常。我最常用的回归测试是用普通用户身份执行:
su - testuser -c "pkexec /bin/echo test"正常情况下应该弹出认证提示,比如要求输入密码或提示需要认证代理,而不会再出现直接崩溃或者异常段错误。这个命令实际不会执行成功,但它的输出能帮你判断 pkexec 是否还具备正常的提权流程。再检查一下系统日志:
journalctl -u polkit --since "today" grep pkexec /var/log/messages看到正常的服务启动日志和认证尝试记录,没有重复的 crash 记录,就说明修复生效且功能正常。如果这台机器上有图形桌面,最好再点开一次系统设置里的管理功能,确认认证弹窗能正常出现,这一步能避免遗漏权限位错误引起的隐性故障。
5. 实操中容易踩的坑:问题排查速查表
5.1 升级后 pkexec 权限位变成 0755 导致功能异常
我在处理一批机器时遇到过这种情况:系统里之前做了chmod 0755 /usr/bin/pkexec应急缓解,升级完 polkit 包之后,发现 pkexec 权限依然是 0755,版本明明已经是修复后的,但桌面管理的提权操作全部失败。原因很简单,rpm 升级不会自动把被管理员手工修改过的权限位强制纠正回来。解决办法是手动恢复:
chmod 4755 /usr/bin/pkexec恢复后再确认,权限应是-rwsr-xr-x。这个小细节不写在补丁公告里,容易让人以为升级完成就万事大吉,实际上还要补上这一脚。
5.2 仓库刷不到修复版本,升级计划卡住
最常见的问题就是执行dnf update polkit后提示“没有软件包需要更新”。如果确定当前版本确实在受影响范围内,说明现在的源仓库里没有推送修复包。运维人员,尤其是 CentOS 8 这种生命周期结束的发行版,必须在心里明确一个事实:官方源不更新安全补丁了。
处理方式有两种,一种是把源切到仍在维护的兼容仓库,或者直接使用 RHEL 8.5 对应的安全更新通道;另一种是使用社区维护的 rebuild 包。无论用哪种,都必须留意包的签名校验,非官方渠道的包风险自担,安装前先确认 MD5 或 GPG 签名是否正确。升级完成后记得把源配置切回原样,避免后续操作误用到不确定的仓库。
5.3 升级后是否需要重启机器
这个问题在我处理的群里被问了不下十遍。我给出的建议是分场景看。如果系统里有桌面套件,并且很多图形进程长时间运行,稳妥起见安排一次重启;如果是纯命令行的业务服务器,只需要执行systemctl restart polkit,并确认依赖 polkit 的进程没有残留异常,可以暂时不重启。但要注意,如果你在用容器,特别是容器和宿主机共享/usr目录的部署方式,宿主机和容器里的 polkit 包都要检查,只更新一个层面是不够的。
5.4 如何防止同类问题再次大面积漏掉
经历过一轮紧急修复后,最怕的就是机房里还有某台机器被漏掉,下次漏洞通告出来又得手工确认一次。我们可以把巡检自动化。最简单的思路是用一个命令批量收集所有机器的 polkit 版本,再和修复版本号做对比。
rpm -qa polkit --qf "%{NAME}-%{VERSION}-%{RELEASE}\n"配合自动化配置工具下发到所有机器,把执行结果集中汇总,设定版本阈值告警,低于修复版本的主机自动标记为需要人工介入。平时关注官方的安全通告,有新的 polkit 相关 CVE 出来时,第一时间更新自己的版本识别脚本。安全运维这活儿,靠记忆靠不住,靠自动化的版本基线才能减少漏网之鱼。
5.5 别把 CVE-2021-4034 和 CVE-2021-3560 搞混
复习一下知识要点:CVE-2021-3560 是 polkit 的另一个本地提权漏洞,利用的是认证流程中的逻辑漏洞,和 4034 的越界写完全不同。有些文章会把二者写在一起,导致有些同学以为修复了一个就把另一个也覆盖了。实际上,两个漏洞需要各自的补丁包,检查版本时最好同时确认这两个漏洞的修复版本号。我在实际巡检脚本里会把这两个 CVE 对应的修复版本都写成检查项,避免出现修了 A 漏了 B 的局面。
6. 最后分享几点个人经验
这批漏洞处置下来,我个人的最大体会是:对于本地提权漏洞,修复窗口真的要压缩到最短。CVE-2021-4034 从公开到出现公开利用脚本的时间间隔非常短,秒级提权的演示视频传播速度远超很多运维的响应速度。与其等攻击者先扫描到你,不如自己先动手把补丁打上。
还有一个教训是,应急缓解措施和正式补丁要分开记台账。chmod 0755 之后一定要在监控或巡检脚本里留下记录,否则几天后你自己都会忘了这台机器是不是已经恢复权限。我这边的做法是每台执行过缓解操作的机器,都要在备注里写清楚“待升级补丁,升级后必须恢复 4755”,并且把这类机器放在自动化巡检的最高优先级。
最后说个小技巧:如果机器数量多,可以把rpm -qa polkit --qf "%{NAME}-%{VERSION}-%{RELEASE}\n"的结果统一收集,再用脚本比对预期版本号。这一条命令的成本几乎为零,却能避免你在某次安全通告后逐台登录排查的尴尬。安全这件事,功夫下在平时,比临时抱佛脚有用得多。