Linux capabilities

Capabilities 机制是在 Linux 内核 2.2 之后引入的,原理很简单,就是将之前与超级用户 root(UID=0)关联的特权细分为不同的功能组,Capabilites 作为线程(Linux 并不真正区分进程和线程)的属性存在,每个功能组都可以独立启用和禁用。其本质上就是将内核调用分门别类,具有相似功能的内核调用被分到同一组中。

这样一来,权限检查的过程就变成了:在执行特权操作时,如果线程的有效身份不是 root,就去检查其是否具有该特权操作所对应的 capabilities,并以此为依据,决定是否可以执行特权操作。

Capabilities 可以在进程执行时赋予,也可以直接从父进程继承。所以理论上如果给 nginx 可执行文件赋予了 CAP_NET_BIND_SERVICE capabilities,那么它就能以普通用户运行并监听在 80 端口上。

capability 名称描述CAP_AUDIT_CONTROL启用和禁用内核审计;改变审计过滤规则;检索审计状态和过滤规则CAP_AUDIT_READ允许通过 multicast netlink 套接字读取审计日志CAP_AUDIT_WRITE将记录写入内核审计日志CAP_BLOCK_SUSPEND使用可以阻止系统挂起的特性CAP_CHOWN修改文件所有者的权限CAP_DAC_OVERRIDE忽略文件的 DAC 访问限制CAP_DAC_READ_SEARCH忽略文件读及目录搜索的 DAC 访问限制CAP_FOWNER忽略文件属主 ID 必须和进程用户 ID 相匹配的限制CAP_FSETID允许设置文件的 setuid 位CAP_IPC_LOCK允许锁定共享内存片段CAP_IPC_OWNER忽略 IPC 所有权检查CAP_KILL允许对不属于自己的进程发送信号CAP_LEASE允许修改文件锁的 FL_LEASE 标志CAP_LINUX_IMMUTABLE允许修改文件的 IMMUTABLE 和 APPEND 属性标志CAP_MAC_ADMIN允许 MAC 配置或状态更改CAP_MAC_OVERRIDE忽略文件的 DAC 访问限制CAP_MKNOD允许使用 mknod() 系统调用CAP_NET_ADMIN允许执行网络管理任务CAP_NET_BIND_SERVICE允许绑定到小于 1024 的端口CAP_NET_BROADCAST允许网络广播和多播访问CAP_NET_RAW允许使用原始套接字CAP_SETGID允许改变进程的 GIDCAP_SETFCAP允许为文件设置任意的 capabilitiesCAP_SETPCAP参考 capabilities man pageCAP_SETUID允许改变进程的 UIDCAP_SYS_ADMIN允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等CAP_SYS_BOOT允许重新启动系统CAP_SYS_CHROOT允许使用 chroot() 系统调用CAP_SYS_MODULE允许插入和删除内核模块CAP_SYS_NICE允许提升优先级及设置其他进程的优先级CAP_SYS_PACCT允许执行进程的 BSD 式审计CAP_SYS_PTRACE允许跟踪任何进程CAP_SYS_RAWIO允许直接访问 /devport、/dev/mem、/dev/kmem 及原始块设备CAP_SYS_RESOURCE忽略资源限制CAP_SYS_TIME允许改变系统时钟CAP_SYS_TTY_CONFIG允许配置 TTY 设备CAP_SYSLOG允许使用 syslog() 系统调用CAP_WAKE_ALARM允许触发一些能唤醒系统的东西(比如 CLOCK_BOOTTIME_ALARM 计时器)

capabilities 的赋予和继承

Linux capabilities 分为进程 capabilities 和文件 capabilities。对于进程来说,capabilities 是细分到线程的,即每个线程可以有自己的capabilities。对于文件来说,capabilities 保存在文件的扩展属性中。

线程的 capabilities

每一个线程,具有 5 个 capabilities 集合,每一个集合使用 64 位掩码来表示,显示为 16 进制格式。这 5 个 capabilities 集合分别是:ep

Permitted 1,2,3

Effective

Inheritable

Bounding

Ambient

每个集合中都包含零个或多个 capabilities

Permitted 允许

定义了线程能够使用的 capabilities 的上限。它并不使用线程的 capabilities,而是作为一个规定。也就是说,线程可以通过系统调用 capset() 来从 Effective 或 Inheritable 集合中添加或删除 capability,前提是添加或删除的 capability 必须包含在 Permitted 集合中(其中 Bounding 集合也会有影响,具体参考下文)。 如果某个线程想向 Inheritable 集合中添加或删除 capability,首先它的 Effective 集合中得包含 CAP_SETPCAP 这个 capabiliy。

Effective 有效

内核检查线程是否可以进行特权操作时,检查的对象便是 Effective 集合。如之前所说,Permitted 集合定义了上限,线程可以删除 Effective 集合中的某 capability,随后在需要时,再从 Permitted 集合中恢复该 capability,以此达到临时禁用 capability 的功能。

Inheritable

当执行exec() 系统调用时,能够被新的可执行文件继承的 capabilities,被包含在 Inheritable 集合中。这里需要说明一下,包含在该集合中的 capabilities 并不会自动继承给新的可执行文件,即不会添加到新线程的 Effective 集合中,它只会影响新线程的 Permitted 集合。

Bounding

Bounding 集合是 - 集合的超集,如果某个 capability 不在 Bounding 集合中,即使它在 Permitted 集合中,该线程也不能将该 capability 添加到它的 Inheritable 集合中。

Bounding 集合的 capabilities 在执行 fork() 系统调用时会传递给子进程的 Bounding 集合,并且在执行 execve 系统调用后保持不变。

当线程运行时,不能向 Bounding 集合中添加 capabilities。

一旦某个 capability 被从 Bounding 集合中删除,便不能再添加回来。

将某个 capability 从 Bounding 集合中删除后,如果之前 Inherited 集合包含该 capability,将继续保留。但如果后续从 Inheritable 集合中删除了该 capability,便不能再添加回来。

Ambient

Linux 4.3 内核新增了一个 capabilities 集合叫 Ambient ,用来弥补 Inheritable 的不足。Ambient 具有如下特性:

Permitted 和 Inheritable 未设置的 capabilities,Ambient 也不能设置。

当 Permitted 和 Inheritable 关闭某权限(比如 CAP_SYS_BOOT)后,Ambient 也随之关闭对应权限。这样就确保了降低权限后子进程也会降低权限。

非特权用户如果在 Permitted 集合中有一个 capability,那么可以添加到 Ambient 集合中(也有了A权限),这样它的子进程便可以在 Ambient、Permitted 和 Effective 集合中获取这个 capability。现在不知道为什么也没关系,后面会通过具体的公式来告诉你。

Ambient 的好处显而易见,举个例子,如果你将 CAP_NET_ADMIN 添加到当前进程的 Ambient 集合中,它便可以通过 fork() 和 execve() 调用 shell 脚本来执行网络管理任务,因为 CAP_NET_ADMIN 会自动继承下去。

文件的 capabilities

文件的 capabilities 被保存在文件的扩展属性中。如果想修改这些属性,需要具有 CAP_SETFCAP 的 capability。文件与线程的 capabilities 共同决定了通过 execve() 运行该文件后的线程的 capabilities。

文件的 capabilities 功能,需要文件系统的支持。如果文件系统使用了 nouuid 选项进行挂载,那么文件的 capabilities 将会被忽略。

类似于线程的 capabilities,文件的 capabilities 包含了 3 个集合:

Permitted

Inheritable

Effective

这3个集合的具体含义如下:

Permitted

这个集合中包含的 capabilities,在文件被执行时,会与线程的 Bounding 集合计算交集,然后添加到线程的 Permitted 集合中。

Inheritable

这个集合与线程的 Inheritable 集合的交集,会被添加到执行完 execve() 后的线程的 Permitted 集合中。

Effective

这不是一个集合,仅仅是一个标志位。如果设置开启,那么在执行完 execve() 后,线程 Permitted 集合中的 capabilities 会自动添加到它的 Effective 集合中。对于一些旧的可执行文件,由于其不会调用 capabilities 相关函数设置自身的 Effective 集合,所以可以将可执行文件的 Effective bit 开启,从而可以将 Permitted 集合中的 capabilities 自动添加到 Effective 集合中。

运行 execve() 后 capabilities 的变化

直接上公式

P'(ambient) A = (file is privileged) ? 0 : P(ambient)

P'(permitted) A = (P(inheritable) & F(inheritable)) |

(F(permitted) & P(bounding)) | P'(ambient) A

P'(effective)A = F(effective) ? P'(permitted) : P'(ambient)A

P'(inheritable) = P(inheritable) [i.e., unchanged]

P'(bounding) = P(bounding) [i.e., unchanged]

我们一条一条来解释:

如果用户是 root 用户,那么执行 execve() 后线程的 Ambient 集合是空集;如果是普通用户,那么执行 execve() 后线程的 Ambient 集合将会继承执行 execve() 前线程的 Ambient 集合。

执行 execve() 前线程的 Inheritable 集合与可执行文件的 Inheritable 集合取交集,会被添加到执行 execve() 后线程的 Permitted 集合;线程执行前 bounding 集合与可执行文件的 Permitted 集合取交集,也会被添加到执行 execve() 后线程的 Permitted 集合;同时执行 execve() 后线程的 Ambient 集合中的 capabilities 会被自动添加到该线程的 Permitted 集合中。

如果可执行文件开启了 Effective 标志位,那么在执行完 execve() 后,线程 Permitted 集合中的 capabilities 会自动添加到它的 Effective 集合中。

执行 execve() 前线程的 Inheritable 集合会继承给执行 execve() 后线程的 Inheritable 集合。

这里有几点需要着重强调:

上面的公式是针对系统调用 execve() 的,如果是 fork(),那么子线程的 capabilities 信息完全复制父进程的 capabilities 信息。

可执行文件的 Inheritable 集合与线程的 Inheritable 集合并没有什么关系,可执行文件 Inheritable 集合中的 capabilities 不会被添加到执行 execve() 后线程的 Inheritable 集合中。如果想让新线程的 Inheritable 集合包含某个 capability,只能通过 capset() 将该 capability 添加到当前线程的 Inheritable 集合中(因为 P’(inheritable) = P(inheritable))。

如果想让当前线程 Inheritable 集合中的 capabilities 传递给新的可执行文件,该文件的 Inheritable 集合中也必须包含这些 capabilities(因为 P’(permitted) = (P(inheritable) & F(inheritable))|…)。

将当前线程的 capabilities 传递给新的可执行文件时,仅仅只是传递给新线程的 Permitted 集合。如果想让其生效,新线程必须通过 capset() 将 capabilities 添加到 Effective 集合中。或者开启新的可执行文件的 Effective 标志位(因为 P’(effective) = F(effective) ? P’(permitted) : P’(ambient))。

在没有 Ambient 集合之前,如果某个脚本不能调用 capset(),但想让脚本中的线程都能获得该脚本的 Permitted 集合中的 capabilities,只能将 Permitted 集合中的 capabilities 添加到 Inheritable 集合中(P’(permitted) = P(inheritable) & F(inheritable)|…),同时开启 Effective 标志位(P’(effective) = F(effective) ? P’(permitted) : P’(ambient))。有 Ambient 集合之后,事情就变得简单多了,后续的文章会详细解释。

如果某个 UID 非零(普通用户)的线程执行了 execve(),那么 Permitted 和 Effective 集合中的 capabilities 都会被清空。

从 root 用户切换到普通用户,那么 Permitted 和 Effective 集合中的 capabilities 都会被清空,除非设置了 SECBIT_KEEP_CAPS 或者更宽泛的 SECBIT_NO_SETUID_FIXUP。

总结

通过学习我觉得capbilibities中的比较重要的就是Effective 有效 Permitted 允许 Inheritable 继承

其实还是主要看e,p可以文件权限集合的上限,i这么说吧,我以nginx举例子吧,假设你有一台Nginx服务器(这里设置为2核哦),启动以后会产生一个主线程,子线程,想必我们大家都知道Nginx的主线程就是老板吧(基本不干活),子线程就是员工吧,但是子线程的权限就很小,但是他可以超过他的权限干一些事情,那么必然他是继承主线程才获得的。

我再说一下我对运行后execve()后capabilities的变化吧,我认为最重要的是公式中的第三步P'(effective)A = F(effective) ? P'(permitted) : P'(ambient)A

说说我的理解如果一个线程有e那么我们肯定会有这个权限A

如果没e 那就看一下Ambient 有没有获得执行后的权限

如果没e,Ambient也没获得执行后的权限那么只能继承

inux 系统中主要提供了两种工具来管理 capabilities:libcap 和 libcap-ng。libcap 提供了 getcap 和 setcap 两个命令来分别查看和设置文件的 capabilities,同时还提供了 capsh 来查看当前 shell 进程的 capabilities。libcap-ng 更易于使用,使用同一个命令 filecap 来查看和设置 capabilities。

libcap

首先先安装,(Centos)

[root@localhost ~]# yum install -y libcap

这里看一下shell的Capabilities

[root@localhost ~]# capsh --print

Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36+ep

Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36

Securebits: 00/0x0/1'b0

secure-noroot: no (unlocked)

secure-no-suid-fixup: no (unlocked)

secure-keep-caps: no (unlocked)

uid=0(root)

gid=0(root)

groups=0(root)

Current : 表示当前 shell 进程的 Effective capabilities 和 Permitted capabilities。

Bounding set : 这里仅仅表示 Bounding 集合中的 capabilities,不包括其他集合,所以分组的末尾不用加上 +... 。

想看完整的信息用下面的命令

[root@localhost ~]# cat /proc/$$/status

Name: bash

Umask: 0022

State: S (sleeping)

Tgid: 1528

Ngid: 0

Pid: 1528

PPid: 1524

TracerPid: 0

Uid: 0 0 0 0

Gid: 0 0 0 0

FDSize: 256

Groups: 0

VmPeak: 115648 kB

VmSize: 115616 kB

VmLck: 0 kB

VmPin: 0 kB

VmHWM: 2080 kB

VmRSS: 2080 kB

RssAnon: 424 kB

RssFile: 1656 kB

RssShmem: 0 kB

VmData: 440 kB

VmStk: 132 kB

VmExe: 888 kB

VmLib: 2148 kB

VmPTE: 56 kB

VmSwap: 0 kB

Threads: 1

SigQ: 0/7182

SigPnd: 0000000000000000

ShdPnd: 0000000000000000

SigBlk: 0000000000010000

SigIgn: 0000000000384004

SigCgt: 000000004b813efb

CapInh: 0000000000000000

CapPrm: 0000001fffffffff

CapEff: 0000001fffffffff

CapBnd: 0000001fffffffff

CapAmb: 0000000000000000

NoNewPrivs: 0

Seccomp: 0

Speculation_Store_Bypass: thread vulnerable

Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff

Cpus_allowed_list: 0-127

Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001

Mems_allowed_list: 0

voluntary_ctxt_switches: 243

nonvoluntary_ctxt_switches: 2

自从 Linux 4.10 开始,/proc/[pid]/status 中的 NoNewPrivs 值表示了线程的 no_new_privs 属性。

no_new_privs

一般情况下,execve() 系统调用能够赋予新启动的进程其父进程没有的权限,最常见的例子就是通过 setuid 和 setgid 来设置程序进程的 uid 和 gid 以及文件的访问权限。这就给不怀好意者钻了不少空子,可以直接通过 fork 来提升进程的权限,从而达到不可告人的目的。

为了解决这个问题,Linux 内核从 3.5 版本开始,引入了 no_new_privs 属性(实际上就是一个 bit,可以开启和关闭),提供给进程一种能够在 execve() 调用整个阶段都能持续有效且安全的方法。

开启了 no_new_privs 之后,execve 函数可以确保所有操作都必须调用 execve() 判断并赋予权限后才能被执行。这就确保了线程及子线程都无法获得额外的权限,因为无法执行 setuid 和 setgid,也不能设置文件的权限。

一旦当前线程的 no_new_privs 被置位后,不论通过 fork,clone 或 execve 生成的子线程都无法将该位清零

Docker 中可以通过参数 --security-opt 来开启 no_new_privs 属性,例如:docker run --security-opt=no_new_privs busybox。下面通过一个例子来体会一下 no_new_privs 属性的作用。

演示开始

先上代码

首先创建一个文件夹

[root@localhost cctest]# pwd

/root/cctest

撸一段 C 代码,显示当前进程的有效用户 id:

#include

#include

#include int main(int argc, char *argv[])

{

printf("Effective uid: %d\n", geteuid());

return 0;

}

将这个文件放到[root@localhost cctest]# vim testnnp.c

将这个文件编译一下(先看看自己装没装gcc)

[root@localhost cctest]# make testnnp

cc testnnp.c -o testnnp

将可执行文件打入 docker 镜像中:

[root@localhost cctest]# vim Dockerfile

[root@localhost cctest]# ls

Dockerfile testnnp testnnp.c

[root@localhost cctest]# cat Dockerfile

FROM fedora:latest

ADD testnnp /root/testnnp

RUN chmod +s /root/testnnp

ENTRYPOINT /root/testnnp

[root@localhost cctest]#

启动docker创建镜像

[root@localhost cctest]# systemctl start docker

[root@localhost cctest]# docker build -t testnnp .

Sending build context to Docker daemon 12.29 kB

Step 1/4 : FROM fedora:latest

Trying to pull repository docker.io/library/fedora ...

latest: Pulling from docker.io/library/fedora

7778c55b9bda: Pull complete

Digest: sha256:07a6341eb8b7dcc45fb392fdac0210362a3901ab19846d29e949d70285883216

Status: Downloaded newer image for docker.io/fedora:latest

---> e0d7a1685fed

Step 2/4 : ADD testnnp /root/testnnp

---> 141ee6d0a292

Removing intermediate container 1d2563738fb2

Step 3/4 : RUN chmod +s /root/testnnp

---> Running in 5efe2d4b5d0e

---> fa7b54ad51b5

Removing intermediate container 5efe2d4b5d0e

Step 4/4 : ENTRYPOINT /root/testnnp

---> Running in 7606701b9e2e

---> 517bea4209ea

Removing intermediate container 7606701b9e2e

Successfully built 517bea4209ea

[root@localhost cctest]#

这边我们做两组实验观察返回的UID,进而得到实验结论

实验一:不设置no_new_privs,有suid

[root@localhost cctest]# docker run -it --rm --user=1000 testnnp

Effective uid: 0

实验二:设置no_new_privs,无suid

[root@localhost cctest]# docker run -it --rm --user=1000 --security-opt=no-new-privileges testnnp

Effective uid: 1000

总结:

当设置了 no_new_privs 标志后,execve 不会赋予运行进程新的权限, suid、sgid 权限会失效并且文件特殊权限也会失效,这意味着 execve 运行的新程序不会拥有比原进程更高的权限。

no_new_privs 标志一旦设置就不能撤销,使用 fork 与 clone 创建的子进程会继承父进程的此标志,且在 execve 的时候也会保留。

这里需要理解的点在于 execve 那些设置了 suid、sgid bit、Linux capabilities 的可执行文件时,未设置 no_new_privs 属性时,进程将会拥有比原进程更高的权限,no_new_privs 可以理解为是对 execve 提权操作的限制,在一些场景中能够增强系统的安全性。

管理Capabilities

大概意思就是使用getcap来查看文件的Capabilities

使用setcap来设置文件的Capabilities

下面来几个使用的例子

[root@localhost ~]# getcap /bin/ping

/bin/ping = cap_net_admin,cap_net_raw+p

#-r      递归查询

[root@localhost ~]# getcap -r /usr/

/usr/bin/newgidmap = cap_setgid+ep

/usr/bin/newuidmap = cap_setuid+ep

/usr/bin/ping = cap_net_admin,cap_net_raw+p

/usr/sbin/arping = cap_net_raw+p

/usr/sbin/clockdiff = cap_net_raw+p

/usr/sbin/suexec = cap_setgid,cap_setuid+ep

libcap-ng

安装

[root@localhost cctest]# yum install libcap-ng-utils

用法

libcap-ng 使用 filecap 命令来管理文件的 capabilities。有几个需要注意的地方:

filecap 添加删除或查看 capabilities 时,capabilities 的名字不需要带 CAP_ 前缀(例如,使用 NET_ADMIN 代替 CAP_NET_ADMIN);

filecap 不支持相对路径,只支持绝对路径;

filecap 不允许指定 capabilities 作用的集合,capabilities 只会被添加到 permitted 和 effective 集合。

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: