我们发现了本地特权升级(从任何用户到 root)
polkit
的 pkexec
,一个 SUID 根程序,默认安装在
每个主要的 Linux 发行版:
“Polkit(以前称为 PolicyKit)是一个用于控制系统范围的组件
类 Unix 操作系统中的特权。它提供了一种有组织的方式
让非特权进程与特权进程通信。[...]
也可以使用 polkit
来执行提升权限的命令
使用命令 pkexec 后跟命令的权限
被执行(有root权限)。”(
这个漏洞是攻击者梦想成真:
- pkexec 默认安装在所有主要的 Linux 发行版上(我们
被利用的 Ubuntu、Debian、Fedora、CentOS 和其他发行版是
可能也可以利用);
- pkexec 自 2009 年 5 月创建以来就存在漏洞(提交 c8c3d83,
"添加一个 pkexec(1) 命令");
- 任何非特权本地用户都可以利用此漏洞获取
完全根权限;
- 虽然这个漏洞在技术上是内存损坏,但它是
以独立于架构的方式立即、可靠地利用;
- 即使 polkit 守护进程本身没有运行,它也是可利用的。
我们不会立即发布我们的漏洞利用;但是,请注意
这个漏洞很容易被利用,其他研究人员可能
在补丁可用后不久发布他们的漏洞利用。如果不
补丁适用于您的操作系统,您可以删除
来自 pkexec 的 SUID 位作为临时缓解措施;例如:
# chmod 0755 /usr/bin/pkexec
这个漏洞是我们最美丽的发现之一
pkexec 是一个类似 sudo 的 SUID-root 程序,其描述如下:
手册页:
-------------------------------------------------- ----------------------
姓名
pkexec - 以另一个用户身份执行命令
概要
pkexec [--version] [--disable-internal-agent] [--help]
pkexec [--user 用户名] 程序 [参数...]
描述
pkexec 允许授权用户以另一个用户的身份执行 PROGRAM
用户。如果未指定 PROGRAM,则将运行默认 shell。
如果没有指定用户名,则程序将被执行
作为管理超级用户 root。
pkexec 的 main() 函数的开头处理命令行
参数(第 534-568 行),并搜索要执行的程序
(如果它的路径不是绝对的)在 PATH 环境的目录中
变量(第 610-640 行):
-------------------------------------------------- ----------------------
第435章
第436章
...
534 for (n = 1;n < (guint) argc;n++)
第535章
...
第568章
...
610 路径 = g_strdup (argv[n]);
...
第629章
第630章
...
632 秒 = g_find_program_in_path(路径);
...
第639章
第640章
-------------------------------------------------- ----------------------
不幸的是,如果命令行参数 argc 的数量为 0(如果
我们传递给 execve() 的参数列表 argv 为空,即 {NULL}),那么
argv[0] 为 NULL(参数列表的终止符)并且:
- 在第 534 行,整数 n 永久设置为 1;
- 在第 610 行,从 argv[1] 越界读取指针路径;
- 在第 639 行,指针 s 被越界写入 argv[1]。
但是从这个越界的 argv[1] 中读取和写入的到底是什么?
要回答这个问题,我们必须简短地离题。当我们 execve() 一个新的
程序,内核复制我们的参数和环境字符串和
指向新程序堆栈末尾的指针(argv 和 envp);为了
例子:
|---------+---------+------+------------|---------+ ---------+-----+------------|
| 参数 [0] | 参数 [1] | ... | argv[argc] | 环境 [0] | 环境[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+ ----|----+-----+-----|------|
VVVVVV
"程序" "-option" NULL "值" "PATH=name" NULL
显然(因为 argv 和 envp 指针在内存中是连续的),
如果 argc 为 0,则越界 argv[1] 实际上是 envp[0],即
指向我们的第一个环境变量“value”的指针。所以:
- 在第 610 行,读取要执行的程序的路径
argv[1] 越界(即 envp[0]),并指向“值”;
- 在第 632 行,此路径“值”被传递给 g_find_program_in_path()
(因为“值”不是以斜线开头,在第 629 行);
- g_find_program_in_path() 搜索名为“value”的可执行文件
在我们的 PATH 环境变量的目录中;
- 如果找到这样的可执行文件,则返回其完整路径
pkexec 的 main() 函数(在第 632 行);
- 在第 639 行,此完整路径被越界写入 argv[1]
(即 envp[0]),从而覆盖我们的第一个环境变量。
更确切地说:
- 如果我们的 PATH 环境变量是“PATH=name”,并且如果目录
“名称”存在(在当前工作目录中)并包含一个
名为“value”的可执行文件,然后是指向字符串的指针
“名称/值”越界写入 envp[0];
- 或者,如果我们的 PATH 是“PATH=name=.”,并且目录是“name=.” 存在
并包含一个名为“value”的可执行文件,然后是一个指向
字符串“name=./value”被越界写入 envp[0]。
换句话说,这种越界写入允许我们重新引入一个
将“不安全”环境变量(例如 LD_PRELOAD)放入 pkexec 的
环境; 这些“不安全”变量通常会被删除(通过 ld.so)
从 main() 函数之前的 SUID 程序环境
叫。我们将在下面利用这个强大的原语
部分。
最后一分钟说明:polkit 还支持非 Linux 操作系统,例如
作为 Solaris 和 *BSD,但我们尚未调查它们的可利用性;
然而,我们注意到 OpenBSD 是不可利用的,因为它的内核
如果 argc 为 0,则拒绝 execve() 程序。
开发
我们的问题是:要成功利用这个漏洞,
我们应该将“不安全”变量重新引入 pkexec 的环境吗?
我们的选择是有限的,因为在越界后不久写
(在第 639 行),pkexec 完全清除其环境(在第 702 行):
-------------------------------------------------- ----------------------
第639章
...
657 for (n = 0; environment_variables_to_save[n] != NULL; n++)
第658章
第659章
...
662 值 = g_getenv(键);
...
第670章
...
第675章
...
第702章
-------------------------------------------------- ----------------------
我们的问题的答案来自于 pkexec 的复杂性:打印一个
到 stderr 的错误消息,pkexec 调用 GLib 的函数 g_printerr()
(注意:GLib 是 GNOME 库,而不是 GNU C 库,即 glibc);
例如,函数 validate_environment_variable() 和
log_message() 调用 g_printerr() (在第 126 和 408-409 行):
-------------------------------------------------- ----------------------
88 log_message(gint级别,
89 gboolean print_to_stderr,
90 const gchar *格式,
91 ...)
92 {
...
125 如果(打印到标准错误)
126 g_printerr ("%s\n", s);
-------------------------------------------------- ----------------------
第383章
第384章
第385章
...
第406章
407 "在 /etc/shells 文件中找不到 SHELL 变量的值");
408 g_printerr ("\n"
409 "此事件已报告。\n");
g_printerr() 通常打印 UTF-8 错误信息,但它可以打印
如果环境变量 CHARSET 不是另一个字符集中的消息
UTF-8(注意:CHARSET 不是安全敏感的,它不是“不安全的”
环境变量)。将消息从 UTF-8 转换为另一种
charset,g_printerr()
调用glibc 的函数iconv_open()。
要将消息从一个字符集转换为另一个, iconv_open() 执行
小型共享库;通常,这些三元组(“from”字符集,“to”
字符集和库名称)从默认配置文件中读取,
/usr/lib/gconv/gconv 模块。或者,环境变量
GCONV_PATH 可以强制 iconv_open() 读取另一个配置文件;
自然, GCONV_PATH 是“不安全”的环境变量之一
(因为它会导致任意库的执行),并且是
因此被 ld.so 从 SUID 程序的环境中删除。
不幸的是,CVE-2021-4034 允许我们将 GCONV_PATH 重新引入
pkexec 的环境,并以 root 身份执行我们自己的共享库。
重要提示:这种利用技术会在日志中留下痕迹(无论是
“在 /etc/shells 文件中找不到 SHELL 变量的值”或
“环境变量的值 [...] 包含可疑内容”)。
但是,请注意,此漏洞也可以在没有的情况下被利用
在日志中留下任何痕迹,但这留作练习
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。