Kubernetes 密钥泄露:影响评估与缓解策略

威胁情报报告记录了近年来攻击者如何从开发者工作站获取 AWS IAM 凭证、枚举云账户并访问 Kubernetes 集群的活动。在那之后,攻击者部署被植入恶意代码的容器镜像以实现横向移动并窃取敏感信息。MITRE ATT&CK 攻击链映射为:T1552.001(​文件中的凭证)→ T1078.004(​有效账户:云账户)→ T1610(​部署容器)→ T1496(​资源劫持)。

这不是孤例。Shai-Hulud 供应链攻击从 CI 和开发者工作站窃取 Kubernetes 凭证,恰好为这类攻击链提供了养分。

这项研究始于一个简短的问题清单:

  • Kubernetes Secret 究竟是什么?
  • 攻击者能用它们做什么?
  • 防御者如何强化他们的集群?

因此,在我们探讨在野外发现的情况以及如何强化集群以减轻影响之前,先来定义什么是 Kubernetes Secret。

三个攻击面,三种 Secret 格式

集群的简化视图中有三个方面与本文相关:

  • 开发者或自动化流水线使用凭证连接 Kubernetes API 服务器。这是规范的前门。
  • 每个节点上,kubelet 都暴露了自己的 HTTPS API。当在网络上可达时,相同的凭证可以直接对其进行身份验证。
  • 集群的节点使用第二组凭证从容器镜像仓库(Docker Hub、GitHub Container Registry、ECR、Quay、GitLab、ACR……)拉取镜像。
Kubernetes 架构

这些是泄露的 Secret 影响最大的攻击面,而三种 Secret 格式解锁了这些攻击面:

  • TLS 客户端证书供人类通过 kubeconfig 文件配合 kubectl 命令连接 Kubernetes 集群使用。
  • JSON Web Token 或服务账户是非人类身份(NHI),用于从 CI/CD 作业、控制器和集成中自动化集群操作。默认情况下,JWT 没有过期日期——这就是为什么多年前泄露的 JWT 至今仍然有效。
  • 容器镜像仓库凭证在集群中以类型为 kubernetes.io/dockerconfigjson 的 Secret 对象形式存在。它是一个 base64 编码的 JSON 文档。传统的 dockercfg 格式也存在,但在野外已很少见。

这三种格式——TLS、JWT、Docker config JSON——就是我们将在下文中进行检测、验证和利用的对象。

Kubernetes API 服务器

kubeconfig 是一个单文件凭证。一旦泄露,整个集群就暴露了。

bash
apiVersion: v1 kind: Config clusters: - name: minikube cluster: server: https://10.11.12.13:8443 certificate-authority-data: LS0tLS1CRUd[..]LS0tCg== users: - name: minikube user: client-certificate-data: LS0tLS1CRUd[..]LS0tCg== client-key-data: LS0tLS1CRUd[..]LS0tCg== contexts: - name: minikube context: cluster: minikube user: minikube current-context: minikube

在这个示例中,client-certificate-data 和 client-key-data 字段是实际的凭证。其他都是配置信息。确认其中一个是否有效并不需要 kubectl。API 服务器只是一个 HTTPS 服务,用 curl 就足够了。

bash
# 1. Decode the base64-encoded key and certificate echo LS0tLS1CRUd[..]LS0tCg== | base64 -d > key.pem echo LS0tLS1CRUd[..]LS0tCg== | base64 -d > cert.pem # 2. (Optional) Sanity-check that the cert and key match openssl rsa -in key.pem -modulus -noout | sha256sum openssl x509 -in cert.pem -modulus -noout | sha256sum # 3. Use the credentials against the API server curl --insecure --key key.pem --cert cert.pem https://10.11.12.13:8443/api/v1/namespaces

--insecure 在这里是合适的,因为我们正在验证客户端凭证,而不是服务器的身份。成功的响应意味着凭证有效。JWT 也可以用同样的方式测试。

攻击场景

我们在野外发现的凭证几乎总是权限过度的。有了这些凭证,攻击者有多个选择:

  • 横向移动:部署特权 Pod,从容器提权到节点。
  • 持久化:创建或修改服务账户以获取长期有效的 JWT。
  • 凭证访问:转储每个命名空间中每个 Secret 对象的内容。这就是连锁反应开始的地方:一个凭证可以访问更多凭证。

例如,我们发现从公共 Docker 镜像中提取的一个有效 JWT 让我们得以访问一个集群。该集群的 Secret 中包含有效的镜像仓库凭证。这些凭证反过来打开了私有 Docker Hub 镜像和私有 GitHub 组织的大门。

要深入了解 Kubernetes 内部失陷后的持久化技术,Insomni'hack 2025 关于该主题的演讲(https://insomnihack.ch/talks/beyond-the-surface-exploring-attacker-persistence-strategies-in-kubernetes/)值得一读。

加固

没有一项缓解措施是稀奇古怪的:

  • 网络隔离:不要将 API 服务器暴露在互联网上。每个主要云提供商都有相关设置。
  • 日志记录和监控:将集群视为 SSH:记录每个经过身份验证的请求,并通过 SIEM 对可疑活动发出警报。
  • 短期凭证:如果必须允许公共访问,请将集群接入 OIDC 提供商(AWS IAM、Azure AD、Okta)。这样当令牌泄露时,暴露窗口从数年缩短到数小时或数分钟。
  • 最小权限:将服务账户绑定到它们需要的命名空间,而非整个集群。

Kubernetes 容器镜像仓库

镜像仓库凭证作为 Secret 对象存储在集群中:

bash
apiVersion: v1 kind: Secret metadata: name: registry data: .dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsidXNlcm5hbWUiOiJnaXQiLCJwYXNzd29yZCI6Imd1YXJkaWFuIiwiYXV0aCI6IloybDBPbWQxWVhKa2FXRnVDZz09In19fQo= type: kubernetes.io/dockerconfigjson

这里的 .dockerconfigjson 字段包含 base64 编码的真实凭证。一条简短的 jq 命令可以解开这个结构:

bash
echo $DOCKERCONFIGJSON | jq -R '@base64d | fromjson | .auths' { "https://index.docker.io/v1/": { "username": "git", "password": "guardian", "auth": "Z2l0Omd1YXJkaWFuCg==" } }

从那里开始,验证和使用凭证只需一条 docker login:

bash
docker login -u "$USERNAME" -p "$PASSWORD" docker pull "$USERNAME/image:$TAG"

对于 ECR、ACR 及类似的托管镜像仓库,你需要先用静态凭证换取 bearer token 然后再拉取。一旦完成身份验证,有三个操作至关重要:拉取(读取)、推送(写入)以及枚举镜像。

攻击场景

关于镜像仓库凭证的有趣之处在于,它们很少仅限定于该镜像仓库本身:

  • 用于 ghcr.io 的 GitHub 个人访问令牌按照 GitHub 自身的设计,也可以用于 GitHub API:列出私有仓库、读取用户详情、枚举组织。我们将在下文中看到其影响。
  • Docker Hub 凭证也会暴露用户的账户元数据。

因此,一个泄露的镜像仓库 Secret 通常会给攻击者带来:

  • 发现:从同一账户拉取其他 Docker 镜像;对于 GitHub 令牌,枚举 git 仓库。
  • 横向移动:危及集群特权 Pod 即将拉取的镜像。
  • 更多凭证:私有镜像和私有仓库往往包含更多硬编码的 Secret。

加固

我们记录的大部分情况可以通过三个习惯来预防:

• 使用私有容器镜像仓库:公共镜像仓库不是存储私有制品的地方。

• 尽可能使用只读凭证。你的集群只需要拉取权限。它不需要推送权限,当然也不需要在 GitHub 上拥有仓库范围的权限。

• 退役时撤销。当集群消失时,也要撤销其镜像仓库凭证。数据显示,镜像仓库凭证经常会存活得比它们服务的集群更久。

公开泄露与负责任的披露

2025 年秋季,我们扫描了公共 GitHub 和 Docker Hub,查找三种 Secret 格式并验证了每个匹配项。以下是我们的发现。

Kubernetes API 服务器凭证

• 总共 44 个独特的活跃集群。其中四个拥有超过 10 个节点,一个拥有超过 200 个节点:这是一个严肃的生产环境。

• 30% 的集群已被暴露超过两年。迄今为止发现的最古老的有效泄露可追溯到 2021 年。

• 只有 10% 的泄露 Secret 已从其来源处删除。其余的仍然躺在最初发布的位置上。通常是因为开发者删除了提交但从未轮换凭证。

一种模式脱颖而出:有效的 JWT 几乎只在 Docker Hub 上找到,而非 GitHub。我们的理论是,证书在人类意外提交 kubeconfig 时泄露,而 JWT 则是在 CI 构建期间将服务账户烘焙到容器镜像中时泄露的。

容器镜像仓库凭证

• 2,034 个具有可解析主机名的镜像仓库凭证。

• 提供商分布:1,025 个 Docker Hub、480 个 GitHub、276 个 Quay、249 个 GitLab、59 个 Azure。

• 46% 的可解析凭证仍然有效。

• 通过这些凭证发现了 309 个私有 Docker Hub 镜像。

• 730 个私有 GitHub 仓库可访问:并非来自 GitHub 泄露,而是来自 Kubernetes Secret 泄露。

• 一些泄露可追溯到 2022 年,至今仍然有效。

披露

GitGuardian 已在检测到开发者泄露的凭证出现在公共 GitHub 上时通知他们。在这里,我们更进了一步:从开发者上溯到公司。将凭证映射回其所有者本身就是一项研究任务:Pod 名称、主机名等都成为了识别线索。总共,我们联系了 11 家公司:

• Kubernetes API 服务器:已识别并联系了 7 个所有者。

• 容器镜像仓库:已识别并联系了 4 个所有者。

大部分工作都花在了寻找正确的联系人上。RFC 9116(​*/.well-known/security.txt*)在这里对我们帮助极大。如果你运营面向互联网的服务,请考虑实现它。

从研究到产品

这项工作为 GitGuardian 内部的检测和有效性引擎提供了养分。在研究期间,Kubernetes TLS 证书和 JWT 检查器以及容器镜像仓库检查器都经过了调整,现在在涵盖我们覆盖的所有提供商(Docker Hub、GitHub、GitLab、Quay、Azure)上持续捕获新的泄露。

仅在 2026 年第一季度,这些检测器就在 GitHub 上发现了近 2,000 个新的 Kubernetes Secret 泄露;其中 28% 在泄露时仍然有效。

泄露的 Kubernetes Secret 是多样的,配置错误使情况更糟,而横向移动场景是真实存在的。以上所有加固建议都不是新东西:它们已经有完善的文档记录。关键在于应用它们,以及在泄露滑过防线时尽早捕获。

📹

本文基于在 BlackAlps 2025 上的演讲"All You Can Leak: Real Tales of Publicly Leaked Kubernetes Secrets"。在 YouTube 上观看完整演讲: