开源Chart包是对容器编排、配置和管理文件进行封装的一种工具,以实现在容器规模化上下线过程中快速复制。
开源Chart包的使用越来越广泛,平均每天基于微服务架构的软件更新高达上万个,在容器平台上运行的应用可以在几秒时间内成百上千的迅速扩展。
开源Chart包的安全性较低,如果容器存在配置错误,风险会随着应用的快速上线而被不断复制和放大。而为便于开发者快速使用,开源Chart包对其功能易用性做了大量优化,而安全性则放在了较低优先级。
风险隐藏于资源背后
阿里云安全团队归类了 ArtifactHub 中收录的共计 10237 个 Chart 包,其中8708个为有效Chart包,对其以默认值生成的 91791 个 k8s 对象进行了分析,以了解开源 Chart 仓库的安全水位,并公开分享报告分析结果。
1. 报告概览
01.未通过全部检查项的包共 8150 个,占比94%,仅有 6%的 Chart 包通过全部检查项。在未全部通过检查项的 Chart 包中,有 1076 个 Chart 包未通过项种类 >= 10,绝大部分开源 Chart 包中都存在安全配置问题。
Chart 未通过检查项的数量占比情况
02.在未通过的检查项中,集群提权与容器逃逸风险是两大主要类型,也是安全人员最关注的两种安全风险。
各种检查项类型的数量占比
03.未设置seccomp配置文件、以root身份运营容器、可读取任意secret的角色是TOP3风险。
未通过的检查项Top10 情况
2. 主要风险分析
2.1 大量容器工作负载具有危险特权
-75.8% 的 Chart 包都以 root 身份运行容器。
安全风险:以root 用户身份只在一小部分的应用中是必要的,其他应用以 root 用户身份运行,会导致 Linux 的部分文件访问限制失效(如仅 root 可读写的系统文件等),增加不必要的安全风险。
-549 个 Chart 包中的工作负载容器使用了宿主机命名空间、配置了允许特权提升选项。
安全风险:使用了宿主机命名空间的容器,会获得对应宿主机上下文信息,比如共享了宿主机进程命名空间的容器时,容器中可以读取到宿主机进程列表,容器中的应用程序可以获取或修改宿主机进程的敏感信息;共享了宿主机网络命名空间的容器,可以获得宿主机套接字监听情况,可能导致一些本地服务进程被未授权访问,比如 containerd 的 CVE-2020-15257 会导致共享网络命名空间的容器访问宿主机网络命名空间中的 containerd-shim 套接字,从而实现提权。
配置了允许特权提升的容器,可以通过 SUID/SGID 权限位或其他方式来获得比父进程更高的权限,导致容器逃逸风险。
-438 个 Chart 包中直接声明容器以特权模式运行。
安全风险:配置了特权模式的容器,会直接获得与宿主机相当的权限,可以通过挂载宿主机上的设备文件来获得宿主机文件系统,或轻松利用 core_pattern 等传统逃逸方法逃出容器。在 Linux capability 中,SYS_ADMIN 特权涵盖了大部分特权操作,包括挂载用于逃逸的文件系统,可以被利用实现容器逃逸;NET_ADMIN 特权包含了与网络相关的特权操作,如修改路由表等,可以用来发起在集群中的网络攻击,如 CVE-2020-8558 等;DAC_READ_SEARCH 特权则会导致经典的 Shocker 攻击,造成容器逃逸。
-196 个 Chart 包配置了危险 root capabilities(SYS_ADMIN,NET_ADMIN,DAC_READ_SEARCH)。
上述配置项会导致原本的容器隔离机制防护水平降低,甚至失效,从而导致容器逃逸风险增加。
2.2 大量 RBAC 配置存在提权风险
-24% 的 Chart 包配置了读取任意 Secret 对象的权限。
安全风险:会导致其他工作负载的 Secret 可能被读取,如其他 serviceaccount 的 token-secret,导致提权行为发生;
-17.5% 的 Chart 包在配置 RBAC 规则时使用了 * 通配符。
安全风险:将会增加赋予非预期权限的可能性。
-11% 的 Chart包配置了impersonate(可以在 k8s 集群中假冒其他用户主体) 权限,且没有限制可伪装的用户主体范围;10.9%的 Chart 包配置了 bind 权限(可以为自身绑定更高的集群角色),且没有限制可绑定的角色范围;10.8% 的 Chart 包配置了escalate 权限(可以为自身集群角色增加更多权限) ,且没有限制可增加的权限范围;
安全风险:impersonate、bind、escalate 权限是 k8s 设计用于提权的权限,在赋予这三种权限时需要格外谨慎,没有限制可提升的目标,会导致在集群中的任意权限提升,导致控制整个集群;
-1.6% 的 Chart 包直接将集群管理员权限赋予给了应用的工作负载。
安全风险:对于直接使用了集群管理员角色的应用,一旦被攻击者获得 pod 中的服务账号令牌,将导致 k8s 集群直接沦陷。
由此可见,在开源的 Chart 包中,为应用工作负载设置 RBAC 配置时,很多开发者没有对最小权限原则给予足够的关注,这将导致潜在的权限提升等非预期行为的出现。
2.3 容器挂载宿主机敏感目录依然可见
在本次分析结果中,有 322 个 Chart 中的 410 个 object 挂载了宿主机敏感目录,具体分布情况如下:
- 挂载 k8s 配置目录,会导致容器中读取到集群中的凭证信息,导致提权发生;也可能将集群中的插件程序挂载进来,当容器内攻击者将恶意代码植入该程序后,其恶意代码将随着集群每次调用该插件程序而被执行,带来集群范围的风险。
- 挂载 docker.sock 文件在容器中,会导致攻击者可以在容器中发起创建任意容器的请求,比如创建一个特权容器来获得宿主机控制权限。
- 挂载节点的定时任务目录等,容器内的攻击者可以修改对应的参数内容或二进制程序内容,导致恶意代码被系统执行。
- 对于 /proc、/sys 两大伪文件系统,通过调用系统内核函数实现读写。在将其挂载进容器后,攻击者可以利用其文件系统特有的几种用于内核线程执行命令的机制来达到容器逃逸的目的,例如 /proc 目录下的 core_pattern 文件,/sys 下的 uevent_helper 文件等。
- static Pod 目录是 k8s 中的一种特殊目录,在其目录下声明的 Pod,将由节点上的 kubelet 自动创建并维护运行状态,且无法通过集群 apiserver 删除,在攻防场景中常被用来作为持久化手段之一。当容器挂载该目录后,容器内的攻击者可以使用此目录创建一个持久化的特权容器,以获得对集群节点的控制。
- 用户凭证类目录中保存了宿主机上的 ssh 登录信息等内容,在云应用中,还可能保存了其他云平台用户凭证等信息。当挂载进容器中,可能导致被容器攻击者读取,从而获得受害者云平台权限,导致用户更多数据被泄露。
- 重要根目录主要包含宿主机根目录,以及一些系统链接库根目录。当被挂载进容器中,攻击者可以通过修改系统链接库来注入恶意代码,从而获得集群节点的控制权。当直接挂载根目录时,上述所有风险都会一并引入。
2.4 Pod 风险配置会被 DaemonSet 放大
本次分析共包含了 794 个 DaemonSet 配置文件,其中 756 个没有通过所有检查,占到了 93%。其中敏感挂载、特权容器、拥有提权风险服务账号令牌的情况如下:
对于 k8s 中部署 Pod 负载类的对象来说,DaemonSet 是用来实现在集群中的每一个节点上部署 Pod 的方式。因此如果 DaemonSet 的配置中存在风险配置的话,其被利用的概率会更大。在配置 DaemonSet 时,开发者应当重点关注其是否会引入一些安全风险,导致该风险被传播到集群中的每一个节点上。
- 从单个容器的角度来看,如果将 DaemonSet 中的容器配置为一个特权容器并且以节点端口对外暴露服务,k8s 会将该特权容器部署在集群中的每一个节点上,使得暴露的脆弱容器资产随着节点的增加而增加,同时增加攻击者到达特权容器的几率。
- 从集群权限的角度来看,如果将 DaemonSet 中的服务账号绑定一个高权限集群角色,那么可能导致攻击者通过一步容器逃逸而获得整个集群的权限。
具体来说,攻击者通过容器逃逸控制了集群中某一个工作节点。此时,攻击者可以获得同驻在一个节点上的其他 Pod 挂载的服务账号令牌,从而得到其所拥有的权限。在一般情况下,我们期望集群中拥有高权限服务账号的 Pod 只部署在 Master 节点,或少数的 Worker 节点上。这样攻击者必须继续找到能够横向移动的方式,来继续在集群中提权。此时,单个 Pod 逃逸并不意味整个集群的失陷。
但当 DaemonSet 的服务账户被配置为一个高权限服务账户,那么集群中的情况就会变得很糟糕:
这种情况下,攻击者在集群中的任意一个 Pod 中逃逸后,都可以获得一个高权限服务账号的令牌,从而完成在集群中的提权,甚至直接控制整个集群。此时单个 Pod 逃逸则意味着整个集群的失陷。
可以看到,在出现工作负载 Pod 风险配置时,DaemonSet 的形式会将其被利用的几率大大增加。
2.5 敏感信息的使用方式不够规范
在本次分析结果中,在517个Chart 包的环境变量以及118个 Chart包的configmap 中出现了以明文形式声明的敏感信息。
如果敏感信息(如密码)以明文形式保存在环境变量中,那么任何可以访问 Pod 的用户都可以轻松地获取这些信息,从而导致安全问题。并且敏感信息与工作负载声明耦合的情况,会导致敏感信息管理更复杂,其内容会随着 Pod 配置文件的复用而传播。
建议:
在k8s中使用Secrets对象,或者更专业的第三方Secrets管理工具来保存密码密钥等敏感信息。
2.6 集群管理员角色存在滥用情况
在本次分析结果中,出现 141 个 Chart 包在配置 RBAC 角色绑定时,直接为服务账号绑定了集群管理员角色,导致提权风险大大增加。
下图是一个 artifact 里一个 Chart 包中的配置文件,在其 ClusterRole 配置中,直接把集群管理员角色绑定给了工作负载使用的服务账号,这导致其工作负载获得了与集群管理员相同的权限。一旦 Pod 被攻击者控制,导致其上挂载的服务账号令牌被读取,就会直接导致集群被攻击者控制。
2.7 容器加固机制使用不够充分
在 k8s 最新版本 1.27 之后,可以通过使用 SeccompDefault 这一特性为 Pod 默认启用运行时 seccomp 配置,但需要集群创建者手动增加配置项,因此主动在 Pod 的配置文件中声明 seccomp 配置是一个更可靠的选择。
在本次分析结果中,87% 的 Chart 包都没有设置 seccomp 配置文件。在未使用 seccomp 的情况下,容器使用系统调用的能力会更加宽松。
攻击者可以在容器中发起一些敏感系统调用来实现容器逃逸等攻击。例如,利用 cgroup 逃逸的 CVE-2022-0492,攻击者通过 unshare 系统调用可以创建一个拥有所有特权的用户命名空间,从而通过 cgroup 挂载的检查条件,最终导致发生容器逃逸。而 unshare 系统调用在 seccomp 默认配置中是不被允许的。
3. 安全建议
通过以上分析我们发现,开源Chart包中存在的大部分错误配置是可以被轻松修复的。这强调了安全基线检查的重要性,它可以帮助我们维护容器应用的安全性,确保其在运行过程中始终保持最佳安全状态。
因此,我们建议在 helm Chart 编写过程中,应该充分考虑配置文件的安全性问题。
- 从容器角度来说,需要仔细检查容器的 Linux 权限配置以及文件挂载,大部分的容器不需要额外的 Linux 特权,甚至不需要以 root 身份运行,可以通过启用运行时加固机制,减少容器拥有的特权,创建单独的用户身份来提高容器防逃逸能力;在向容器中挂载宿主机文件时,需要考虑是否使用 volume 来实现持久化存储,或限制挂载的读写模式,目录范围来降低危险挂载导致的安全问题。
- 从 Kubernetes 集群的角度来说,k8s 中的 RBAC 机制引入了一种集群范围内的危险权限配置,在为工作负载配置 RBAC 权限时,需要分析负载所需要的最小权限范围,以及引入的权限可能会带来的风险情况,通过更细致的权限规则来消除潜在的安全风险;在k8s中使用Secrets对象,或者更专业的第三方Secrets管理工具来保存密码密钥等敏感信息。在配置 DaemonSet 时要考虑其分布在所有节点的特性造成的风险扩散问题,对它的配置需要更加谨慎。
以上这些检查,对于没有安全背景的开发者和使用者来说是一个比较繁琐的过程。为了解决这一问题,可以在两个阶段引入自动化的工具来应对:
在开发的 CI/CD 流程中引入配置文件安全检查环节。它可以在代码发布或应用部署的过程中分析 helm Chart 包中的 Kubernetes 配置文件,指出其中存在的安全风险配置项,让用户意识到配置文件所引入的安全问题,提高了错误配置的可见性,使得这些问题可以在应用上线前被及时修复。
如果容器应用已经上线,可以借助CSPM(云安全配置管理)/KSPM(Kubernetes安全态势管理),对 Kubernetes 中的用户配置进行分析,指出其中存在的安全风险配置项,并进行修复。对于阿里云上用户来说,可以使用云安全中心,对已经上线的容器进行基线检查。目前阿里云云安全中心具备200+条安全基线,1300+个检查项,可以覆盖上述提到的主要安全风险。
*致谢:特此感谢司序同学对本文的贡献!