Docker是时下使用范围最广的开源容器技术之一,具有高效易用等优点。由于设计的原因,Docker天生就带有强大的安全性,甚至比虚拟机都要更安全,但Docker也会被攻破。
因为Docker所使用的是隔离技术,就导致了容器内的进程无法看到外面的进程,但外面的进程可以看到里面,所以如果一个容器可以访问到外面的资源,甚至是获得了宿主主机的权限,这就叫做“Docker逃逸”。
这里选择ubuntu22.04
建议按照官方文件安装配置,非常详细
首先安装docker
curl -fsSL https://get.docker.com/ | sh
docker run ubuntu echo "welcome" //判断是否有welcome输出
2、安装 Docker-Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version //判断是否有版本信息
#3、Docker 设置国内镜像源
vi /etc/docker/daemon.json
{
"registry-mirrors": ["http://hub-mirror.c.163.com"]
}
重启服务
systemctl restart docker.service
容器逃逸的实质是从Cgroup/Namespace 限制权限的进程获取更多权限
Linux的namespace是一种内核特性,可以将系统资源进行隔离,每个隔离的部分被称为一个namespace。通过使用namespace,可以将不同进程之间的资源进行隔离,使得它们感觉像是在独立的环境中运行
用简洁明了的话来说,namespace实现了容器与容器,容器与宿主机之间的隔离
而业内广为人知的****privileged特权逃逸的本质也是因为特权环境打破了容器与宿主机直接的隔离实现了容器逃逸
我们可以详细看一下namespace的几种类型:
PID namespace:使得每个进程都有一个独立的进程ID,进程只能看到相同namespace下的其他进程。
User namespace:使得不同namespace下的进程可以有不同的用户和用户组ID,进程只能对相同namespace下的用户进行权限管理。
Mount namespace:使得每个namespace可以有独立的挂载点和文件系统层次结构,进程只能看到相同namespace下的文件系统。
Network namespace:使得每个namespace有独立的网络设备、IP地址、端口等网络资源,进程只能访问相同namespace下的网络资源。
UTS namespace:使得每个namespace有独立的主机名和域名,进程只能访问相同namespace下的主机名和域名。
通过使用这些namespace,可以在不同的进程之间实现资源的隔离,提高系统的安全性和稳定性。
Cgroups本质上是在内核中附加的一系列钩子(hook),当程序运行时,内核会根据程序对资源的请求触发相应的钩子,以达到资源追踪和限制的目的。在Linux系统中,Cgroups对于系统资源的管理和控制非常重要,可以帮助管理员更加精细化地控制资源的分配和使用
Cgroups主要实现了对容器资源的分配,限制和管理
Capabilities是Linux一种安全机制,在linux内核2.2之后引入,用于对权限更细致的控制
而容器社区一直在努力将纵深防护,最小权限等理念和原则落地
这里我们再次提到NameSpace和cgroups
Linux 命名空间(NameSpace):实现文件系统、网络、进程、主机名等方面的隔离
Linux 控制组(cgroups):实现 CPU、内存、硬盘等方面的隔离
如果设定了以下配置就会导致相应的隔离机制失效:
其实原理很简单,就是通过权限的变更打破了原来的文件系统、网络、进程、主机名等方面的隔离
--privileged:使容器内的 root 权限和宿主机上的 root 权限一致,权限隔离被打破
--net=host:使容器与宿主机处于同一网络命名空间,网络隔离被打破
--pid=host:使容器与宿主机处于同一进程命令空间,进程隔离被打破
--volume /:/host:宿主机根目录被挂载到容器内部,文件系统隔离被打破
ubuntu22.04的版本
privileged 特权容器可以说是业界最常见以及最广为人知的逃逸手法
privileged 特权容器的权限其实有很多,所以也有很多不同的逃逸方式,挂载设备读写宿主机文件是特权容器最常见的逃逸方式之一
如何判断当前容器是以Privileged 特权模式启动的呢?
这里提供两种方式
我们可以使用 fdisk -l 查看宿主机的磁盘设备
而如果不在 privileged 容器内部,是没有权限查看磁盘列表并操作挂载的
fdisk -l
docker run --rm --privileged=true -it alpine
进入容器后我们判断当前的特权
无论是fdisk -l 还是 cat /proc/self/status | grep CapEff
这里我们尝试使用文件挂载进行逃逸
挂载(Mount)是将一个文件系统(通常是存储设备上的文件系统)连接到操作系统的目录树中的过程。挂载将某个文件系统与一个目录关联起来,使得该目录成为文件系统的访问点。通过挂载,文件系统中的文件和目录可以被读取和写入,就好像它们是操作系统上本地的文件一样。
mkdir /test && mount /dev/sda1 /test
1.mkdir /test - 这部分命令使用mkdir(make directory)指令在根目录(/)下创建一个名为test的新文件夹。即创建一个名为test的目录。
mount /dev/sda1 /test - 这部分命令使用mount指令将/dev/sda1文件系统挂载到之前创建的test目录上。/dev/sda1通常是硬盘分区的设备名称,而/test则是挂载点,指示将该文件系统挂载到此目录。
综合起来,命令mkdir /test && mount /dev/sda1 /test创建了一个名为test的目录,并将/dev/sda1文件系统挂载到该目录中。这意味着该文件系统中的文件和文件夹将出现在/test目录中,可以通过/test目录访问和操作这些文件。
然后我们再通过cat 去查看etc/shadow就可以成功看到宿主机的内容了
cat /test/etc/shadow
于此同时我们也可以切换到/test目录下,去做更多有价值的事情
例如操作宿主机的 crontab config file, /root/.ssh/authorized_keys, /root/.bashrc 等文件,而达到逃逸的目的
值得一提的是
从对抗的层面上,不建议将逃逸的行为当成可以写入宿主机特定文件 (如 /etc/cron*, /root/.ssh/authorized_keys 等文件) 的行为,应该根据目标选择更趋近与业务行为的手法,容器逃逸的利用手段会比大部分情况下的命令执行漏洞利用要灵活。
以目标 “获取宿主机上的配置文件” 为例,以下几种逃逸手法在容易在防御团队中暴露的概率从大到小,排序如下(部分典型手法举例,不同的 EDR 情况不同):
mount /etc + write crontab
mount /root/.ssh + write authorized_keys
old CVE/vulnerability exploit
write cgroup notify_on_release
write procfs core_pattern
volumeMounts: / + chroot
remount and rewrite cgroup
websocket/sock shell + volumeMounts: /path
其他几种就暂时先不介绍了