[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集合。
  • 功能:线程可以从 EffectiveInheritable 集合中添加或删除 capability,前提是添加或删除的 capability 必须包含在 Permitted 集合中。

Bounding (CapBnd):

  • 目的:限制进程在其生命周期内可获得的capabilities能力集合。
  • 功能:即使某个capabilities在进程的InheritablePermitted集合中,它也不能获得该capabilities,除非它也在Bounding集合中。

Ambient (CapAmb):

  • Linux 4.3 内核新增了一个 capabilities 集合叫 Ambient ,用来弥补 Inheritable 的不足。Ambient 具有如下特性:
    • PermittedInheritable 未设置的 capabilitiesAmbient 也不能设置。
    • PermittedInheritable 关闭某权限(比如 CAP_SYS_BOOT)后,Ambient 也随之关闭对应权限。这样就确保了降低权限后子进程也会降低权限。
    • 非特权用户如果在 Permitted 集合中有一个 capability,那么可以添加到 Ambient 集合中,这样它的子进程便可以在 AmbientPermittedEffective 集合中获取这个 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() 前线程的 capabilitiesP' 代表执行 execve() 后线程的 capabilitiesF 代表可执行文件的 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 特权模式容器逃逸