一、简介













在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所示:


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所示:


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


利用功能类型