已经了解了 Linux
系统上的许多错误配置,这些配置允许攻击者获得特权 shell
。这是因为即使一个程序应该执行特定的系统级任务,它也需要拥有 root
用户的 EUID
,从而使攻击者很容易利用它来执行特权升级。让我们称之为权限的二元系统,它将启动两种类型的进程——特权 (EUID == 0
) 和非特权 (EUID != 0
)
为了防止这种情况,Linux
开发人员已经考虑并将所有特权任务分类为一组约 40
(截至目前)的功能。因此,例如,如果想读取一个特权文件,管理员设置正在运行的进程或程序文件拥有 CAP_DAC_READ_SEARCH
特权,绕过文件读取权限检查和目录读取和执行权限检查。在这种情况下,非特权用户只能执行读取任何文件的特定任务并在受保护的目录中执行搜索,将 UID
设置为 0 然后生成 shell
等恶意使用是不可能的。
答案很简单——由 root
用户分配给正在运行的程序或线程甚至程序文件的一组细粒度权限,允许进程使用特权(系统级任务),例如杀死低权限用户的进程。每个能力为流程提供一组或多组相关权限。所有这些都在 capabilities(7)
手册页中列出并得到了很好的解释。
与之前的文章中讨论过的 DAC/MAC
权限不同,如果能力在允许的集合中(如下所述),则可以在运行过程中设置或取消设置这些权限。内核只会检查一组特定的功能
如果线程以有效 UID 值 0 运行,则它将启用所有能力。
扩展权限,例如由 setfacl
设置的访问控制列表和由使用 setxattr(2)
的 setcap
设置的能力标志, 存储的位置与传统权限和由 chmod
的 set[ug]id
标志一样 - 在文件的 inode
中
有一个已经设置了一些能力的二进制文件。当对该文件执行 getfattr
时,我=发现信息存储在 security.capability
部分。用于获取额外文件属性的系统调用是
$ strace -e getxattr getfattr -d -m - cat
getxattr("cat", "security.capability", NULL, 0) = 20
getxattr("cat", "security.capability", "\1\0\0\2\200\0\0\0\200\0\0\0\0\0\0\0\0\0\0", 256) = 20
# file: cat
security.capability=0sAQAAAoAAAACAAAAAAAAAAAAAAAA=
这些能力实际上是在进程执行期间发挥作用的。内核只会在程序尝试执行特殊系统调用时检查它们。
在所有能力列表中,每个进程有 5 组不同的功能
execve()
系统调用保留的。如果该能力设置为可继承,则在使用 execve()
系统调用执行程序时将它添加允许集execve()
系统调用,但用于非特权文件。可以通过 prctl()
控制它们,这不能通过控制台程序设置如果二进制文件可以使用 capget
、capset
或 prctl
等系统调用主动将允许的能力转换为有效,则它可以称为能力敏感。另一方面,一个能力无感的二进制文件没有这个特权使能力集有效,无论是被父进程继承还是在内存中加载程序时
有三个控制台实用程序来管理 Linux
中的能力
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_permitted
和 cap_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
暗号:138791