1. 项目概述
最近在巡检一批老旧的CentOS 8.5服务器时,一个熟悉又刺眼的名字再次跳了出来:CVE-2021-4034,也就是那个大名鼎鼎的“PwnKit”漏洞。这个漏洞虽然已经过去几年,但因其影响深远、利用简单,至今仍是安全渗透测试和红蓝对抗中的“常客”。对于运维和安全工程师来说,它就像一颗埋藏在系统深处的“定时炸弹”,尤其是对于那些已经停止官方支持、无法通过常规渠道更新的系统,比如我们手头这批CentOS 8.5。这个漏洞的核心在于polkit框架中的pkexec工具,一个设计用于以高权限执行命令的程序。攻击者可以构造一个特殊的调用,让pkexec错误地处理参数,从而将攻击者控制的环境变量当作命令来执行,最终实现从普通用户到root权限的“惊险一跃”。整个过程无需用户交互,也无需任何特殊权限,危害极大。
这篇文章,我就结合最近一次为CentOS 8.5集群修复此漏洞的实战经历,从头到尾拆解一遍。不仅会讲清楚漏洞的原理,更重要的是,会详细分享在官方仓库已关闭的“绝境”下,如何手动寻找、验证并安装安全补丁包的全过程。同时,我也会聊聊在修复过程中遇到的那些“坑”,以及一些确保修复彻底、不留后患的检查技巧。无论你是正在管理类似遗留系统的运维,还是想深入了解Linux权限机制和安全攻防的安全爱好者,相信这篇来自一线的复盘都能给你带来直接的帮助。
2. 漏洞原理深度剖析与影响评估
2.1 Polkit与pkexec的角色定位
要理解这个漏洞,首先得知道polkit和pkexec是干什么的。polkit(原名PolicyKit)是Linux系统中一个非常重要的授权管理框架。它的核心作用是解决一个经典问题:如何让普通用户进程安全地请求并执行需要高权限(通常是root权限)的操作。比如,普通用户想用networkmanager修改网络配置,或者挂载一个USB磁盘,这些操作都涉及系统层面的改动,不能直接放行。
polkit提供了一套基于策略的精细控制。系统管理员可以通过编写策略文件(通常放在/usr/share/polkit-1/actions/和/usr/share/polkit-1/rules.d/目录下),明确规定“哪个用户”、“在什么条件下”、“可以执行哪个需要特权的操作”。当用户程序(比如图形界面下的一个设置按钮)发起特权请求时,它会通过D-Bus消息总线与polkit守护进程通信,polkit根据策略判断是否授权,可能还会弹出一个密码对话框让用户认证。
而pkexec则是polkit提供的一个命令行工具。你可以把它看作sudo的一个“近亲”,但机制不同。sudo依赖于/etc/sudoers文件的静态配置,而pkexec在运行时动态地咨询polkit守护进程,根据当前策略决定是否允许执行。它的基本用法是pkexec [command],如果策略允许,command就会以root身份运行。
2.2 CVE-2021-4034的核心漏洞机制
漏洞的根源在于pkexec的代码在处理命令行参数时存在一个经典的“边界错误”(Off-by-one Error)。在C语言中,main函数接收两个参数来获取命令行参数:int argc(参数个数)和char **argv(参数向量数组)。按照C语言惯例,argv[0]是程序名本身,argv[1]是第一个真正的参数,以此类推,并且argv[argc]是一个空指针(NULL),用来标记数组的结束。
pkexec的漏洞代码大致逻辑是这样的:当它被调用时,比如pkexec bash,那么argc=2,argv[0]=”pkexec”,argv[1]=”bash”,argv[2]=NULL。问题出在,如果攻击者以某种方式调用pkexec,并且让argc为0,即没有任何命令行参数。那么按照逻辑,argv数组应该只包含argv[0](程序名)和argv[1](NULL)。
然而,在某些特定的调用方式下(例如通过execve()系统调用并精心控制参数和环境变量),攻击者可以制造出一种局面:argc为0,但argv这个内存数组的边界之外,紧接着的内存区域恰好是进程的环境变量(envp)数组。由于argc为0,pkexec在遍历参数时,错误地越界读取了argv[1],而argv[1]实际上指向了第一个环境变量字符串(例如PATH=/usr/bin)。
pkexec后续的代码逻辑没有充分校验这种异常情况,它误将这个环境变量(argv[1])当作是用户想要执行的命令或参数来处理。更致命的是,在构造最终要执行的命令路径时,它会使用一个名为PATH的环境变量来查找可执行文件。如果攻击者能够控制传入的环境变量,他们就可以精心设置PATH等环境变量,诱导pkexec加载并执行一个由攻击者控制的、位于特定路径下的恶意共享库(.so文件)或可执行脚本,从而以root权限执行任意代码。
简单来说,这个漏洞绕过了pkexec的所有策略检查,因为它发生在策略检查逻辑之前。攻击者无需知道任何密码,也无需在polkit策略中有任何特殊配置,只要能在系统上以一个普通用户身份运行程序,就有可能直接获得完整的root shell。
2.3 漏洞影响范围与严重性
这个漏洞的可怕之处在于其广泛性和隐蔽性。
- 影响范围极广:自2009年
pkexec引入相关代码以来,几乎所有主流的Linux发行版(包括RHEL、CentOS、Ubuntu、Debian、Fedora、SUSE等)的默认安装都受影响。只要系统安装了polkit包(这几乎是图形界面和许多服务器服务的标配),就存在风险。 - 利用门槛极低:漏洞利用程序早已公开,在互联网上随手可得。攻击者只需将一段简单的C代码编译后,在目标机器上以普通用户身份运行,瞬间就能获得root权限。整个过程安静快速,不会产生明显的日志告警(在默认配置下)。
- 修复紧迫性高:对于互联网上暴露的服务器,任何一处低权限的入口(例如一个Web应用的文件上传漏洞、一个配置不当的数据库连接)都可能被攻击者作为跳板,利用此漏洞迅速“拿下”整台服务器。
对于我们的CentOS 8.5环境,官方支持已于2021年底终止,这意味着通过yum或dnf的常规更新通道已经关闭,无法自动获取补丁。这使得修复工作从简单的命令升级,变成了需要手动介入的“外科手术”,这也是本文重点要解决的问题。
3. 修复方案选择与准备工作
3.1 修复路径评估
面对CVE-2021-4034,通常有几种修复思路:
- 官方升级(首选):通过系统包管理器(如
dnf update polkit)直接升级到已修复漏洞的版本。这是最安全、最规范的方式。 - 手动安装补丁包:当官方源不可用(如CentOS 8.5)或内网环境无法连接外网时,需要手动下载对应版本的安全补丁RPM包,进行离线安装。
- 临时缓解措施:如果短期内无法升级,可以移除
pkexec的SUID权限作为临时应对。命令是chmod 0755 /usr/bin/pkexec。但这会破坏所有依赖pkexec进行特权操作的功能(如图形界面的软件更新器、网络管理器等),属于“伤敌一千,自损八百”的临时方案,仅用于应急。 - 源码编译:下载打好补丁的
polkit源码包,自行编译安装。这种方式最灵活,但步骤繁琐,依赖复杂,且容易引入不一致性,不适合生产环境批量部署。
对于我们的CentOS 8.5集群,路径1被堵死,路径3影响业务,路径4过于复杂。因此,手动安装补丁包(路径2)成为了唯一可行的选择。这要求我们精确找到适用于CentOS 8.5的、已修复漏洞的polkitRPM包。
3.2 环境探查与信息收集
在动手之前,必须对当前系统状态有清晰的了解。盲目安装错误的包版本可能导致依赖冲突甚至系统不稳定。
首先,检查当前系统版本和已安装的polkit包信息:
cat /etc/redhat-release rpm -qa | grep polkit rpm -qi polkit关键信息包括:polkit和polkit-libs的具体版本号(例如polkit-0.115-11.el8)以及系统架构(x86_64或aarch64)。
其次,验证漏洞是否存在。虽然我们心里清楚它存在,但有时需要给报告一个证据。可以使用一个无害的检测脚本(非利用脚本)来检查,或者直接尝试寻找公开的漏洞检测命令。一个简单的思路是检查pkexec的--version输出,但更可靠的是检查包版本是否低于安全版本。对于CentOS 8.5,安全版本是polkit-0.115-13.el8_5.3及更高。
注意:切勿在正式生产环境直接运行从不明来源下载的漏洞利用(EXP)程序进行“测试”,这本身就是极高的安全风险。检测应以信息收集和版本比对为主。
3.3 寻找可靠的安全补丁包来源
既然CentOS官方仓库停了,我们就需要寻找替代的、可信的软件源。这里有几个选择:
- AlmaLinux / Rocky Linux仓库:作为RHEL/CentOS的继承者,它们的仓库通常兼容性很好,并且会持续提供安全更新。这是我们的首选来源。
- CentOS Vault:这是CentOS官方存档旧版本软件包的地方。但对于像CVE-2021-4034这种在支持终止后才爆出的漏洞,Vault里很可能没有对应的安全更新包。
- EPEL仓库:Extra Packages for Enterprise Linux,但它主要提供额外的软件,不一定包含核心系统包的安全更新。
- 从其他已更新的同版本系统中提取:如果你有另一台已经通过其他方式更新了的CentOS 8.5机器,可以用
rpm -qa polkit polkit-libs --qf “%{name}-%{version}-%{release}.%{arch}.rpm\n”列出包名,然后用dnf download或yumdownloader工具下载对应的RPM包。
经过评估,我们决定从AlmaLinux 8的BaseOS仓库获取补丁包。因为AlmaLinux与RHEL 8二进制兼容,且其8版本仍在维护期内,必然包含了针对此漏洞的修复。
4. 手动下载与安装补丁包实操详解
4.1 定位并下载正确的RPM包
AlmaLinux的软件包仓库结构清晰。我们需要找到适用于x86_64架构的、版本号大于等于安全版本的polkit和polkit-libs包。
安全版本信息参考:
- CentOS 8.5 安全版本:
polkit-0.115-13.el8_5.3 - 实际上,AlmaLinux后续可能提供了更新的版本,例如
polkit-0.115-15.el8_10.2,这个版本也包含了该漏洞的修复,并且兼容性更好。
访问AlmaLinux 8 BaseOS仓库:https://repo.almalinux.org/almalinux/8/BaseOS/x86_64/os/Packages/这是一个标准的HTTP目录列表页面。我们需要在页面中(或通过wget递归下载索引后查找)找到以下两个包:
polkit-0.115-15.el8_10.2.x86_64.rpmpolkit-libs-0.115-15.el8_10.2.x86_64.rpm
实操心得:在浏览器中按
Ctrl+F搜索“polkit”可以快速定位。务必确认架构是x86_64,并且版本号中的el8表示适用于Enterprise Linux 8系列。el8_10.2表示这是AlmaLinux 8.10的更新,但它提供的二进制包在CentOS 8.5上通常是可安装的,因为用户空间的主要库版本在RHEL 8的大版本生命周期内保持兼容。
下载包到服务器上。可以使用wget直接下载:
# 在服务器上执行,假设当前目录为 /tmp wget https://repo.almalinux.org/almalinux/8/BaseOS/x86_64/os/Packages/polkit-0.115-15.el8_10.2.x86_64.rpm wget https://repo.almalinux.org/almalinux/8/BaseOS/x86_64/os/Packages/polkit-libs-0.115-15.el8_10.2.x86_64.rpm如果服务器无法直接访问外网,就需要先在能上网的机器上下载好,再通过scp、sftp或者内部文件共享服务上传到目标服务器。
4.2 执行手动安装与依赖处理
拿到RPM包后,不要急着直接安装。先检查一下包的依赖关系,避免安装失败。
rpm -qpR polkit-0.115-15.el8_10.2.x86_64.rpm rpm -qpR polkit-libs-0.115-15.el8_10.2.x86_64.rpm重点关注是否有类似glib2 >= 2.56.0这样的关键库依赖。在同一个大版本(EL8)内,基础库的版本通常满足要求。如果遇到缺失的依赖,也需要从AlmaLinux仓库下载对应的包。
安装时,使用rpm -Uvh命令。-U表示升级(如果旧版本存在则升级,否则安装),-v显示详细信息,-h显示进度条。强烈建议将polkit和polkit-libs两个包同时安装,因为它们之间版本必须严格匹配。
sudo rpm -Uvh polkit-0.115-15.el8_10.2.x86_64.rpm polkit-libs-0.115-15.el8_10.2.x86_64.rpm如果系统提示缺少依赖,例如libsomething.so.0,你需要根据缺少的库名,去仓库里找到对应的rpm包(通常是something-libs包),一并下载并安装。有时,dnf或yum本地安装可以自动解决依赖,但在离线环境下,rpm命令不会自动处理,需要手动补全。
踩坑记录:在一次安装中,系统提示依赖
libglib-2.0.so.0()(64bit)版本不满足。检查发现是glib2库版本过低。CentOS 8.5自带的glib2版本是2.56.4,而AlmaLinux 8.10的polkit可能要求2.56.0或更高,理论上是满足的。但错误信息可能是由于RPM数据库中的版本字符串比较方式导致的。此时,一个比较稳妥的方法是,从AlmaLinux仓库也下载对应版本的glib2和glib2-libs包进行升级。但升级基础库风险较高,需要谨慎评估。在我们的案例中,经过核实,实际版本是满足的,这个警告可以忽略,通过加上--nodeps参数强制安装(不推荐,除非你非常确定)。更好的做法是寻找一个与当前系统glib2版本匹配的polkit补丁包,或者接受升级glib2。
4.3 安装后验证与重启服务
安装完成后,立即验证安装的版本:
rpm -q polkit polkit-libs输出应显示为新安装的版本,例如polkit-0.115-15.el8_10.2.x86_64。
仅仅安装包还不够,因为pkexec是一个SUID二进制文件,它的权限和内容已经更新。我们需要确保相关的服务也使用新的库文件。重启polkit服务是最直接的方式:
sudo systemctl restart polkit为了确保所有可能缓存了旧pkexec进程的会话完全刷新,最彻底的方法是重启服务器。尤其是在关键生产环境,安排一次合规的重启窗口是值得的。如果无法立即重启,至少需要确保所有用户会话(特别是那些可能已经调用了pkexec的会话)都已注销。
验证漏洞是否修复,可以再次检查版本,并尝试搜索是否有公开的、针对该版本pkexec的漏洞检测方法(注意,不是利用方法)。一个间接但有效的方法是检查pkexec的--help输出中是否包含相关的安全补丁信息,或者查看RPM包的更新日志:
rpm -q polkit --changelog | head -20在更新日志中,你应该能看到关于CVE-2021-4034的修复记录。
5. 修复过程中的常见问题与排查实录
5.1 依赖冲突与版本兼容性问题
问题描述:执行rpm -Uvh时,报错“Error: Failed dependencies: … conflicts with file from package …”。
排查思路:这是最典型的问题。冲突可能来自两个方面:
- 文件冲突:新包要安装的文件已经被其他包占用了。这在不兼容的第三方仓库混用时常见。对于
polkit这样的核心系统包,很少见。 - 依赖不满足:新包要求的某个库或工具的版本高于当前系统已安装的版本。
解决方案:
- 对于依赖不满足,首先用
yum provides或dnf provides命令查找哪个包提供了所需的文件或库。例如:dnf provides “libglib-2.0.so.0()(64bit)”。在离线环境,这需要你有一个本地的仓库镜像或者手动搜索。 - 如果冲突的包是系统基础组件且版本差距不大,可以考虑从同一来源(AlmaLinux仓库)下载并升级这些依赖包。务必注意升级顺序,先升级底层依赖(如
glib2),再升级上层应用(如polkit)。 - 如果无法升级依赖,可能需要寻找一个与当前系统环境版本更匹配的
polkit补丁包。可以尝试在https://vault.centos.org/或https://repo.almalinux.org/vault/中寻找CentOS 8.5生命周期内发布的最后一个更新版本(如果有的话)。 - 最后手段:在充分评估风险并做好回滚准备后,可以使用
rpm -Uvh --nodeps --force命令强制安装。这可能导致系统不稳定或某些功能异常,仅适用于紧急且可控的环境,并需明确记录。
5.2 安装成功但服务无法启动
问题描述:polkit服务重启失败,systemctl status polkit显示错误,可能与新的库文件或配置有关。
排查思路:
- 检查日志:第一时间查看
journalctl -u polkit --since “5 minutes ago”,获取详细的错误信息。 - 验证文件完整性:使用
rpm -V polkit验证安装的文件是否完整,是否有配置文件被意外修改。如果发现配置文件(标记为c)被修改,RPM会提示。新的RPM包可能会用.rpmnew文件提供新的默认配置,需要管理员手动合并。 - 检查依赖库:使用
ldd /usr/bin/pkexec检查pkexec二进制文件依赖的动态库是否能正确链接。确保没有出现“not found”的库。 - 检查SELinux上下文:在启用SELinux的系统上,新安装的二进制文件可能需要恢复正确的安全上下文。使用
restorecon -v /usr/bin/pkexec进行修复。
解决方案:
- 根据
journalctl的日志错误进行针对性修复。常见的是配置语法错误或权限问题。 - 如果是因为配置文件冲突,对比
/etc/polkit-1/rules.d/和/etc/polkit-1/localauthority/目录下的.rpmnew或.rpmsave文件,与现有配置文件合并。 - 运行
sudo systemctl daemon-reload重新加载systemd配置,再尝试启动。
5.3 验证漏洞是否真正修复
问题描述:如何确信漏洞已经补上?仅仅版本号对了就行吗?
排查思路与解决方案:
- 版本比对法:这是最基本的方法。确认
rpm -q polkit输出的版本号大于等于已知的安全版本(对于CentOS 8.5,是0.115-13.el8_5.3)。我们安装的0.115-15.el8_10.2显然更高。 - 补丁检查法:查看软件包更新日志,确认包含了CVE-2021-4034的修复。
如果输出中包含相关记录,则是强有力的证据。rpm -q --changelog polkit | grep -i “CVE-2021-4034” - 行为检测法(谨慎操作):可以编写一个简单的测试程序,模拟漏洞触发的前置条件(如调用
execve并设置argc=0),然后观察pkexec的行为。一个安全的版本应该会输出错误信息并拒绝执行,而不是崩溃或执行任意代码。网上有一些开源的非破坏性检测脚本,可以在隔离的测试环境(如虚拟机)中运行验证。 - 漏洞扫描工具:使用专业的漏洞扫描工具(如OpenVAS, Nessus, Qualys等)对修复后的系统进行扫描,确认CVE-2021-4034的风险状态已变为“已修复”或“低风险”。
5.4 批量部署的自动化考虑
如果面临成百上千台CentOS 8.5服务器需要修复,手动操作是不现实的。
解决方案:
- 制作本地仓库:在一台内部服务器上,下载所有必需的RPM包(
polkit,polkit-libs及其依赖),使用createrepo命令创建一个本地YUM/DNF仓库。然后,在所有目标机器上修改repo文件,指向这个本地仓库,最后执行sudo dnf update polkit即可。这是最规范、最接近原生体验的方式。 - 使用配置管理工具:如果你使用了Ansible、SaltStack、Puppet等工具,可以编写一个Playbook或模块。任务包括:
- 将补丁RPM包分发到目标节点的临时目录。
- 使用
rpm或yum模块进行安装。 - 重启
polkit服务。 - 验证安装版本。
- 编写分发脚本:一个简单的Shell脚本结合
ssh和scp也可以实现小规模批量部署。脚本逻辑:遍历主机列表,上传RPM包,远程执行安装命令,收集结果。务必做好错误处理和回滚预案。
无论采用哪种方式,务必先在少数几台测试机上充分验证,确认安装过程平滑,安装后系统核心功能(特别是需要特权授权的操作,如网络管理、用户管理、软件包安装等)均正常,再进行全网推广。
6. 长期维护与安全加固建议
修复一个高危漏洞只是安全运维的一环。对于像CentOS 8.5这样已经EOL(生命周期终止)的系统,真正的挑战在于如何可持续地保障其安全。
1. 制定迁移计划:CentOS 8.5已于2021年底停止维护,这意味着今后不会再收到任何安全更新。CVE-2021-4034只是其中一个已知漏洞,未来必然会有新的漏洞出现而无法得到官方修复。最根本的解决方案是制定并执行向受支持系统的迁移计划,例如迁移到RHEL(需订阅)、AlmaLinux、Rocky Linux或CentOS Stream。
2. 建立内部补丁仓库:如果迁移无法立即进行,需要建立一个内部流程,持续关注上游社区(如AlmaLinux、Rocky Linux)的安全公告,及时将重要安全补丁包同步到内部仓库,并推送到存量CentOS 8.5主机。这需要持续的人力投入。
3. 实施最小权限原则:即使修复了此漏洞,也应审视系统上的SUID/SGID程序。使用命令find / -type f -perm /6000 2>/dev/null列出所有此类程序,评估每个程序是否业务必需。非必要的应移除特殊权限。
4. 加强监控与审计:配置审计规则(如auditd),监控对/usr/bin/pkexec等关键特权二进制文件的执行。同时,集中收集和分析系统日志,关注异常的用户切换、权限提升行为。
5. 考虑应用层防护:对于运行在服务器上的应用,确保其以最小必要权限运行。避免使用root用户直接运行应用服务。使用容器技术(如Docker)在一定程度上可以隔离漏洞的影响范围。
手动修复CVE-2021-4034的过程,更像是一次对老旧系统维护现状的“压力测试”。它暴露出在缺乏官方支持后,安全维护成本急剧上升的问题。这次经历让我更加坚信,对于生产环境,运行在仍有全面支持的生命周期内的系统,不是一种选择,而是一条必须遵守的底线。在完成本次紧急修复后,我们团队立刻启动了将这批服务器向AlmaLinux 8迁移的项目计划,这才是治本之策。