翻译总结于:《A Methodology for Penetration Testing Docker Systems》
文章从两个攻击模型进行分析,结合错误配置和已知漏洞对docker渗透测试进行总结归纳,并给出了一份docker渗透测试检查清单。
一 攻击模型
作者讨论了两种情况:在容器内和在容器外。在容器内部,攻击者会聚焦在逃逸隔离(即容器逃逸)。在容器外部,即宿主机上,攻击者还没有主机特权,这时候攻击者将会使用Docker(即Docker daemon攻击)来获得权限。
容器逃逸重点在攻击和绕过隔离和保护机制,其中又可分成两种:一种是从容器逃逸到主机(CVE-2017-7308),另一种是从容器逃逸到另一个容器获取其中数据。
在Docker守护程序攻击中,攻击者不会攻击docker或者docker隔离,而是使用docker来执行恶意操作。因为Docker Daemon需要以root身份运行,攻击者恶意控制Docker Daemon后能以root权限执行操作。
二 Docker中的已知漏洞
作者从错误配置和安全漏洞两个角度对上述两个场景中的安全问题进行了梳理。漏洞问题是自身程序问题,错误配置更多的是用户使用问题。
1、错误配置
前两个错误配置与在主机上执行的Docker Daemon有关,其他错误配置与从容器内执行的容器逃逸攻击有关。
-
A.Docker Permissions:
(1)没有sudo权限的用户添加到docker用户组后,该用户可以通过运行docker挂载文件来读取host上的敏感信息甚至对系统进行改变。
# 没有sudo权限的用户但在docker用户组中
(host)$ sudo -v
(host)$ groups | grep -o docker
# 用户可以通过运行docker挂载文件来读取host上的敏感信息
(host)$ docker run -it --rm -v /:/host ubuntu:latest bash
(cont)# grep admin /host/etc/shadow
(2)可读写的Docker Socket:一些管理员设置了所有用户的读写权限,给了所有用户Docker Daemon的权限,尽管用户不在docker group也能使用docker。
# 所有用户对Docker Daemon都有读写权限(666)
(host)$ groups | grep -o docker
(host)$ ls -l /var/run/docker.sock
# 用户可以通过运行docker挂载文件来读取host上的敏感信息
(host)$ docker run -it --rm -v /:/host ubuntu:latest bash
(cont)# grep admin /host/etc/shadow
(3)setuid bit:系统管理员在docker二进制文件上设置setuid位。setuid位是Unix中的权限位,它允许用户运行二进制文件而不是其本身作为二进制文件的所有者。如果为docker二进制文件错误配置了setuid位,那么用户将能够以root身份执行docker。
# 允许用户运行docker二进制文件(chmod +s)
(host)$ sudo -v
(host)$ groups | grep -o docker
(host)$ ls -halt /usr/bin/docker
# 用户可以运行docker文件
(host)$ docker run -it --rm -v /:/host ubuntu:latest bash
(cont)# grep admin /host/etc/shadow
B.Readable Configuration Files:
docker用户为了减少docker设置环境的复杂性,会使用docker-compose等保存一些必要的设置。但这些配置文件经常包含一些敏感信息,如果文件权限配置不当,可能会造成信息泄露。
(1).docker/config.json:.docker/config.json中会缓存用户登录到仓库的凭证。
(2)docker-compose.yaml:docker-compose.yaml文件中会包含一些敏感信息如密码等。C.Privileged Mode:使用特权模式启动容器,可以获取大量设备文件访问权限。因为当管理员执行docker run —privileged时,Docker容器将被允许访问主机上的所有设备,并可以执行mount命令进行挂载。当控制使用特权模式启动的容器时,docker管理员可通过mount命令将外部宿主机磁盘设备挂载进容器内部,获取对整个宿主机的文件读写权限,此外还可以通过写入计划任务等方式在宿主机执行命令。该文章中作者还介绍了特权模式下滥用cgroups特性的一个例子。
D.Capabilities:
Docker容器在启动时只有最小的Capabilities,但是在运行时添加的额外的Capabilities可能会给容器执行某些操作的权限,其中一些操作允许docker逃逸。
(1)CAP_SYS_ADMIN。
(2)CAP_DAC_READ_SEARCH:这个功能允许进程对容器进行转义,见https://medium.com/@fun_cuddles/docker-breakout-exploit- analysis-a274fff0e6b3。E.Docker Socket:
Docker Socket(即/var/run/docker.sock)是客户端与Docker Daemon通信的方式。每当用户执行docker client命令时,docker客户端就会向socket发送一个http请求。用户不使用docker客户端也可以直接向套接字发送http请求。因此,一些错误配置socket的方法也会带来很多安全危险。
# 使用docker客户端
docker ps -a
# 直接向socket发送http请求
curl --unix-socket /var/run/docker.sock -H 'Content-Type: application/json' "http://localhost/containers/json?all=1"
(1)Container Escape Using the Docker Socket:如果/var/run/docker.sock作为volume挂载到容器上,那么容器中的进程可以完全访问主机上的docker。
(2)Sensitive Information:当容器可以访问/var/run/docker.sock时,用户可以查看现有容器的配置,其中可能包含一些敏感信息。
(3)Remote Access:如果没有配置docker API只监听本地主机,那么网络上的每个主机都可以访问docker,攻击者可以利用这种错误配置启动其他容器。
F.iptables Bypass:
linux内核有一个名为Netfilter的内置防火墙,可以配置iptables程序。这个防火墙由存储在表中的多个规则链组成。每个表都有不同的用途,例如,有一个用于地址转换的nat表和一个用于流量过滤的filter表。每个表都有有序的规则链,这些规则链也有不同的用途。例如,filter表中有分别用于所有传出和传入流量的output和input链。可以使用一个名为iptables的程序来配置这些规则。
当docker daemon启动时,它会建立自己的链和规则来创建隔离网络。可以使用docker绕过防火墙规则。详见https://www.cnblogs.com/qjfoidnh/p/11567309.html。G.ARP Spoofing:
默认情况下,所有docker容器都添加到同一个网桥网络中,且docker容器还接收CAP_NET_RAW功能,这允许可以创建原始数据包。因此,默认情况下,容器能够ARP欺骗其他容器。2019 KCon:针对Docker容器网络的ARP欺骗与中间人攻击.pdf
2、软件漏洞
文章中列举了一些最近的且已完全公开的可能在渗透测试期间使用的bug。
(1)CVE-2019-16884
(2)CVE-2019-13139
(3)CVE-2019-5736
(4)CVE-2019-5021
(5)CVE-2018-15664
(6)CVE-2018-9862
(7)CVE-2016-3697
三 docker下的渗透测试
首先需要对目标系统执行侦查来收集数据,然后使用收集到的信息来识别弱点和漏洞。
1、手动发现漏洞
-
A.检测是否处于容器中:
首先,需要判断系统是否处于docker容器中。
(1)检查/.dockerenv文件是否存在。
(2)Control Group,检查/proc/1/cgroup内是否包含"docker"等字符串。
(3)Running Processes,进程的数量和pid为1的进程可以很好地指示是否在容器中。
(4)Available Libraries and Binaries,如果我们看到大量丢失的包、二进制文件或库,说明是在容器中运行的。
(5)检查是否存在container环境变量。 -
B.在容器内的渗透测试:
在容器内执行代码,攻击者着重于容器逃逸。docker daemon是以root权限运行的,因此容器逃逸后就可以获得对主机的root权限。
(1)用户识别:第一步需要确定是否是特权用户并识别其他用户。
# 使用id查看当前用户
(cont)# id
# 查看/etc/passwd查看所有用户
(cont)# cat /etc/passwd
(2)识别容器的操作系统(或者Docker镜像)
# 所有linux发行版都有/etc/os-release,其中包含了正在运行的操作系统的信息
docker run -it --rm centos:latest cat /etc/os-release
# 还可以看看容器正在运行的进程
docker exec database ps -A -o pid,cmd
(3)识别主机操作系统:因为容器使用宿主的内核,所以可以使用内核版本来标识宿主信息,从而检测一些内核利用。
docker run -it --rm alpine:latest cat /etc/os-release
(4)读环境变量:环境变量是启动容器时与容器通信信息的一种方式。当一个容器启动时,环境变量被传递给它,这些变量通常包含密码和其他敏感信息。
(host)$ docker run --rm -e MYSQL_ROOT_PASSWORD=supersecret -- name=database mariadb:latest
(host)$ docker exec -it database bash
# 使用env命令列出在docker中设置的所有环境变量,或者查看进程的/proc/lib/environment文件
(cont)# env
(5)检查Capabilities:通过查看/proc/self/status来查看容器的内核功能。其中CapEff是当前功能的值,可以使用capsh工具从十六进制值获取功能列表。可以使用这个来检查是否有可以用来容器逃逸的功能。
(cont)# grep Cap /proc/self/status
CapInh: 00000000a80425fb # CapInh可继承能力是允许子进程获得的功能
CapPrm: 00000000a80425fb # CapPrm允许能力是一个进程可以使用的最大功能
CapEff: 00000000a80425fb # CapEff是进程有的功能
CapBnd: 00000000a80425fb # CapBnd是调用树中允许的功能
CapAmb: 0000000000000000 # CapAmb是非root子进程可以继承的功能
(host)$ capsh --decode=00000000a80425fb 0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,
cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap, cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,
cap_audit_write,cap_setfcap
(6)检查特权模式:如果容器以特权模式运行,它将获得所有功能,因此可以通过查看能力(0000003fffffffff是所有能力的表示)来检查是否以特权模式运行进程。
(host)$ docker run -it --rm --privileged ubuntu:latest grep CapEff /proc/1/status
CapEff: 0000003fffffffff
(host)$ capsh --decode=0000003fffffffff 0x0000003fffffffff=cap_chown,cap_dac_override,
cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill, cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable, cap_net_bind_service,cap_net_broadcast,cap_net_admin, cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module, cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,
cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource, cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease, cap_audit_write,cap_audit_control,cap_setfcap, cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm, cap_block_suspend,cap_audit_read
(7)检查volumes:卷中可能包含敏感信息,可以通过查看挂载的文件系统位置来查看。
(host)$ docker run -it --rm -v /tmp:/host/tmp ubuntu cat /proc /mounts
overlay / overlay...
(8)检查挂载的docker socket
# 查看挂载
(host)$ docker run -it --rm -v /var/run/docker.sock:/var/run/
docker.sock ubuntu grep docker.sock /proc/mounts
tmpfs /run/docker.sock tmpfs rw,nosuid,noexec,relatime,size
=792244k,mode=755 0 0
# 或者寻找类似于docker.sock名称的文件
(host)$ docker run -it --rm -v /var/run/docker.sock:/var/run/
docker.sock ubuntu find . -name "docker.sock" / /run/docker.sock
(9)检查网络配置
# 查看/etc/hosts/发现可访问地址
(host)$ docker run -it --rm alpine tail -n1 /etc/hosts
172.17.0.2 e0e6b96367db
# nmap查看容器网络
(host)$ docker run -it --rm ubuntu bash (cont)# apt update
(cont)# apt install nmap
(cont)# nmap -sn -PE 172.17.0.0/16 ...
Nmap scan report for 172.17.0.1
-
C.在运行有docker的主机上的渗透测试:
(1)Docker版本
# 检查docker版本,查找其对应版本是否有可用的cve
(host)$ docker -v
(2)拥有docker使用权限的用户
# 1 查找对/var/run/docker.sock有读写权限的用户
# 2 查看/etc/group查找在docker组中的用户
$ grep docker /etc/group
# 3 拥有sudo权限的用户
# 4 docker client上设置了setuid位
(host)$ ls -l $(which docker)
-rwxr-xr-x 1 root root 88965248 nov 13 08:28 /usr/bin/docker
(host)# chmod +s $(which docker)
(host)$ ls -l $(which docker)
-rwsr-sr-x 1 root root 88965248 nov 13 08:28 /usr/bin/docker
(3)配置:/etc/docker/daemon或/etc/default/docker
(4)可获得的镜像和容器
# 列出所有可用的镜像
docker images -a
# 列出所有容器
(host)$ docker ps -a --no-trunc --format="{{.Names}} {{.
Command}} {{.Image}}"
# 查看容器相关信息:传递给容器的环境变量
(host)$ docker run --rm -e MYSQL_ROOT_PASSWORD=supersecret -- name=database mariadb:latest
(host)$ docker inspect database | jq -r '.[0].Config.Env'
# 查看容器相关信息:volumes
(host)$ docker inspect database | jq -r '.[0].HostConfig.Binds'
(5)iptables规则:使用 iptables -vnL 和 iptables -t nat -vnL,可以看到默认表filter和nat的规则。所有关于docker容器的防火墙规则都在filter中的docker -user链中设置。