导语:Azure 容器实例(ACI) 是 Azure 的容器即服务 (CaaS) 产品,ACI)使客户能够在 Azure 中运行容器而无需管理底层服务器。最近有研究人员发现并向 Microsoft 披露了 ACI 中的安全漏洞。
0x01 漏洞描述
Azure 容器实例(ACI) 是 Azure 的容器即服务 (CaaS) 产品,ACI)使客户能够在 Azure 中运行容器而无需管理底层服务器。最近有研究人员发现并向 Microsoft 披露了 ACI 中的安全漏洞。恶意的 Azure 用户可能会利用这些问题逃逸到其他用户的容器上执行代码,窃取部署到平台的客户敏感文件,并可能滥用 ACI 的基础设施进行挖矿。研究人员将该漏洞命名为Azurescape——公有云中的跨账户容器逃逸接管漏洞。
https://azure.microsoft.com/en-us/services/container-instances/
Azurescape 允许恶意用户破坏托管 ACI的多租户Kubernetes集群,建立对其他用户容器的完全控制。这篇文章描述了研究过程、漏洞分析,并提出了保护 Kubernetes 的措施,重点是多租户防止类似攻击的缓解措施。
微软在我们披露后不久修补了 ACI,现在还不知道 Azurescape 是否被在野利用。
0x02 Azure容器实例
Azure 容器实例 (ACI) 于2017 年 7 月发布,是大型云提供商提供的第一个容器即服务 (CaaS) 产品。借助 ACI,客户无需管理底层基础结构即可将容器部署到 Azure。ACI 负责扩展、请求路由和调度,为容器提供无服务器体验。
https://azure.microsoft.com/en-us/blog/announcing-azure-container-instances/
Azure 官网对 ACI 的描述如下:“无需管理虚拟机或学习新工具即可快速开发应用程序,它只是在容器中、在云中运行的程序。”
在集群内部,ACI托管在客户容器的多租户集群上。刚开始是使用 Kubernetes 集群,在过去的一年里,微软也开始在 Service Fabric 集群上托管 ACI。此处的漏洞会影响 Kubernetes 上的 ACI,本文的其余部分将仅参考该架构。根据我们的测试,我们在平台上部署了数千个容器,在披露时 Kubernetes 托管了大约 37% 的 ACI 新创建的容器。
该图说明了托管在多租户 Kubernetes 群集上的 Azure 容器实例,显示了主节点上的 api 服务器如何与为三个不同客户运行的三个工作节点相关联。 租户边界用红色虚线表示。
图 1. 托管在多租户 Kubernetes 集群上的 ACI。
在 ACI 等多租户环境中,你需要在租户之间实施强边界。在 ACI 中,该边界是节点虚拟机。每个客户容器都在专用的单租户节点上的 Kubernetes pod 中运行。这种 Kubernetes 多租户方法通常称为node-per-tenant。
0x03 AzureScape 攻击场景
ACI 会防止相邻容器的恶意攻击。由于几乎任何人都可以将容器部署到平台上,因此 ACI 必须确保恶意容器不会进行破坏、泄漏信息、执行代码或以其他方式影响其他客户的容器,这种攻击影响就是跨账户或跨租户攻击。
Azure 容器实例中的跨账户攻击场景图解,展示了恶意客户如何占用工作节点。
图 2. 跨账户攻击场景。
以下部分涵盖了我们对 ACI 中跨账户攻击的研究。我们发现了一种跨租户攻击,恶意的 Azure 用户可以通过这种攻击进行容器逃逸,获取特权 Kubernetes 服务帐户令牌并接管 Kubernetes api-server,从而建立对多租户集群和在其中运行的所有客户容器的完全控制。
0x04 ACI 容器逃逸
CaaS 产品很难研究。用户只暴露了他们的容器环境,并且通过防火墙禁止访问本地网络。为了更好地了解 CaaS 平台如何运行我们的容器,我们创建了 WhoC。WhoC是一个容器镜像,它可以读取执行它的容器runtime,基于 Linux 容器中一个很少讨论的设计缺陷,允许它们读取底层主机的容器runtime。这个想法与 CVE-2019-5736非常相似,不同之处在于CVE-2019-5736是读取runtime,它WhoC是覆盖主机的runtime。
https://github.com/twistlock/whoc https://nvd.nist.gov/vuln/detail/CVE-2019-5736 https://unit42.paloaltonetworks.com/breaking-docker-via-runc-explaining-cve-2019-5736/
通过将 WhoC 部署到 ACI,我们能够检索平台中使用的容器runtime。不出所料,我们发现了行业标准容器runtime runC。
https://github.com/opencontainers/runc
让我们措手不及的是runC的版本,如图 3 所示。
屏幕截图显示了在 2016 年 10 月 1 日发布的 runC v1.0.0-rc2 上运行的 Azure 容器实例。
图 3. ACI 中使用的容器runtime。
RunC v1.0.0-rc2 于 2016 年 10 月 1 日发布,并且容易受到至少两个容器逃逸漏洞的攻击。
https://unit42.paloaltonetworks.com/breaking-docker-via-runc-explaining-cve-2019-5736/
ACI 中存在这个旧版本的 runC,使用这个容器映像对其进行优化并将其部署到 ACI。通过cve-2019-5736成功突破容器,并获得了一个在底层主机上以 root 身份运行的反向 shell,主机是一个 Kubernetes 节点(Node)。
https://docs.paloaltonetworks.com/prisma/prisma-cloud/20-12/prisma-cloud-compute-edition-admin/runtime_defense/incident_types/reverse_shell.html
一旦我们发现 ACI 中存在这个旧版本的 runC,我们就采用了当时开发的 PoC 容器映像,对其进行了优化并将其部署到 ACI。 我们成功地突破了容器,并获得了一个在底层主机上以 root 身份运行的反向 shell,结果证明它是一个 Kubernetes 节点。 在这里,我们展示了利用 CVE-2019-5736 来逃避我们的 ACI 容器的过程的屏幕截图。
图 4. 利用 CVE-2019-5736 来逃逸 ACI 容器。
虽然实现了容器逃逸,但仍然在租户边界内——VM节点 。CaaS 平台可以抵御复杂的攻击者,这些攻击者可以通过内核漏洞实现权限提升和容器逃逸。
该图显示了租户边界如何防范恶意客户。 虽然我们逃离了我们的容器,但我们仍然在租户边界内——节点 VM。 CaaS 平台旨在抵御复杂的攻击者,这些攻击者拥有内核漏洞,可实现权限提升和容器突破。 恶意容器爆发是一种预期的威胁,通过节点级隔离可以容忍。
图 5. 获取节点权限后任然在node中。
0x05 探测K8s节点环境
探测扫描节点可以验证当前容器是唯一的客户容器。使用Kubelet凭据,我们列出了集群中的 pod 和节点。该集群托管了大约 100 个客户 pod,拥有大约 120 个节点。每个客户都被分配了一个 Kubernetes 命名空间,他们的 pod 在其中运行;我们的容器ID是caas-d98056cf86924d0fad1159XXXXXXXXXX。
https://kubernetes.io/docs/concepts/overview/components/#kubelet
可以看到节点的 Kubelet 允许匿名访问,尝试访问相邻节点上的 Kubelet。所有尝试访问相邻节点的请求都超时了,可能是由于防火墙配置阻止了工作节点之间的通信。
节点在kubernetes.azure.com/cluster标签中引用了集群名称,格式如下:CAAS-PROD-< LOCATION >-LINUX-< ID >。
图 6. 集群名称。
我们部署了几个分支容器,它们在不同的 Kubernetes 集群节点上。发现每个集群节点都有唯一的集群 ID,范围在 1-125 之间。这些集群 ID 表明托管了几十个集群节点。
1.Kubernetes 1 day
接下来,我们检查了集群的 Kubernetes 版本。
Azure 容器实例托管在运行 Kubernetes v1.8.4、v1.9.10 或 v1.10.9 的群集上,如下所示。 这些版本于 2017 年 11 月至 2018 年 10 月期间发布,容易受到多个已知漏洞的攻击。
图 7. ACI Kubernetes 版本。
ACI 托管在运行 Kubernetes v1.8.4、v1.9.10 或 v1.10.9 的集群上。这些版本于 2017 年 11 月至 2018 年 10 月期间发布,容易受到多个已知漏洞的攻击。运行较旧的 Kubernetes 版本有较大的风险,但它不一定会导致 ACI 中的安全问题。
回顾过去的 Kubernetes 漏洞,寻找可以通过被控节点提升权限或访问其他节点的漏洞,目标锁定: CVE-2018-1002102。
https://github.com/kubernetes/kubernetes/issues/85867
2.Kubernetes CVE-2018-1002102
当为kubectl exec < pod > < cmd >命令提供服务时,api-server 会将请求推迟到适当的 Kubelet 的 / exec端点。
CVE-2018-1002102 是 api-server 与 Kubelets 通信中存在的漏洞,可以实现重定向。通过将 api-server 的请求重定向到另一个节点的 Kubelet,恶意 Kubelet 命令可以在集群中传播。图8展示了漏洞的基本流程:
CVE-2018-1002102 的基本流程:1) 由 api-server 服务的命令,2) api-server 将请求延迟到适当的端点,3) 302 重定向,4) 通过集群传播。
图 8. CVE-2018-1002102 利用流程。
漏洞利用前提条件:
存在漏洞的 api-server 版本:✓
获取一个节点权限:✓
通过 api-server 可以使被控节点与其他节点通信。例如,可以通过向被控节点上的 pod 发出 kubectl exec 来完成。
事实证明,ACI 满足第三个前提条件。ACI 支持通过镜像kubectl exec的az container exec 命令在上传的容器上执行命令。
az container exec --name --exec-command
创建一个利用 CVE-2018-1002102 的自定义 Kubelet 映像,将传入的 exec 请求重定向到其他节点上的 pod。为了最大限度地发挥作用,将其配置为以 api-server pod 为目标,最后,运行az container exec my-ctr --exec-command /bin/bash,希望能在 api-server 容器上建立一个 shell。
执行命令失败了,经过一些调试,我们注意到重定向操作仅在目标容器托管在同一节点上时才有效。这有效地消除了攻击,因为我们无法传播到其他节点。分析CVE-2018-1002102的补丁,确实对该漏洞的修复。
https://github.com/kubernetes/kubernetes/pull/66516
重新检查到达节点的exec请求,请求可能会从 api-server IP 到达,如图 8 所示。但是,请求来自在默认命名空间中运行的“bridge” pod。
重新检查到达节点的 exec 请求后发现,这些请求源自在默认命名空间中运行的被称为“网桥”的 pod,如图所示。
图 9.az container exec会话期间的 Kubelet 连接。
我们发现 ACI 将exec请求的处理从 api-server 转移到自定义服务。这可能是通过将az 容器 exec命令路由到桥接容器而不是 api-server 来实现的。
我们发现 ACI 将 exec 请求的处理从 api-server 转移到自定义服务。 这可能是通过将 az 容器 exec 命令路由到桥接容器而不是 api-server 来实现的。
图 10. Bridge Pod 处理 ACI 中的执行程序。
网桥镜像标签是master_20201125.1,表明它是在 CVE-2018-1002102 之后更新的。从其最近的构建时间和拒绝重定向exec请求来看,似乎 CVE-2018-1002102 的补丁已经patch到了bridge上。微软应该是注意到了此漏洞影响了他们的自定义网桥 Pod 并进行了修补。
值得一提的是,CVE-2018-1002102 也可以在其他情况下被利用,例如,当客户端要求恶意 Kubelet 检索容器日志(例如kubectl 日志)时。这实际上与 ACI 相关,其中此功能是通过az container logs 命令实现的。但与exec请求一样,ACI 将日志检索的处理推迟到适当的log-fetch专用 pod上 。与 Bridge Pod 一样,CVE-2018-1002102 的修复程序也被移植到log-fetch Pod,以防止被利用。
0x06 获取集群管理员权限
CVE-2018-1002102 不在讨论范围内,但我们在调试漏洞利用时确实注意到了一些奇怪的事情。到达节点的exec请求包含一个Authorization Header头,其中包含一个 Kubernetes 服务帐户令牌,如图 11 所示。
到达节点的 exec 请求包含一个 Authorization 标头,其中包含一个 Kubernetes 服务帐户令牌,如图所示。
图 11. Bridge 发送带有服务帐户令牌的“exec”请求。
在这里找到令牌令人惊讶。如前所述,集群中的 Kubelet 被配置为允许匿名访问,因此请求不需要通过令牌进行身份验证,也许是旧版本中遗留的令牌。
Kubernetes 服务帐户令牌是未加密的 JSON Web 令牌 (JWT),因此它们是可解码的。如下所示,接收到的令牌是“bridge”服务帐户的服务帐户令牌。考虑到来自桥接 Pod 的请求,这是有道理的。
https://jwt.io/
收到的令牌是“网桥”服务帐户的服务帐户令牌。 考虑到来自桥接 Pod 的请求,这是有道理的。
图 12. 已解码的桥接服务帐户令牌。
如果你要运行 Kubernetes,一定要注意要将服务帐户令牌发送给谁:任何收到令牌的人都可以自由使用它并伪装成其所有者。令牌窃取者很可能对他们被盗令牌的权限非常感兴趣。api-server 公开了两个允许客户端查询其权限的 API,SelfSubjectAccessReview和SelfSubjectRulesReview。kubectl 提供了kubectl auth can-i作为访问这些 API 的便捷方式。
以下是默认命名空间中“Bridge”令牌的权限:
这显示了默认命名空间中“桥”令牌的权限。
图 13. 桥接令牌权限——默认命名空间。
查看其他命名空间,权限是一致的,表明它们是集群范围的(而不是命名空间范围的)。以下是 kube-system 命名空间中令牌的权限。尝试确定允许我们在多租户集群中传播的权限:
这显示了 kube-system 命名空间中令牌的权限,包括 pods/exec 权限,表明令牌可用于在集群中的任何 Pod 上执行命令。
图 14. 桥接令牌权限 - kube-system 命名空间。
经验丰富的 Kubernetes 安全人员可能已经确定了pods/exec权限,这表明该令牌可用于在集群中的任何 pod 上执行命令——包括 api-server pod!图 15 显示了在 api-server 容器上打开 shell 的令牌:
这显示了在 api-server 容器上打开 shell 的令牌。
图 15. 使用网桥的令牌在 api-server 上弹出一个 shell。
我们刚刚完成了一次跨账户攻击。通过在 api 服务器上执行代码,我们现在是集群管理员了,可以完全控制多租户集群和其中的所有客户容器:)
0x07 Azurescape 攻击总结
让我们总结一下恶意 Azure 客户可能通过托管 ACI 的多租户 Kubernetes 集群获得管理权限的步骤:
将利用 CVE-2019-5736 的镜像部署到 ACI。恶意镜像在执行时在底层节点上实现逃逸代码执行。
在节点上,监听 Kubelet 端口 10250 上的流量,并等待在Authorization标头中包含 JWT 令牌的请求。
发出az container exec以在上传的容器上运行命令。桥接 Pod 将向受感染节点上的 Kubelet 发送 exec 请求。
在节点上,从请求的Authorization标头中提取桥接令牌,并使用它在 api-server 上获取 shell。
以下视频演示了该攻击:
https://youtu.be/YfZBwKP18CQ
恶意 Azure 用户可以破坏托管 ACI 的多租户 Kubernetes 集群。作为集群管理员,攻击者可以在其他客户容器中执行命令、泄露部署到平台的敏感信息和私有映像,或者部署加密挖矿程序。
0x08 获取Cluster权限的另一种方法:Bridge SSRF
某些功能仅在 Kubernetes 上受支持,例如gitRepo volume。如果 ACI 容器使用了此类功能,并且它部署在 Kubernetes 集群上,这意味着其他容器可能会控制Kubernetes。私有虚拟网络中的容器就是这种情况。
https://docs.microsoft.com/en-us/azure/container-instances/container-instances-volume-gitrepo
https://docs.microsoft.com/en-us/azure/container-instances/container-instances-vnet
我们发现的第二个问题是网桥 Pod 中的服务器端请求伪造 (SSRF) 漏洞。
当桥接 Pod 为az 容器 exec< ctr > < cmd >命令提供服务时,它会向相应的 Kubelet 的/exec端点发送请求。桥接器根据Kubelet 的 /exec 端点的API 规范构造请求,生成以下 URL:
https://< nodeIP >:10250/exec/< customer-namespace >/< customer-pod >/< customer-ctr >?command=< url-encoded-cmd >&error=1&input=1&output=1&tty=1
Bridge必须以某种方式填充 <> 中缺少的参数,< nodeIP >的值是从客户 pod 的status.hostIP字段中检索到的。发现这一点非常有趣,因为节点有权更新其 pod 的状态(例如,为了将其 pod 的status.state字段更新为Running、Terminated等)。
我尝试使用受感染节点的凭据更改 pod 的status.hostIP字段,确实起作用了,但在一两秒钟后,api-server 将hostIP字段更正为其原始值。尽管更改没有持续存在,但我们可以重复更新此字段。
我们编写了一个小脚本来反复更新 pod 的状态,并使用它来将status.hostIP字段设置为1.1.1.1。然后发送了一个az container exec命令。命令执行失败,验证网桥将exec请求发送到1.1.1.1而不是真实节点 IP。我们开始考虑哪些特制的hostIP可以诱使网桥在其他 pod 上执行命令。
简单地将 pod 的status.hostIP设置为另一个节点的 IP 也没有成功。Kubelets 只接受指向它们托管的容器的请求,即使Bridge将exec请求发送到另一个 Kubelet 的 IP,URL 仍将指向我们的命名空间、pod 名称和容器名称。
然后我们意识到 api-server 实际上并没有验证status.hostIP值是否是一个有效的 IP,并且会接受任何字符串——包括 URL 组件。经过几次尝试,我们想出了一个hostIP值,它会诱使网桥在 api-server 容器而不是我们的容器上执行命令:
< apiserver-nodeIP > :10250/exec/kube-system/< apiserver-pod >/< apiserver-container >?command=< url-encoded-command >&error=1&input=1&output=1&tty=1#
此hostIP值将导致网桥将exec请求发送到以下 URL:
https:// < apiserver-nodeIP >:10250/exec/kube-system/< apiserver-pod-name >/< apiserver-ctr >?command=< url-encoded-command >&error=1&input=1&output=1&tty=1 # :10250/exec/< customer-namespace >/< customer-pod-name >/< customer-ctr-name >?command=< command >&error=1&input=1&output=1&tty=1
我们将 pod 的status.hostIP设置为此值并通过az container exec执行命令。攻击成功了!我们拿到的不是容器的shell,而是 api-server 容器的shell。完整的攻击可以在以下视频中看到:
https://youtu.be/7Alea_9oZgU
欺骗Bridge打开 api-server上的shell。
这里的影响与之前的攻击完全相同——对多租户集群的完全管理控制。
0x09 研究总结
跨账户漏洞是公有云的“噩梦”。Azurescape 证明它们比我们想象的更真实。云提供商在保护他们的平台方面投入了大量资金,但不可避免地会存在未知的0 day漏洞并使用户面临风险。云用户应该对云安全采取深度防御方法,以确保控制和检测漏洞,无论威胁来自外部还是来自平台本身。
本文翻译自:https://unit42.paloaltonetworks.com/azure-container-instances/如若转载,请注明原文地址