Docker安全(二)
继上一篇,在(一)中,总结了常用的docker环境的判断和信息收集,以及常用的三种逃逸方式:Priviliged 特权模式逃逸、挂载宿主机 procfs 逃逸、挂载 Docker Socket 逃逸,本文将总结一些通过docker自身漏洞以及安全配置不当造成的逃逸或者其他渗透利用方式
CVE-2019-16884
攻击者可以在容器镜像中可以声明一个VOLUME并挂载至/proc,之后欺骗runc使其认为AppArmor已经成功应用从而绕过AppArmor策略
(runc是一个根据OCI规范实现的CLI工具,用于生成和运行容器,docker的runtime使用的就是runc)
影响版本:runc <= 1.0.0-rc8

漏洞复现:
首先创建apparmor规则
1 |
|
(注:如果docker环境中不能使用vim,可以使用cat+EOF)
随后创建一个flag文件,以证实逃逸是否成功

再对刚才创建的规则进行应用
1 |
|
这个时候如果我们启动一个正常镜像并尝试读取flag是报错的,会提示无权限读取flag内容,这个时候我们就可以利用漏洞启动一个恶意镜像,来读取flag
1 |
|
逃逸成功,并成功读取宿主机文件

CVE-2020-15257
containerd是行业标准的容器运行时,可作为Linux和Windows的守护程序使用。在版本1.3.9和1.4.3之前的容器中,容器填充的API不正确地暴露给主机网络容器。填充程序的API套接字的访问控制验证了连接过程的有效UID为0,但没有以其他方式限制对抽象Unix域套接字的访问。这将允许在与填充程序相同的网络名称空间中运行的恶意容器(有效UID为0,但特权降低)导致新进程以提升的特权运行,总结来说,攻击者可以使容器与宿主机处于同一网络命名空间,从而突破容器命名空间隔离,实现容器逃逸
影响版本:containerd < 1.4.3、containerd < 1.3.9
知识补充
containerd-shim:
夹杂在containerd和runc之间,每次启动一个容器,都会创建一个新的containerd-shim进程,它通过指定的三个参数:容器id、bundle目录、运行时二进制文件路径,来调用运行时的API创建、运行容器,持续存在到容器实例进程退出为止,将容器的退出状态反馈给containerd
Unix套接字:
在Linux系统中,有一种Unix域套接字,可以用于同一个主机上的进程之间进行通信,它的API调用方法和普通的TCP/IP的套接字一样,也是调用socket函数创建一个套接字,域设置成AF_UNIX,套接字的类型可以是流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)
1 |
|
docker网络模式:
1 |
|
(在使用docker run命令创建并运行容器时,可以使用–network选项指定容器的网络模式)
每次启动一个容器时,containerd会创建一个新的containerd-shim进程,由containerd-shim进程(而不是containerd)来直接控制容器的整个生命周期,而containerd在创建containerd-shim之前,会创建一个Unix域套接字,containerd传递Unix域套接字文件描述符给containerd-shim。containerd-shim在正式启动之后,会基于父进程(也就是containerd)传递的Unix域套接字文件描述符,建立gRPC服务,对外暴露一些API用于container、task的控制,此时,containerd-shim做为server向外提供服务,containerd做为client,调用containerd-shim提供的API实现对容器的间接管理,而抽象Unix域套接字没有权限限制,所以只能靠连接进程的UID、GID做访问控制,限定了只能是root(UID=0,GID=0)用户才能连接成功。通过访问/proc/net/unix文件,可以获取到当前网络命名空间下所有的Unix域套接字信息,在默认情况下,docker run启动的容器的网络模式是bridge,容器和主机之间实现了网络隔离,所以在容器内部读取/proc/net/unix文件,看不到任何信息

但是在host模式下,由于容器和主机共享同一个网络命名空间,容器能访问到主机中的所有网络资源,所以在容器内部读取/proc/net/unix文件,显示的就是真实主机中的信息

漏洞复现

首先下载容器,并使用host网络模式启动,进入docker之后进行记下id号,后面会用到

接下来就要搬出我们大名鼎鼎的CDK,一款专门为K8s、Docker 和 Containerd 打造的的安全测试(https://github.com/cdk-team/CDK)
这里我们将下载好的压缩包解压,然后直接copy id号,将工具传入到docker容器

进入容器,在容器内执行exp,同时攻击机设置监听
1 |
|
成功完成逃逸获得宿主机的shell

CVE-2022-0492
漏洞发生的点在于对修改cgroup 的release_agent缺失权限校验,导致给逃逸利用的门槛进一步降低(以前需要CAP_SYS_ADMIN权限,该漏洞无需CAP_SYS_ADMIN),当容器没有开启额外安全措施时,获得容器内root权限即可逃逸到宿主机
漏洞产品: linux kernel - cgroup
知识补充
cgroup:
cgroup 即Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)
cgroup 有如下子系统:
1 |
|
宿主机中的cgroup 都在/sys/fs/cgroup 下,可以看到各个cgroup 子系统

docker 中对应的cgroup 子系统就是宿主机中该cgroup 的子节点,docker中查看memory cgroup
如果一个服务器有一个普通的用户,并且这个用户加入了 docker 组,则这个用户已经是 root 了

主机docker 目录中的对应容器名节点,一模一样

release_agent:
cgroup的每一个subsystem都有参数notify_on_release,这个参数值是Boolean型,1或0。分别可以启动和禁用释放代理的指令。如果notify_on_release启用(为1),当cgroup不再包含任何任务时(即cgroup中最后一个进程退出的时候,cgroup的tasks文件里的PID为空时),系统内核会执行release_agent参数指定的文件里的内容.通过修改notify_on_release 文件的形势修改notify_on_release的值

漏洞发生就位于对release_agent 的修改,在原本只要可以操作cgroup 便可对release_agent 进行修改,而需要CAP_SYS_ADMIN才可以使用cgroup。但后来有人研究发现通过unshare命令创建新的namespace可以获得全部的capbilities,那么对于CAP_SYS_ADMIN 的限制就不存在了,漏洞利用的门槛一下子降低了很多
unshare命令:
unshare 命令功能为取消指定的共享父进程中指定的命名空间,然后执行指定的程序并加入新创建的namespace。和我们漏洞利用相关的就是,unshare 新创建的namespace 拥有包括CAP_SYS_ADMIN在内的全部的capbilities

漏洞复现
在存在漏洞版本的内核的linux中使用docker 即可,我这里直接使用metarget搭建环境

带有 sys_admin 启动 Docker, 关闭 apparmor
1 |
|
上面是带有 CAP_SYS_ADMIN 权限的 Docker,当进行mount 的时候会主动加载 release_agent
进入 Docker 内部,首先挂载 cgroup

修改 release_agent 触发逃逸,将 notify_on_release 设置为1,开启 task 进程清空后执行 release_agent 功能
1 |
|
创建 release_agent 触发时执行的文件
1 |
|
修改release_agent ,指向 cmd 文件在宿主机中的路径(上面已经获取了容器根目录在宿主机中的路径)
1 |
|
接下来向 x cgroup 节点中输入一个任务,将自己所属的 sh 的pid 写入 cgroup.procs
1 |
|
sh 命令只执行了一个 echo 指令,一瞬间就会结束,那么 x cgroup 节点中就 / 没有任何任务了,触发 notify_on_release 执行 release_agent 指向的 /cmd 文件,内核触发,在容器外执行我们指定的命令,完成逃逸


从脏管道到Docker逃逸
脏管道介绍
提到脏管道,就不得不说CVE-2022-0847,它是一个是存在于 Linux 内核 5.8 及之后版本中的本地提权漏洞,攻击者通过利用此漏洞,可覆盖重写任意可读文件中的数据,从而可将普通权限的用户提升到特权 root,漏洞原理类似于 CVE-2016-5195 脏牛漏洞(Dirty Cow),但它更容易被利用。漏洞作者将此漏洞命名为 Dirty Pipe(脏管道)
漏洞利用:
exp: https://github.com/AlexisAhmed/CVE-2022-0847-DirtyPipe-Exploits.git
https://github.com/Al1ex/CVE-2022-0847
两种利用方式:一种是修改/覆盖只读文件提权,另一种是劫持 SUID 二进制文件提权
第一种我失败了,应该是内核版本的问题

第二种以 root 身份运行的只读 SUID 进程内存中注入和覆盖数据

利用条件与限制
利用条件:
1、有可读权限或者可以传回文件的文件描述符。
2、有漏洞的内核
利用的限制:
1、第一个字节不可修改,并且单次写入不能大于 4k
2、只能单纯覆盖,不能调整文件大小
3、由于漏洞基于内存页,所以不会对磁盘有影响
与Docker的关系
由于 Docker 和宿主机是共享内核,尽管与其他进程资源是隔离开的,内核漏洞也很可能会对 Docker 容器造成安全问题
对于容器的影响:
假设我们有两个容器:liu1和liu2,由于 Docker 本质上是由一组互相重叠的层所组层的,然后容器引擎将其合并到一起,原本这些层都是只读的,但由于脏管道漏洞的影响,我们可以在 liu1 容器里修改 /etc/passwd 使得 liu2 容器的 /etc/passwd 被修改

docker用户组提权
Docker 需要 root 权限才能跑起来,其运行的所有命令都是需要 sudo 来运行,因此 Docker 监护进程有一个特性,它能被允许访问 root 用户或者是在 docker 组里面的所有用户,这就如同拥有 root 的访问权限。简而言之,如果我们拿到了一个docker组内用户的权限,就可以提升到root权限
漏洞复现
如果一个服务器有一个普通的用户,并且这个用户加入了 docker 组,则这个用户已经是 root 了
方法一:
环境搭建
1 |
|

默认情况下,Docker 软件包是会默认添加一个 docker 用户组的,Docker 守护进程会允许 root 用户和 docker组用户访问 Docker,给用户提供 Docker 权限就相当于给用户无需认证便可以随便获取的 root 权限
1 |
|

参数 -v 将容器外部的目录 / 挂载到容器内部 /hostOS,然后获取到宿主机的 root 权限
方法二:
环境搭建
1 |
|
将 /etc/ 目录挂载进 Docker,查看 shadow 和 passwd

这里已经获取到密码 hash,我们添加一个特权账号
1 |
|


docker远程API未授权访问逃逸
Docker的2375端口主要用于Docker守护进程的监听和通信。它主要用于Docker容器的网络连接和通信,包括容器的启动、停止、删除等操作。该端口可以被Docker守护进程用于接收来自客户端的请求,并与其进行交互和通信,在早期的版本安装Docker是会默认将2375端口对外开放,目前改为默认只允许本地访问。而docker远程API未授权访问漏洞原理就是:docker remote api 可以执行 docker 命令,docker 守护进程监听在 0.0.0.0,可直接调用 API 来操作 docker
如何开启远程访问(环境搭建)
1 |
|
如何检测2375端口存在未授权?
可以直接在浏览器访问敏感接口

或者通过下面这条指令,如果返回404,说明存在
1 |
|

fofa语法索引
1 |
|

漏洞复现
1 |
|
