前沿研究 | 容器逃逸即集群管理员?你的集群真的安全吗?

2022-11-14 11:39:36 浏览数 (1)

一、简介

在2022年的KubeCon会议上,来自Palo Alto Networks的安全研究员Yuval Avrahami和Shaul Ben Hai分享了议题《Trampoline Pods:Node to Admin PrivEsc Built Into Popular K8s Platforms》[1] ,介绍了攻击者在容器逃逸之后如何利用节点上“TrampolinePods”的权限来接管集群,其中涉及的技术和思路十分值得学习与思考,本文主要介绍该技术的原理和步骤,扩展了同一类型的方法,希望云安全人员在深入了解攻击技术之后,能够发现并缓解生产环境中存在的类似风险,共同建设云环境安全。

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

二、事出有因

该技术的分析来源于针对恶意软件Siloscape的分析[2] 。2021年3月,研究员第一次发现针对Windows容器的恶意软件并将其命名为Siloscape,在还原其攻击链时(图1所示)发现Siloscape展示了一种未曾见过的在野攻击思路:在入侵Kubernetes集群的一个节点后,它会检查节点上是否有create Deployments的权限,如果有则在集群内创建一个Deployment后门,如果没有则停止继续攻击。

图1 Siloscape的攻击链(图片源自针对恶意软件Siloscape的分析[2] )

该思路引发人思考:如何配置Kubernetes集群节点的权限才能防止此类攻击?如果配置不当又会造成什么风险?带着这两个问题我们继续深入其中。

三、容器逃逸的真正影响

容器技术在被广泛应用的同时,也带来了对应的安全问题——容器逃逸。我们在《容器逃逸技术概览》中,系统地将容器逃逸的根源划分为4个类型:危险配置导致的容器逃逸、危险挂载导致的容器逃逸、相关程序漏洞导致的容器逃逸、内核漏洞导致的容器逃逸,不论通过何种方式从容器逃逸到宿主机,直接的影响似乎只是控制了容器所在的宿主机。若该宿主机是Kubernetes集群的一个普通节点,从渗透测试的角度来看,下一步需要进行的便是横向移动或权限提升。关于Kubernetes集群的权限提升,不论是CVE-2018-1002105还是CVE-2020-8559,漏洞的利用都依赖相关组件存在漏洞这个前提,倘若目标集群Kubernetes的相关组件都是安全的,如何在集群内进行权限提升呢?笔者通过整理现有的技术并类比针对容器逃逸的类型划分,将Kubernetes集群的权限提升手法划分为2个类型:相关程序漏洞导致的权限提升、危险的RBAC(基于角色的访问控制)配置导致的权限提升。本文主要讨论危险的RBAC配置导致的权限提升,为了更加容易理解后文涉及的技术手法,下面将介绍一些相关背景知识。

四、背景知识

4.1

DaemonSets

当希望Pod在集群中的每个节点上运行时,需要创建DaemonSet对象,如Kubernetes的kube-proxy进程,负责节点的网络代理,需要运行在每个节点上。当有节点加入集群时,DaemonSet会为它们新增一个Pod,当节点从集群中移除时,这些Pod也会被回收。删除DaemonSet将会删除它创建的所有Pod。

4.2

ServiceAccount

Pod自身在访问Kubernetes API Server时,需要使用内置的ServiceAccount(简称sa,下同)。sa在创建时,会在同一命名空间下生成一个与之关联的Secret资源,Secret存储认证所需的token、ca.crt等内容。默认情况下,Pod会自动挂载同一命名空间下的名为default的sa,相关文件挂载在Pod中容器/var/run/secrets/kubernetes.io/serviceaccount/路径下。

五、危险的RBAC配置

“人类才是系统中最大的漏洞”,不论是传统应用场景,还是如今的云原生场景,权限配置一直是困扰安全人员的最大挑战之一。权限配置不当导致的安全事件比比皆是,因此诞生了各种标准来规范应用程序的权限以防止发生风险。

一般情况下,Kubernetes集群中节点上主要运行的Pod类型有以下三种,如图2所示:

图2 节点上运行的Pod类型

- 业务Pod

- 附加组件(Prometheus,Istio此类)

- 系统组件(Kube-proxy,coredns)

业务Pod的权限一般较小,附件组件和系统组件提供集群管理的服务,它的权限也不应等于集群管理员的权限,但在实际场景中,集群管理员可能配置不当,赋予对应角色过高的权限。当攻击者从外部进入容器环境中并逃逸至宿主机时,往往会关注节点上Pod的权限,若发现高权限的sa,则可以凭借它完成集群内的权限提升。现根据利用功能将角色涉及的敏感权限和对应的风险进行整理,如图3和附录A所示:

图3 根据利用手段划分权限

注:

操控认证/授权:有权修改认证标识或角色权限,如escalate clusterrole

获取凭证:有权获取或下发凭证,如list secrets

命令执行:有权在Pod或Node上执行命令,如pods/exec

管理Pod:有权转移Pod或更新节点,如update nodes,delete pods

中间人:有权拦截通信流量,如create endpointslices

六、攻击案例

下面将以CNI插件Cilium为例,介绍攻击者在容器逃逸之后,如何利用高权限的Pod从工作节点获取集群管理员权限。

Cilium的架构主要包含Cilium Agent(简称Agent)和Cilium Operator(简称Operator)组件,如图4所示。

图4 Kubernetes集群中的Cilium

其中Agent的功能是接收来自上层的配置,包括通过Kubernetes或API来定义网络、服务负载均衡、网络策略、可见性和监控需求,它以DaemonSet形式部署,在每个节点上运行。在较早版本(v1.12.0-rc2版本之前)中的Agent内置的sa拥有集群内的delete pods权限和update nodes/status权限。

Operator的功能是管理集群,主要是节点之间资源信息的同步、确保 Pod DNS 更新管理、集群 NetworkPolicy 的管理和更新等,它以Deployment形式部署,随机分配在集群中的某个节点上。同样地,Operator在较早版本中内置的sa拥有集群内的list secrets权限。需要知道的是,在Kubernetes集群中,list secrets权限可以直接获得secret的内容,官网文档已经说明[3] ,具体效果如图5、图6、图7所示:

图5 list secrets权限示例

图6 get secret name效果

metarget命名空间下名为default的sa拥有list secrets的权限,直接get secrets secretname读取会提示forbidden,但是可以改用get secret来获取其内容,如图7所示:

图7 list权限获取secret值

当获取到Operator的sa时,可以利用它来读取一些更高权限账户的secret。但当攻击者通过一系列手段从容器逃逸至宿主机时,该宿主机可能只是集群中的一个普通节点,而Operator是以Deployment的形式随机部署在某个节点上,并不一定会在当前节点,如图8所示:

图8 攻击者入侵至普通节点

因此需要通过一定手段将Operator从其他节点转移至当前节点,由此可将攻击大概分为以下三个步骤:

第一步:转移Operator

如何转移Operator?需要利用Agent的sa。在节点上可以通过文件系统或进入容器内部获取Agent的sa,先利用update nodes/status权限将其他所有节点的PodCapacity置为0,然后利用delete pods权限将Operator删除。Capacity代表节点的资源容量,包括cpu、memory、将PodCapacity置为0,代表节点上Pod的容量为0。当Operator被删除时,因为Deployments的特性,Kubernetes API Server会重新创建一个副本,在资源调度时会检查节点上的Capacity值,当发现其他所有节点的PodCapacity值为0时,便会将Operator部署在攻击者控制的节点上,完成转移。

第二步:窃取凭证

当Operator可控时,同样可以通过文件系统或进入容器的方式获取Operator的sa。那么问题来了,当拥有读取secret的权限时,需要读取谁的secret才能进一步扩大权限,甚至一步到位?经过调查,发现Kubernetes集群内有这样一个角色:clusterrole-aggregation-controller(简称CRAC,下同),它的权限如图9所示:

图9 CRAC角色权限

值得注意的是其中的“escalate”权限。一般来说,role或者clusterrole角色只能拥有在它们被创建时所赋予的权限。但escalate是个例外,它可以升级角色或集群角色的权限。为此我们需要利用Operator的sa获取CRAC的ca.crt和secret值,通过以下命令可以获取,如图10所示:

代码语言:javascript复制
curl https://server-ip:port/api/v1/namespaces/kube-system/secrets/clusterrole-aggregation-controller-token-name --header "Authorization: Bearer $token" --insecure

图10 获取CRAC凭证

注:其中token为Operator的secret值。

第三步:权限提升

在经过上述步骤获取到相关凭证之后便可在从节点上进行权限提升。

使用以下命令给system:controller:clusterrole-aggregation-controller角色添加权限,效果如图11、图12、图13所示:

代码语言:javascript复制
kubectl --server=https://server-ip:port --certificate-authority=./ca.crt --token=$token edit clusterrole system:controller:clusterrole-aggregation-controller

图11 修改权限前

图12 修改权限

图13 修改权限后

将权限修改为cluster-admin,即可提权至集群管理员权限。

七、方法扩展

回顾上文提到的利用Cilium在集群中的提升权限的思路,大致路线如图14所示:

图14 权限提升路线

观察发现,一旦攻击者获取到kube-system命名空下的list secrets权限,就可以利用CRAC角色提权至集群管理员,该角色的权限是集群默认赋予的,因此在生产环境中需要控制的是list secrets权限的赋予。那么除了上述思路外,是否还有其他权限能完成攻击链的构造进行权限提升?经过调研[4] [5] ,还发现下面两种权限提升思路:

利用Node/Proxy提权

在Kubernetes的机制中,Kubelet工作在集群中的每个节点上,它负责执行来自API Server的请求并返回结果。正常情况下,访问Kubelet API是需要凭证,但当攻击者拥有get、create node/proxy权限时,便可以与Kubelet API直接通信,绕过API Server的访问控制,同时因为Kubelet API不会被日志审计,也增大了检测的难度。

攻击者在获取到拥有get、create node/proxy权限的secret值后,若能访问到master节点上的Kubelet API,便可以直接与其通信,获取到API Server的凭证,从而控制整个集群,如图15所示:

图15 和Kubelet API通信

利用CSR API提权

CSR即证书签名请求,Kubernetes在多处使用客户端证书进行认证,包括用于Kubelet和API Server之间的通信。当攻击者拥有签名者为kubernetes.io/kube-apiserver-client的create CSRs权限和update CSRs/approval权限时,可以为高权限的系统账户创建一个新的客户端证书,用于和Kubernetes进行认证,能够获取到系统账户的权限。

其他更多思路和权限风险可以参考附录A中提及的风险项深入挖掘。

八、思考与总结

通过对比分析不同的权限提升思路,总结了以下两条集群内的提权路线图:

若拥有的权限可以绕过认证,直接和API Server或Kubelet通信,便可以读取到kube-apiserver的凭证,获得集群管理员权限;若拥有的权限可以读取到CRAC角色的token值,便可以通过修改角色来获得集群管理员权限。

站在防御者的角度,高效的修复方案便是直接针对此类攻击路线进行阻断。在对角色的权限分配时,可以参考图3中涉及的权限和文中提及的攻击案例,仔细考虑每项权限的作用范围与危害,在生产环境中遵循权限最小化原则,进行合理分配。节点之间的隔离防护,如给Kubelet服务设置防火墙,尽可能控制攻击者的影响面。同时加强API Server的日志审计和异常检测,对于异常的API请求应及时记录、阻断和警报。

本文介绍了在集群内利用危险的RBAC配置进行权限提升的思路,以此说明权限配置不当对容器逃逸后的进一步影响,希望企业的集群管理员与云厂商在管理集群环境中的角色与权限时,能够合理分配,防范权限滥用攻击,共同建设安全的集群环境。

附录A

利用功能类型

RBAC权限

可利用点

操控认证/授权

impersonate users/groups/serviceaccounts

模拟身份,如用户、组和sa

escalate roles/clusterroles

向现有角色或集群角色添加权限

bind rolebindings/cluster role bindings

将现有角色或集群角色绑定至任意身份

approve signers & update certificatesigningrequests/approval

让现有的签名者批准证书签名请求

control mutating webhooks

修改已承认的角色或集群角色

获取凭证

list secrets

获取secret的列表和内容

create secrets

为现有sa下发新的secret

create serviceaccounts/token

通过TokenRequests为现有sa下发临时secret

create pods

将指定sa挂载至新建的Pod中或以环境变量或卷的方式附加至新建的Pod中

control pod controllers

将指定sa挂载至新建或现存的Pod中或以环境变量或卷的方式附加至新建或现存的Pod中.

control validating webhooks

在创建sa时获取其secret

control mutating webhooks

在创建sa时获取其secret或将sa附加至新的Pod中

命令执行

create pods/exec

通过API Server在Pod中执行命令

update pods/ephemeralcontainers

容器注入至现有Pod中以执行命令

create nodes/proxy

通过Kubelet在Pod中执行命令

control pods

修改Pod为特权模式

control pod controllers

通过pod controllers创建或修改Pod,如设置为特权模式以执行命令

control mutating webhooks

修改容器的镜像、执行命令、执行参数、环境变量或卷等来执行命令

管理Pod

modify nodes

通过NoExecute驱逐节点上的Pod,使其转移至在指定节点上

modify nodes/status

修改节点状态,如将其pod capacity设置为0

create pods/eviction

驱逐Pod,迫使其重新生成

delete pods

删除Pod,迫使其重新生成

delete nodes

通过删除节点来删除Pod,迫使其重新生成

modify pods/status

设置Pod标签以匹配标签选择器,同时设置Pod的生成时间以欺骗控制器删除现有副本,完成替代

modify pods

设置Pod标签以匹配标签选择器,同时设置Pod的生成时间以欺骗控制器删除现有副本,完成替代

中间人

control endpointslices

修改现有的endpointslices以拦截流量或为现有服务新建endpointslices以拦截流量

modify endpoints

修改现存服务的endpoints以重定向流量,对endpointslices无效

modify services/status

附加一个负载均衡IP来利用CVE-2022-8554,进行流量劫持

modify pods/status

修改Pod的标签以匹配服务的选择器进行流量劫持

modify pods

修改Pod的标签以匹配服务的选择器进行流量劫持

create services

创建一个ExternalIP服务来利用CVE-2022-8554,进行流量劫持

control mutating webhooks

修改新生成的服务、endpoints和endpointslices来进行流量拦截

参考自Palo Alto Networks报告[6]

参考文献

[1] https://kccnceu2022.sched.com/event/ytlb/

[2] https://unit42.paloaltonetworks.com/siloscape/

[3] https://Kubernetes.io/docs/concepts/security/rbac-good-practices/#listing-secrets

[4] https://blog.aquasec.com/privilege-escalation-kubernetes-rbac

[5] https://blog.aquasec.com/kubernetes-rbac-privilige-escalation

[6] https://www.paloaltonetworks.com/apps/pan/public/downloadResource?pagePath=/content/pan/en_US/resources/whitepapers/kubernetes-privilege-escalation-excessive-permissions-in-popular-platforms

0 人点赞