[Docker容器安全2]Linux Capabilities & 特权模式容器逃逸
笔记
学习下Linux capabilities
概念
Linux capabilities 将 root 权限划分为较小的、独立的单元,使进程可以拥有部分权限,以对 root 权限进行更细粒度的控制。
有哪些?
| 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 | 允许改变进程的 GID |
| CAP_SETFCAP | 允许为文件设置任意的 capabilities |
| CAP_SETPCAP | 参考 capabilities man page |
| CAP_SETUID | 允许改变进程的 UID |
| CAP_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 分为进程(Processes) capabilities 和文件(Binaries) capabilities。
进程(Processes) capabilities
每一个线程,具有 5 个 capabilities 集合,每一个集合使用 64 位掩码来表示,显示为 16 进制格式。这 5 个 capabilities 集合分别是:
- Permitted
- Effective
- Inheritable
- Bounding
- Ambient
每个集合中都包含零个或多个 capabilities。这5个集合的具体含义如下:
Inherited (CapInh):
- 目的:确定从父进程传递下来的
capabilities。 - 功能:当创建(
exec())一个新进程时,它会从父进程继承这个集合中的capabilities。不会添加到新线程的Effective集合中,它只会影响新线程的Permitted集合。
Effective (CapEff):
- 目的:表示进程在任何时刻实际使用的
capabilities。 - 功能:是内核检查各种操作权限时使用的
capabilities集合。
Permitted (CapPrm):
- 目的:定义进程可以拥有的最大
capabilities集合。 - 功能:线程可以从
Effective或Inheritable集合中添加或删除capability,前提是添加或删除的capability必须包含在Permitted集合中。
Bounding (CapBnd):
- 目的:限制进程在其生命周期内可获得的
capabilities能力集合。 - 功能:即使某个
capabilities在进程的Inheritable或Permitted集合中,它也不能获得该capabilities,除非它也在Bounding集合中。
Ambient (CapAmb):
- Linux 4.3 内核新增了一个 capabilities 集合叫 Ambient ,用来弥补 Inheritable 的不足。Ambient 具有如下特性:
Permitted和Inheritable未设置的capabilities,Ambient也不能设置。- 当
Permitted和Inheritable关闭某权限(比如CAP_SYS_BOOT)后,Ambient也随之关闭对应权限。这样就确保了降低权限后子进程也会降低权限。 - 非特权用户如果在
Permitted集合中有一个capability,那么可以添加到Ambient集合中,这样它的子进程便可以在Ambient、Permitted和Effective集合中获取这个capability - 有点抽象,在具体的使用场景感受
文件(Binaries) capabilities
文件的 capabilities 功能,需要文件系统的支持。如果文件系统使用了 nouuid 选项进行挂载,那么文件的 capabilities 将会被忽略。
类似于线程的 capabilities,文件的 capabilities 包含了 3 个集合:
- Permitted
- Inheritable
- Effective
和线程 capabilities 有较大差异的是Effective集合:
Effective
这不是一个集合,仅仅是一个标志位。如果设置开启,那么在执行完 execve() 后,线程 Permitted 集合中的 capabilities 会自动添加到它的 Effective 集合中。对于一些旧的可执行文件,由于其不会调用 capabilities 相关函数设置自身的 Effective 集合,所以可以将可执行文件的 Effective bit 开启,从而可以将 Permitted 集合中的 capabilities 自动添加到 Effective 集合中。
公式:运行 execve() 后 capabilities 的变化
我们用 P 代表执行 execve() 前线程的 capabilities,P' 代表执行 execve() 后线程的 capabilities,F 代表可执行文件的 capabilities。那么:
P’(ambient) = (file is privileged) ? 0 : P(ambient)
P’(permitted) = (P(inheritable) & F(inheritable)) |
(F(permitted) & P(bounding))) |
P’(ambient)
P’(effective) = F(effective) ? P’(permitted) : P’(ambient)
P’(inheritable) = P(inheritable) [i.e., unchanged]
P’(bounding) = P(bounding) [i.e., unchanged]
注:
- 上面的公式是针对系统调用
execve()的,如果是fork(),那么子线程的 capabilities 信息完全复制父进程的 capabilities 信息。
capabilities 操作
查看 capabilities
相关工具:libcap-ng
安装(Centos):yum install libcap-ng-utils
使用/proc/<pid>/status查看进程capabilities
cat /proc/1234/status | grep Cap
cat /proc/$$/status | grep Cap #This will print the capabilities of the current process
通常有5行
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
CapAmb: 0000000000000000
使用capsh解码让其可读
# capsh --decode=0000003fffffffff
0x0000003fffffffff=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,37
使用 getpcaps 工具
# ping baidu.com > /dev/null&
[1] 9227
# getpcaps 9227
Capabilities for `9227': = cap_net_admin,cap_net_raw+p
# cat /proc/9227/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000003000
CapEff: 0000000000000000
CapBnd: 0000001fffffffff
CapAmb: 0000000000000000
# capsh --decode=0000000000003000
0x0000000000003000=cap_net_admin,cap_net_raw
注意到这里启动的 ping 它的 CapEff 是 0,这点后面会提(应该)
文件capabilities查看
getcap /usr/bin/ping
/usr/bin/ping = cap_net_admin,cap_net_raw+p
###
capability[,capability…]+(e|i|p),其中 e 表示 effective,i 表示 inheritable,p 表示 permitted。不同的分组之间通过空格隔开,例如:Current: = cap_sys_chroot+ep cap_net_bind_service+eip。再举一个例子,cap_net_bind_service+e cap_net_bind_service+ip 和 cap_net_bind_service+eip 等价。
遍历目录下所有可执行文件的capabilities(2>/dev/null 则是将错误输出重定向到 /dev/null,即忽略错误消息)
# getcap -r / 2>/dev/null
/usr/sbin/arping = cap_net_raw+p
/usr/sbin/clockdiff = cap_net_raw+p
/usr/sbin/suexec = cap_setgid,cap_setuid+ep
/usr/bin/ping = cap_net_admin,cap_net_raw+p
/usr/bin/newuidmap = cap_setuid+ep
/usr/bin/newgidmap = cap_setgid+ep
使用capsh设置的新shell
使用 capsh 工具在运行 ping 之前移除 CAP_NET_RAW capabilities,并输出当前权限设置。
# capsh --drop=cap_net_raw --print -- -c "ping baidu.com"
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_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),994(docker)
ping: socket: 不允许的操作
使用setcap设置文件的capabilities
设置新的限制
setcap cap_net_admin+p /usr/bin/ping
也可以清空capabilities
setcap -r </path/to/binary>
具有 capabilities 感知能力的文件
这部分的内容开始看的时候有点难理解,之后在HackTrick找了段英文的解释
The capability-aware binaries won't use the new capabilities given by the environment, however the capability dumb binaries will use them as they won't reject them. This makes capability-dumb binaries vulnerable inside a special environment that grant capabilities to binaries.
Capability-aware Binaries(具有能力感知的二进制文件):
- 这些二进制文件是设计和编写时考虑到 Linux capabilities 的。因此,它们会显式地检查和管理它们需要的 capabilities。
- 当这些二进制文件在具有额外 capabilities 的环境中运行时,它们不会自动使用这些新授予的 capabilities。相反,它们会检查当前的 capabilities 设置,并决定是否使用这些权限。
Capability-dumb Binaries(不具备能力感知的二进制文件):
- 这些二进制文件在编写时没有考虑 Linux capabilities,它们对这些权限的存在或不存在不敏感。
- 当这些二进制文件在具有额外 capabilities 的环境中运行时,它们不会主动拒绝这些新授予的 capabilities。因此,它们会自动拥有并使用这些权限。
结合文章ping命令的实验就好理解了:https://icloudnative.io/posts/linux-capabilities-in-practice-2/
Privileged 特权模式容器逃逸
当使用--privileged=true选项运行容器时,Docker会赋予容器几乎与主机相同的权限4。具体来说,这个选项做了以下两件事情:
- 给容器添加了所有的capabilities
- 允许容器访问主机的所有设备
docker run --privileged=true -it ubuntu
在容器内部执行下面的命令,从而判断容器是不是特权模式,如果是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff
cat /proc/$$/status | grep CapEff
查看挂载磁盘设备
fdisk -l
Disk /dev/vda: 40 GiB, 42949672960 bytes, 83886080 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x000beb6e
Device Boot Start End Sectors Size Id Type
/dev/vda1 * 2048 83886046 83883999 40G 83 Linux
在容器内部执行以下命令,将宿主机文件挂载到 /test 目录下
mkdir /test && mount /dev/vda1 /test

方法一:写计划任务
方法二:
chroot /mnt adduser john
参考
Linux Capabilities 入门教程:概念篇
Linux Capabilities 入门教程:基础实战篇
Linux Capabilities 入门教程:进阶实战篇
HackTricks - Linux Capabilities
docker run:--privileged=true选项解析(特权模式:赋予容器几乎与主机相同的权限)-CSDN博客
Privileged 特权模式容器逃逸