一、简介
在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配置进行权限提升的思路,以此说明权限配置不当对容器逃逸后的进一步影响,希望企业的集群管理员与云厂商在管理集群环境中的角色与权限时,能够合理分配,防范权限滥用攻击,共同建设安全的集群环境。
利用功能类型 | 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