
ATT&CK TA0007 Discovery——攻擊者在行動前,先摸清楚叢集的全貌。
| 場景 | ATT&CK | 技術 | 攻擊手法 |
|---|---|---|---|
| S27 | T1613 | Container and Resource Discovery | kubectl 全面列舉叢集資源 |
| S28 | T1046 | Network Service Discovery | nmap 掃描 Service/Pod/Node CIDR |
| S29 | T1069 | Permission Groups Discovery | 列舉 RBAC 角色和權限 |
Discovery 是攻擊鏈中承上啟下的環節——攻擊者拿到憑證後(Day 16 TA0006),先偵察環境,再決定橫向移動(Day 19 TA0008)和最終破壞(Day 20–21 TA0040)的目標。Discovery 本身不造成傷害,但沒有 Discovery 就沒有後續的精準攻擊。
所有指令都在 koad 專案目錄下執行。如果還沒 clone,請先參考 Day 1 的 Step 0。
開始前請確認以下環境就緒。
# 主機終端
kubectl get pods -n koad | grep -E "resource-enum|net-scanner|rbac-enum"
預期結果:
resource-enum-... 1/1 Running 0 ...
net-scanner-... 1/1 Running 0 ...
rbac-enum-... 1/1 Running 0 ...
如果 Pod 不在 Running 狀態,重新部署:
kubectl apply -f scenarios/discovery/S27-resource-discovery.yaml kubectl apply -f scenarios/discovery/S28-network-scan.yaml kubectl apply -f scenarios/discovery/S29-rbac-enumeration.yaml
# 第二終端 — 觀察偵察行為告警
kubectl logs -f -n falco -l app.kubernetes.io/name=falco | grep -i --color "koad\|discovery\|nmap\|enum"
完成本日實作後,你將能夠:
拿到一個有權限的 SA Token 後,攻擊者的第一個動作就是「看我能看什麼」。K8s API 是結構化的——Namespace、Pod、Service、Secret、Node——每一個都是情報。

apiVersion: v1
kind: Pod
metadata:
name: resource-scanner
namespace: koad
labels:
koad-scenario: S27
mitre-attck: T1613
spec:
serviceAccountName: overprivileged-sa
containers:
- name: scanner
image: bitnami/kubectl:latest
command: ["sh", "-c"]
args:
- |
echo "=== KOAD-S27: Cluster Resource Discovery ==="
echo "--- Namespaces ---"
kubectl get namespaces
echo "--- Pods (all namespaces, first 20) ---"
kubectl get pods -A --no-headers | head -20
echo "--- Nodes ---"
kubectl get nodes -o wide
echo "--- ServiceAccounts (first 20) ---"
kubectl get sa -A --no-headers | head -20
echo "--- Secrets (first 20) ---"
kubectl get secrets -A --no-headers | head -20
echo "--- Services (first 20) ---"
kubectl get svc -A --no-headers | head -20
sleep infinity
這個場景的 YAML 有兩個關鍵設計:
spec:
serviceAccountName: overprivileged-sa # 關鍵:綁定了過寬權限的 SA
containers:
- name: scanner
image: bitnami/kubectl:latest # 內建 kubectl,無需額外安裝
serviceAccountName: overprivileged-sa:模擬現實中常見的過度授權——開發者為了方便,給 SA 綁了 cluster-admin 或大量 list/get 權限。攻擊者拿到這個 Pod 的 Shell 後,自動繼承 SA 的所有權限bitnami/kubectl 映像:省去攻擊者自己安裝 kubectl 的步驟。真實環境中,攻擊者會從 /var/run/secrets/kubernetes.io/serviceaccount/token 抓 Token,用 curl 直接呼叫 API資源列舉是所有 K8s 攻擊的第一步——攻擊者需要了解叢集全貌才能決定下一步。2023 年 Aqua Security 報告指出,超過 50% 的 K8s 叢集存在過度授權的 ServiceAccount,使得攻擊者可以在幾秒內完成情報收集。這個場景讓學員親身體驗「一個過寬的 SA 能看到多少資訊」,理解為什麼最小權限原則如此重要。
步驟 1:列舉 Namespace——了解叢集範圍和組織結構
kubectl get namespaces
預期結果:
NAME STATUS AGE
default Active 2d
koad Active 2d ← 攻擊靶場
koad-production Active 2d ← 模擬生產環境(有價值目標)
kube-system Active 2d ← 系統元件
falco Active 1d ← 安全工具(Day 15 的攻擊目標)
kyverno Active 1d ← 安全工具
攻擊者從 Namespace 名稱就能推斷叢集結構:
koad-production暗示有生產環境資料,falco/kyverno暴露了安全工具的部署位置——這些都是後續攻擊的關鍵情報。

步驟 2:列舉 Node——了解基礎設施
kubectl get nodes -o wide
預期結果:
NAME STATUS ROLES VERSION INTERNAL-IP OS-IMAGE
minikube Ready control-plane v1.30.0 192.168.49.2 Ubuntu 22.04
攻擊者學到:單節點叢集、K8s v1.30.0、Ubuntu 22.04

步驟 3:列舉 Pod——找出運行的工作負載
kubectl get pods -A -o wide
預期結果:
NAMESPACE NAME READY IP NODE
koad ssrf-webapp-xxx 1/1 10.244.0.5 minikube
koad attacker 1/1 10.244.0.10 minikube
koad-production production-app-xxx 1/1 10.244.0.20 minikube
falco falco-7k2x9 2/2 10.244.0.30 minikube
攻擊者學到:IP 位址、服務分佈、安全工具位置

步驟 4:查看 Pod 詳情——找出特權容器和特殊掛載
kubectl get pods -A -o json | \
jq -r '.items[] | select(
.spec.containers[]?.securityContext?.privileged == true or
.spec.volumes[]?.hostPath != null
) | "\(.metadata.namespace)/\(.metadata.name) ← 特權或 HostPath"'
預期結果:
koad/privileged-pod ← 特權或 HostPath
koad/host-image-builder ← 特權或 HostPath
攻擊者學到:哪些 Pod 可以用來逃逸

步驟 5:列舉 Secret——找出憑證位置
kubectl get secrets -A
預期結果:
NAMESPACE NAME TYPE
koad database-credentials Opaque
koad docker-registry-creds kubernetes.io/dockerconfigjson
koad-production production-db-creds Opaque
攻擊者學到:哪些 Secret 值得竊取

步驟 6:列舉 ServiceAccount——找出可劫持的身份
kubectl get sa -A --no-headers | grep -v default
預期結果:
koad overprivileged-sa 0 2d
koad readonly-sa 0 2d
攻擊者學到:
overprivileged-sa名稱暗示高權限

步驟 7:列舉 Service——找出服務端點
kubectl get svc -A
預期結果:
NAMESPACE NAME TYPE CLUSTER-IP PORT(S)
default kubernetes ClusterIP 10.96.0.1 443/TCP
koad ssrf-webapp ClusterIP 10.96.100.10 8080/TCP
koad kube-dashboard ClusterIP 10.96.100.20 443/TCP
攻擊者學到:哪些服務可以作為攻擊跳板

| 資源 | 攻擊者學到什麼 | 下一步行動 |
|---|---|---|
| Namespace | 叢集邊界、團隊結構 | 確定攻擊目標範圍 |
| Pod | 運行的服務、映像版本 | 查 CVE、找特權 Pod |
| Node | 基礎設施規模、OS 版本 | 規劃逃逸路徑 |
| Secret | 可竊取的憑證清單 | TA0006 Credential Access |
| Service | 可存取的服務端點 | TA0008 Lateral Movement |
| ServiceAccount | 可劫持的身份清單 | TA0004 Privilege Escalation |
| ConfigMap | 可能包含的憑證 | 搜尋明文密碼 |
| NetworkPolicy | 網路隔離程度 | 找出允許的通訊路徑 |
K8s 叢集有三個 CIDR 範圍:Service CIDR(預設 10.96.0.0/16)、Pod CIDR(預設 10.244.0.0/16)和 Node IP。攻擊者用 nmap 掃描這些範圍,找出暴露的服務——特別是那些沒有出現在 kubectl get svc 中的服務。

apiVersion: v1
kind: Pod
metadata:
name: network-scanner
namespace: koad
labels:
koad-scenario: S28
mitre-attck: T1046
spec:
containers:
- name: scanner
image: ubuntu:22.04
command: ["sh", "-c"]
args:
- |
apt-get update -qq && apt-get install -y -qq nmap dnsutils curl iproute2 > /dev/null 2>&1
echo "=== KOAD-S28: Network Service Discovery ==="
echo "--- Container Network Info ---"
ip addr show eth0 | grep inet
echo "--- DNS Servers ---"
cat /etc/resolv.conf
sleep infinity
env:
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
這個場景的 YAML 有三個關鍵設計:
spec:
containers:
- name: scanner
image: ubuntu:22.04 # 通用映像,模擬攻擊者自備工具
args:
- |
apt-get install -y nmap ... # 安裝掃描工具——真實攻擊者也會這樣做
env:
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP # 關鍵:自動取得 Node IP,無需猜測
ubuntu:22.04 通用映像:沒有用專門的安全工具映像,模擬攻擊者從一般容器開始安裝工具的真實流程status.hostIP 欄位參考:K8s 的 Downward API 讓 Pod 自動取得 Node IP。攻擊者不需要猜測——K8s 主動提供了掃描目標serviceAccountName:使用 default SA,證明即使沒有 RBAC 權限,純網路層的掃描仍然可以發現大量資訊網路掃描是攻擊者在拿到容器 Shell 後的標準動作之一。K8s 預設不隔離 Pod 之間的網路流量——沒有 NetworkPolicy 的叢集就像一個扁平網路,任何 Pod 都能掃到 kubelet(10250)、etcd(2379)等高危服務。Capital One 2019 年事件中,攻擊者正是透過 Metadata API 加上內部網路探測完成了橫向移動。這個場景讓學員看到「沒有 NetworkPolicy 的叢集有多透明」。
步驟 1:K8s 內建 DNS 洩漏服務名稱
nslookup kubernetes.default.svc.cluster.local
預期結果:
Server: 10.96.0.10 ← CoreDNS 的 Service IP
Address: 10.96.0.10#53
Name: kubernetes.default.svc.cluster.local
Address: 10.96.0.1 ← API Server 的 Service IP
K8s 內建 DNS 會自動回應
kubernetes.default.svc.cluster.local,暴露 API Server 的 ClusterIP。攻擊者不需要任何權限就能透過 DNS 定位核心服務。

步驟 2:DNS 反向查詢——找出更多服務
nslookup 10.96.0.1
預期結果:
1.0.96.10.in-addr.arpa name = kubernetes.default.svc.cluster.local.
反向 DNS 查詢能從 IP 反推服務名稱,攻擊者可以用它確認掃描到的 IP 屬於哪個 K8s Service。

步驟 3:DNS 區域轉移嘗試(通常被阻擋)
dig axfr cluster.local @10.96.0.10
預期結果:
; Transfer failed.
如果 CoreDNS 未正確設定,可能洩漏所有 Service 記錄

步驟 4:掃描關鍵連接埠——找出高價值服務
nmap -sT -p 443,8443,6443,2379,2380,10250,10255,10256,8080,9090 \
10.96.0.0/24 --open -T4
預期結果:
PORT STATE SERVICE
443/tcp open https ← API Server / Dashboard
6443/tcp open sun-sr-https ← API Server
10250/tcp open unknown ← kubelet API(危險!)
10255/tcp open unknown ← kubelet 唯讀 API
掃描結果直接暴露了叢集的高價值目標:kubelet 10250 連接埠允許未認證 RCE,etcd 2379 連接埠存放所有 Secret 明文。這些連接埠的開放狀態決定了攻擊者的下一步方向。

步驟 5:掃描 Pod 網路——找出所有容器連接埠
nmap -sT -p 1-10000 10.244.0.0/24 --open -T4
預期結果:
PORT STATE SERVICE
80/tcp open http ← nginx
8080/tcp open http-proxy ← SSRF webapp
8888/tcp open sun-answerbook ← Jupyter(未認證 shell!)
5432/tcp open postgresql ← 資料庫
Pod CIDR 掃描揭露了
kubectl get svc看不到的服務——例如直接暴露連接埠的 Jupyter(8888)和資料庫(5432),這些是攻擊者直接利用的目標。

步驟 6:掃描 NodePort 範圍——找出暴露到節點的服務
nmap -sT -p 30000-32767 $NODE_IP --open
預期結果:
PORT STATE SERVICE
30080/tcp open unknown ← Falcosidekick UI
30443/tcp open unknown ← Dashboard NodePort
NodePort 暴露到節點外部網路,攻擊者不需要進入叢集內部就能存取這些服務。Dashboard NodePort 是常見的攻擊入口。

| 連接埠 | 服務 | 攻擊價值 | 風險等級 |
|---|---|---|---|
| 6443 | API Server | 叢集完全控制 | 極高 |
| 10250 | kubelet API | 容器 RCE、節點存取 | 極高 |
| 10255 | kubelet 唯讀 | Pod 資訊洩漏、環境變數 | 高 |
| 2379/2380 | etcd | 所有資料(含 Secret 明文) | 極高 |
| 8080 | 應用服務 | SSRF / Web 攻擊 | 中 |
| 8888 | Jupyter | 未認證 Shell | 極高 |
| 9090 | Prometheus | 指標洩漏(可能含敏感資訊) | 中 |
| 3000 | Grafana | 預設密碼 admin/admin | 高 |
- rule: KOAD S28 Network Scanning
desc: Detect nmap or similar network scanning tools in containers
condition: >
spawned_process and container and
proc.name in (nmap, masscan, zmap, naabu, rustscan)
output: >
Network scan 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, T1046, discovery, network-scan]
K8s 的 RBAC 定義了「誰能做什麼」。攻擊者列舉 ClusterRole、ClusterRoleBinding,找出過度授權的帳戶——特別是 cluster-admin 綁定。一旦找到高權限 SA,就能利用它進行更大範圍的攻擊。
apiVersion: v1
kind: Pod
metadata:
name: rbac-enumerator
namespace: koad
labels:
koad-scenario: S29
mitre-attck: T1069
spec:
serviceAccountName: overprivileged-sa
containers:
- name: enumerator
image: bitnami/kubectl:latest
command: ["sh", "-c"]
args:
- |
echo "=== KOAD-S29: RBAC Permission Enumeration ==="
echo "--- My Permissions ---"
kubectl auth can-i --list | head -30
echo "--- ClusterRoles ---"
kubectl get clusterroles --no-headers | head -20
echo "--- ClusterRoleBindings ---"
kubectl get clusterrolebindings --no-headers | head -20
echo "--- cluster-admin Bindings ---"
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") |
{name: .metadata.name, subjects: .subjects}'
sleep infinity
這個場景的 YAML 重點在 SA 綁定和列舉邏輯:
spec:
serviceAccountName: overprivileged-sa # 與 S27 共用同一個過寬 SA
containers:
- name: enumerator
image: bitnami/kubectl:latest # 內建 kubectl + jq,方便 JSON 解析
args:
- |
kubectl auth can-i --list # 第一步:看自己能做什麼
kubectl get clusterrolebindings -o json | \
jq '... select(.roleRef.name=="cluster-admin")' # 找高權限綁定
serviceAccountName: overprivileged-sa:與 S27 共用,展示同一個 SA 如何從資源列舉延伸到 RBAC 探測——攻擊是連續的kubectl auth can-i --list:K8s 內建的權限自查指令,攻擊者用它確認自己能做什麼jq 過濾 cluster-admin:精準定位高權限綁定,這是攻擊者決定提權路徑的關鍵情報RBAC 列舉是 K8s 攻擊中最具價值的探測行為——找到一個綁了 cluster-admin 的 SA 就等於拿到叢集的鑰匙。真實世界中,大量叢集存在被遺忘的 ClusterRoleBinding(例如 CI/CD 系統留下的、開發測試用的),這些都是攻擊者的黃金目標。這個場景教學員用攻擊者的視角審視 RBAC 配置,理解為什麼定期 audit ClusterRoleBinding 是必要的安全實踐。
步驟 1:檢查目前 Token 的權限
kubectl auth can-i --list
預期結果:
Resources Non-Resource URLs Resource Names Verbs
*.* [] [] [*] ← cluster-admin!
[*] [] [*]
如果看到
*.* [*],攻擊者已經是 cluster-admin

步驟 2:快速檢查特定危險權限
kubectl auth can-i create pods
預期結果:
yes
yes代表此 Token 可以建立任意 Pod——攻擊者能部署特權容器進行容器逃逸(Day 8–11)。
kubectl auth can-i get secrets
預期結果:
yes
能讀取 Secret 代表攻擊者可以竊取所有憑證(資料庫密碼、API Key、其他 SA Token),是 Credential Access 的關鍵權限。
kubectl auth can-i create clusterrolebindings
預期結果:
yes
能建立 ClusterRoleBinding 是最危險的權限之一——攻擊者可以把任何 SA 綁定到
cluster-admin,實作自我提權。
kubectl auth can-i '*' '*'
預期結果:
yes ← 確認是 cluster-admin
auth can-i回傳yes,確認目前 SA 擁有 cluster-admin 權限——攻擊者現在知道可以完全控制叢集。

步驟 3:找出所有 cluster-admin 綁定(最有價值的目標)
kubectl get clusterrolebindings -o json | \
jq -r '.items[] | select(.roleRef.name=="cluster-admin") |
"\(.metadata.name): \(.subjects // [] | map("\(.kind)/\(.namespace // "cluster")/\(.name)") | join(", "))"'
預期結果:
koad-attacker-binding: ServiceAccount/koad/overprivileged-sa
system:masters: Group//system:masters
這個輸出直接揭露了叢集中誰擁有最高權限。
overprivileged-sa綁定到 cluster-admin 代表攻擊者只需要竊取該 SA Token 就能控制整個叢集。

步驟 4:找出可以提權的 Role
kubectl get clusterroles -o json | \
jq -r '.items[] | select(.rules[]? |
(.verbs[]? == "*") or
(.resources[]? == "clusterrolebindings" and (.verbs[]? | IN("create","patch")))
) | .metadata.name' | sort -u
預期結果:
admin
cluster-admin
edit
這些 ClusterRole 如果綁定到不該有的 SA,就是提權路徑

步驟 5:檢查特定 SA 的完整權限
kubectl auth can-i --list \
--as=system:serviceaccount:koad:overprivileged-sa
預期結果:
Resources Non-Resource URLs Resource Names Verbs
*.* [] [] [*]
如果是 cluster-admin,所有操作都回傳 "yes"

步驟 6:找出所有非 default 的 RoleBinding
kubectl get rolebindings -A -o json | \
jq -r '.items[] | select(.metadata.name != "system:") |
"\(.metadata.namespace)/\(.metadata.name) → \(.roleRef.name)"'
預期結果:
koad/koad-attacker-role → admin
koad-production/prod-readonly → view
RoleBinding 列表揭露了跨 Namespace 的權限分佈。攻擊者可以找到綁定到
admin或edit角色的 SA,作為橫向移動的跳板。


# 限制 SA 只能存取必要的資源
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
namespace: koad
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"] # 只能 get,不能 list
resourceNames: ["my-app"] # 限定資源名稱
關鍵原則:
list 比 get 危險——list 可以列舉所有資源,get 需要知道名稱resourceNames 限制只能存取特定資源——進一步縮小範圍cluster-admin——這是最常見的過度授權# Default Deny:預設拒絕所有流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: koad
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
# nmap 掃描結果:所有連接埠顯示 filtered
---
# 只允許必要的出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-only
namespace: koad
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: UDP
port: 53 # 只允許 DNS
- protocol: TCP
port: 53
# Audit Policy:記錄所有 list 請求
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
verbs: ["list", "watch"]
resources:
- group: ""
resources: ["secrets", "pods", "nodes", "serviceaccounts"]
- level: RequestResponse
verbs: ["list"]
resources:
- group: "rbac.authorization.k8s.io"
resources: ["clusterroles", "clusterrolebindings"]
連續的 list 請求是 Discovery 的典型特徵——正常應用不會在短時間內 list 多種資源。
觸發條件:
同一個 SA 在 5 分鐘內執行 > 10 次不同資源的 list 請求
例如:
14:30:01 list namespaces
14:30:02 list pods (all namespaces)
14:30:03 list secrets (all namespaces)
14:30:04 list services (all namespaces)
14:30:05 list clusterroles
...
→ 高度可疑:正常應用不會這樣做

| 場景 | Falco 規則 | 其他偵測 | 防禦 |
|---|---|---|---|
| S27 | — | Audit Log 偵測大量 list 請求 | RBAC 最小權限 + 禁止 list |
| S28 | KOAD S28 Network Scanning | NetworkPolicy 阻擋 | Default Deny NetworkPolicy |
| S29 | — | Audit Log 偵測 RBAC 列舉 | 限制 RBAC 資源的 list 權限 |
| 考點 | 本日內容 | 權重 | 考試提示 |
|---|---|---|---|
| RBAC 設計 | 最小權限 + resourceNames | Cluster Hardening 15% | get vs list 的差異是常考題 |
| NetworkPolicy | default-deny 阻擋掃描 | Cluster Setup 15% | 必考:完整的 default-deny 設定 |
| Audit Log | 偵測列舉行為 | Monitoring/Runtime 20% | 會考 Audit Policy 的 level 設定 |
| SA 安全 | 不給應用 cluster-admin | Cluster Hardening 15% | 注意 RoleBinding vs ClusterRoleBinding |
# 主機終端 — Discovery 場景為讀取操作,不需要特殊清理
kubectl delete pod -n koad -l koad-scenario=S27 2>/dev/null
kubectl delete pod -n koad -l koad-scenario=S28 2>/dev/null
kubectl delete pod -n koad -l koad-scenario=S29 2>/dev/null
kubectl apply -f scenarios/discovery/
Discovery 場景是純偵察操作,不會修改叢集狀態。重建 Pod 即可恢復初始環境。
| 問題 | 原因 | 解法 |
|---|---|---|
nmap 找不到指令 |
network-scanner Pod 的 apt-get install 尚未完成 |
等 Pod 完全啟動(約 30 秒);kubectl exec -n koad network-scanner -- which nmap 確認 |
nmap 掃描全部顯示 filtered |
NetworkPolicy 已套用 default-deny | kubectl get networkpolicy -n koad 檢查;測試前先刪除限制性 NetworkPolicy |
| nmap 掃描非常慢(超過 5 分鐘) | 掃描範圍太大或使用了 -sS (SYN scan) 需要 root |
改用 -sT(TCP connect scan)不需 root;縮小掃描範圍如 /24 而非 /16;加 -T4 加速 |
kubectl auth can-i --list 回傳空白 |
使用 default SA,權限極少 | 確認 Pod 使用 overprivileged-sa:kubectl get pod -n koad rbac-enumerator -o jsonpath='{.spec.serviceAccountName}' |
jq 找不到指令 |
映像未內建 jq | bitnami/kubectl 映像內建 jq;若用其他映像需 apt-get install -y jq |
DNS 查詢 nslookup 無回應 |
CoreDNS Pod 未正常運行或 /etc/resolv.conf 設定錯誤 |
kubectl get pods -n kube-system -l k8s-app=kube-dns 確認 CoreDNS 狀態 |
| 掃描結果與預期連接埠不同 | Minikube 環境連接埠配置可能與標準叢集不同 | 先用 kubectl get svc -A 確認實際 ClusterIP,再針對已知 IP 掃描 |
kubectl auth can-i --list 評估目前 SA 的權限範圍kubectl auth can-i --list 評估 SA 權限範圍明天 Day 19 進入 ATT&CK 的最後一個攻擊戰術——TA0008 Lateral Movement,攻擊者如何用竊取的 Token 跨 Namespace 橫向移動,甚至從 K8s 穿透到雲端。