前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Polkit pkexec 本地权限提升

Polkit pkexec 本地权限提升

作者头像
Khan安全团队
发布2022-03-03 09:14:21
1.4K0
发布2022-03-03 09:14:21
举报
文章被收录于专栏:Khan安全团队

我们发现了本地特权升级(从任何用户到 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 位作为临时缓解措施;例如:

代码语言:javascript
复制
# chmod 0755 /usr/bin/pkexec

这个漏洞是我们最美丽的发现之一

pkexec 是一个类似 sudo 的 SUID-root 程序,其描述如下:

代码语言:javascript
复制
手册页:

-------------------------------------------------- ----------------------
姓名
       pkexec - 以另一个用户身份执行命令

概要
       pkexec [--version] [--disable-internal-agent] [--help]

       pkexec [--user 用户名] 程序 [参数...]

描述

pkexec 允许授权用户以另一个用户的身份执行 PROGRAM

用户。如果未指定 PROGRAM,则将运行默认 shell。

如果没有指定用户名,则程序将被执行

作为管理超级用户 root。

pkexec 的 main() 函数的开头处理命令行

参数(第 534-568 行),并搜索要执行的程序

(如果它的路径不是绝对的)在 PATH 环境的目录中

代码语言:javascript
复制
变量(第 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}),那么

代码语言:javascript
复制
argv[0] 为 NULL(参数列表的终止符)并且:

- 在第 534 行,整数 n 永久设置为 1;

- 在第 610 行,从 argv[1] 越界读取指针路径;

- 在第 639 行,指针 s 被越界写入 argv[1]。

但是从这个越界的 argv[1] 中读取和写入的到底是什么?

要回答这个问题,我们必须简短地离题。当我们 execve() 一个新的

程序,内核复制我们的参数和环境字符串和

指向新程序堆栈末尾的指针(argv 和 envp);为了

例子:

代码语言:javascript
复制

|---------+---------+------+------------|---------+ ---------+-----+------------|
| 参数 [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 行):

代码语言:javascript
复制

-------------------------------------------------- ----------------------
 第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);

代码语言:javascript
复制
例如,函数 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”

字符集和库名称)从默认配置文件中读取,

代码语言:javascript
复制
/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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档