安全修复 · 2026 年 11 月 11 日

PwnKit 提权漏洞:pkexec 与 Polkit 的十年隐患

pkexec 里一个存在了十余年的参数处理缺陷,让任何本地用户都能拿到 root。这篇讲清 PwnKit 的触发条件、怎么批量确认几百台机器是否暴露,以及为什么删掉 setuid 位比等补丁更快。

2022 年 1 月,Qualys 公开 CVE-2021-4034。漏洞在 polkit 的 pkexec 里,本地普通用户即可提权到 root。利用代码公开后很快被集成进各类自动化工具,成了渗透测试里最省事的一步。

它的特别之处是年龄。从 pkexec 诞生算起,这个问题存在了十多年,几乎覆盖所有主流 Linux 发行版。

背景:pkexec 是什么#

polkit 是 Linux 上的授权框架,桌面环境用它来判断“这个用户能不能挂载磁盘、能不能改网络配置”。pkexec 是它的命令行前端,作用类似 sudo:以另一个用户(通常是 root)身份执行命令,但走 polkit 的规则,而不是 sudoers 文件。

因为要让普通用户能触发,pkexec 被安装成 setuid root 程序。这个设定决定了影响面:只要机器上有 pkexec 且带 setuid 位,本地用户就多了一条通往 root 的路径。

服务器上通常用不到它。桌面发行版默认装 polkit,服务器版也常因为依赖关系被带进来,所以它不像 sudo 那样天天被用到,加固清单里也很少提。一个没人记得的 setuid 程序,就这样躺在机器上十多年。

漏洞原理:参数数量为零的边界#

点到为止。pkexec 启动时会重新拼装参数与环境变量,为后续的授权检查做准备。当它以“参数个数为 0”的方式被调用时,内部对 argv 的索引假设失效,把本不该写入的位置当成了可写空间。

结果是越界写,同时让攻击者能够重新引入本应被清理掉的环境变量。其中一个被利用的是 GCONV_PATH,它会让动态链接器去加载指定路径下的字符集转换模块。攻击者只要让这个路径指向自己的目录,就能以 root 权限加载自己的共享库。

整条链路不依赖内核漏洞,也不需要竞态,稳定得可怕。这也是它比很多“需要拼运气”的提权洞更危险的地方。

这类洞为什么能藏这么久?因为它不是内存破坏的典型形态,静态分析很难把“argv 索引假设”判定为缺陷;正常调用时参数永远大于零,测试也覆盖不到。直到有人手工构造出零参数调用,问题才暴露出来。

影响范围#

环境 状态 怎么查
Debian / Ubuntu 受影响,已发补丁 dpkg -l policykit-1
RHEL / CentOS / Rocky 受影响,已发补丁 rpm -q polkit
容器镜像内含 pkexec 可能带旧版本 镜像内看 /usr/bin/pkexec
最小化安装、无桌面 多数不受影响 command -v pkexec

Debian 系后来把 polkit 拆成了 polkitd、pkexec 等包,按 policykit-1 检索的资产清单会漏项。具体的包名与修复版本请以发行版安全公告为准。

另一个容易漏的地方是模板镜像。基础镜像里带着旧版 pkexec,之后建出的几十台机器都会继承,但没人会在业务清单里登记它。

云主机的公共镜像也要问一句。镜像厂商的构建时间、基线版本、是否附带桌面组件,决定了它有没有把旧版 pkexec 带进来。买机器时顺手跑一次 command -v pkexec,比事后补救便宜。

检测:先找 setuid,再对版本#

bash
# 1. 找出带 setuid 位的 pkexec
find / -xdev -perm -4000 -name pkexec 2>/dev/null

# 2. 确认包版本
rpm -q polkit 2>/dev/null || dpkg -l | grep -E 'polkit|pkexec'

# 3. 看有没有异常调用痕迹(需提前开启 audit)
ausearch -m EXECVE -ts recent 2>/dev/null | grep -i pkexec | tail

第一条要加 -xdev,否则会扫进挂载的网络盘和容器层,慢且容易误报。如果机器上根本没有 pkexec,输出为空就是最好的结果。

第二条用于判断补丁状态。发行版常回填补丁不改版本号,Debian/Ubuntu 可以看 apt changelog policykit-1,RHEL 系看 rpm -q --changelog polkit | head -40。

第三条用于失陷排查。pkexec 提权成功后会拉起一个 root shell,audit 里能看到 pkexec 后面跟着异常的参数。没有 audit 的环境,退而求其次看 /var/log/auth.log 里的 pkexec 记录,以及 root 的 last 登录时间。

再补两项手工检查:cat /etc/ld.so.preload 是否被写入陌生路径,/tmp、/dev/shm 下是否有近期出现的 .so 文件。提权洞常被当作持久化的第一步。

批量环境可以用一条命令出一张表,交给配置管理工具收集:find / -xdev -perm -4000 -name pkexec -printf '%p %m\n'。把结果和包版本拼在一起,就能按“有 setuid 且版本旧”筛出优先级最高的机器。

修复与缓解:升级,或者直接拿掉 setuid#

两条路,选一条就能闭环。

  1. 升级 polkit 到发行版修复版本,然后重启 polkit 服务。这是唯一“修好”的路径,桌面环境必须走这条。
  2. 立刻止血:去掉 pkexec 的 setuid 位,或者直接删掉二进制。绝大多数服务器根本不需要 pkexec。
bash
# 方案 A:升级(Debian/Ubuntu 示例)
sudo apt-get update
sudo apt-get install --only-upgrade policykit-1

# 方案 B:临时移除 setuid 位(可逆,先记录原权限)
stat -c '%a %n' /usr/bin/pkexec     # 通常是 4755
sudo chmod 0755 /usr/bin/pkexec

# 需要恢复时
sudo chmod 4755 /usr/bin/pkexec
注意

批量下发前先在测试机跑一遍。有的运维脚本、桌面自启项、监控 agent 会调用 pkexec,去掉 setuid 后这些会静默失败。变更单里记下原权限和回滚命令。

用 Ansible、Salt 这类工具推的时候,把“当前权限”写进采集结果,而不是无脑 chmod。否则某台机器本来就没装 pkexec,任务会直接报错。

走升级路线时注意 polkit 是常驻服务,升级后要重启它,否则内存里还是旧代码。重启一般不影响已登录的桌面会话,但正在进行的授权请求会失败,挑低峰做。

验证#

  • find / -xdev -perm -4000 -name pkexec 应无输出(走方案 B 时)。
  • 以普通用户执行 pkexec id,应提示未授权或直接失败,而不是弹出认证框后成功。
  • 走升级路线时,pkexec --version 与包版本一致,systemctl status polkit 正常,桌面登录、挂载 U 盘这类操作不受影响。
  • 复查 ld.so.preload、sudoers、authorized_keys 是否被改动。提权成功后攻击者第一件事通常是留后门。
  • 有条件的话,用 auditd 监控 pkexec 的 execve 事件,持续观察一周。

结论#

  • PwnKit 是本地提权,前提是攻击者已经有一个低权限 shell;它决定的是“进来之后能不能变 root”。
  • 判断是否受影响只看两件事:pkexec 在不在、带不带 setuid 位。
  • 服务器场景下删 setuid 位比等补丁更快,也更彻底。
  • 容器镜像与模板镜像最容易漏,资产清单要单列一项。
  • 修完要查持久化痕迹,别只打个补丁就收工。

完

#Polkit#提权#Linux
回到顶部 ↑