K8s 是怎么被攻破的:威胁模型与攻击树入门

Posted on 三 29 7月 2026 in Tech

Abstract K8s 是怎么被攻破的:威胁模型与攻击树入门
Authors Walter Fan
Category Cloud Native Security
Status v1.0
Updated 2026-07-29
License CC-BY-NC-ND 4.0

K8s 是怎么被攻破的:威胁模型与攻击树入门

前阵子帮团队看一个 K8s 集群的安全,我发现一个有意思的现象:大家都在装各种扫描器、连各种告警,可要是问一句"攻击者到底会怎么打进来、一步步走到哪一步",多半答不上来。

这就有点像家里装了一屋子摄像头,却没想过"小偷最可能从哪扇窗户进"。工具是"术",威胁模型才是"道"。 你得先知道对手会怎么想,才知道该把钱花在哪把锁上。

这篇咱们不堆工具,就聊两件事:K8s 的威胁模型(这座"城"有哪些门、哪些墙、哪些值钱东西),以及攻击树(攻击者从一个小 Pod 出发,怎么一格一格爬到"城主")。内容主要参考 ControlPlane 为 CNCF 做的 Kubernetes 威胁模型,以及 Kubernetes 官方 2019 年的安全审计(含官方的《Attacking Kubernetes》攻击树和威胁模型报告)——都是公开、权威的资料,我尽量翻成大白话。

提醒:本文是防御视角的科普,讲清"攻击者会怎么想"是为了知道"我该怎么守"。所有内容用于加固自己的系统,别拿去干坏事。


先把这座"城"画出来

要谈威胁,先得知道保护的是什么。K8s 的核心组件,我用一座城来打比方:

flowchart TB
  subgraph CP["控制平面 Control Plane(市中心)"]
    API["kube-apiserver<br/>市政大厅:所有请求的唯一入口"]
    ETCD["etcd<br/>档案馆:存着全部配置和 Secrets"]
    SCHED["scheduler<br/>调度局:决定 Pod 去哪个节点"]
    CM["controller-manager<br/>执法队:让现状逼近期望状态"]
    API <--> ETCD
    API <--> SCHED
    API <--> CM
  end

  subgraph N1["工作节点 Node(一个街区)"]
    KUBELET["kubelet<br/>物业管家:管本街区的 Pod"]
    PROXY["kube-proxy<br/>门卫:管网络转发"]
    CRI["容器运行时 containerd<br/>包工头:真正把容器跑起来"]
    POD1["Pod<br/>租户:你的业务容器"]
    KUBELET --> CRI --> POD1
    KUBELET --> PROXY
  end

  USER["开发者 / CI<br/>(拿 kubeconfig 的人)"] -->|kubectl / HTTPS| API
  API -->|下发指令| KUBELET

  classDef cp fill:#1E3A5F,stroke:#12263a,color:#fff;
  classDef node fill:#E8F4EA,stroke:#2C7A3F,color:#1a1a1a;
  classDef ext fill:#FCF3CF,stroke:#B7950B,color:#1a1a1a;
  class API,ETCD,SCHED,CM cp;
  class KUBELET,PROXY,CRI,POD1 node;
  class USER ext;

几个关键角色,记住它们的"身份",后面攻击树全靠它们:

  • kube-apiserver(市政大厅):整个集群唯一的正门。所有人——开发者、其他组件、每个节点——都得通过它办事。它管三件事:认证(你是谁)、鉴权(你能干啥)、准入控制(这事合不合规矩)。攻破它,基本等于拿下全城。
  • etcd(档案馆):一个键值数据库,存着集群的全部状态——包括所有的 Secrets(数据库密码、Token、证书)。注意:K8s 里的 Secret 默认只是 base64 编码,不是加密。谁能直接读 etcd,谁就拿到了全城的钥匙串。
  • kubelet(物业管家):每个节点上都有一个,负责按 apiserver 的指令把 Pod 跑起来。它有个要命的接口,后面会讲。
  • 容器运行时(包工头):真正干活的,比如 containerd。容器"逃逸"到宿主机,逃的就是它这一层。
  • kubeconfig / ServiceAccount Token(通行证):人用 kubeconfig,Pod 用 ServiceAccount Token。这些"通行证"一旦泄露,攻击者就能冒充你办事。

一句话:K8s 安全的核心,就是守住 apiserver 这道正门、看住 etcd 这本账、别让通行证乱跑。


威胁模型:用 STRIDE 给每个组件"过筛子"

知道有哪些资产后,怎么系统地想"它们会遭什么罪"?业界常用一个叫 STRIDE 的清单——它是六类威胁的首字母,你可以理解成给每个组件挨个问六个问题:

字母 威胁 一句话理解 K8s 里的典型例子
S Spoofing 假冒 有人冒充别人 伪造 ServiceAccount Token 冒充某个 Pod
T Tampering 篡改 数据被偷改 直接改 etcd 里的对象;篡改镜像
R Repudiation 抵赖 干了坏事不认账 关掉审计日志,事后查无对证
I Information Disclosure 泄露 该保密的看见了 从 etcd / 环境变量里读出 Secrets
D Denial of Service 拒绝服务 把服务打瘫 一个 Pod 吃光节点资源,拖垮 kubelet
E Elevation of Privilege 提权 小权限变大权限 从普通 Pod 一路爬到 cluster-admin

STRIDE 不是让你背首字母,而是给你一副"防御眼镜":看到任何一个组件,就挨个问"它会不会被假冒、被篡改、被抵赖、被泄露、被打瘫、被提权"。

下面挑三个最要害的组件,戴上这副眼镜看看。

apiserver:守好正门这一关

apiserver 是唯一入口,所以它的认证 → 鉴权 → 准入三道关,任何一道松了都出大事:

  • Spoofing / Elevation:如果 RBAC(基于角色的权限控制)配得太松——比如给某个 ServiceAccount 绑了 cluster-admin——那这个身份被偷了就等于全盘皆输。最常见的翻车不是被"攻破",而是权限本来就给大了。
  • Information Disclosure:早期集群允许匿名访问--anonymous-auth=true)或开放未授权的只读端口,等于正门虚掩。
  • Denial of Service:apiserver 被海量请求打满,整个集群就"失联"了——所有组件都靠它协调。

etcd:这本账最值钱

官方审计里反复强调:etcd 的数据分类是 Critical。 因为它存着一切,尤其是 Secrets。

  • Information Disclosure:如果 etcd 没开加密(--encryption-provider-config),或者备份文件随手扔在某个没人管的对象存储里,那一份 etcd 快照 = 全城钥匙
  • Tampering:能直接写 etcd,就能凭空造出一个 cluster-admin 绑定,绕过 apiserver 所有的准入检查。
  • 所以 etcd 必须:只让 apiserver 访问、双向 TLS、静态加密、备份也要加密。

kubelet:那个"要命的接口"

kubelet 有个历史包袱:它自带一个 API(默认 10250 端口),能执行 exec(进容器执行命令)、读日志、列 Pod。官方审计里明确写了,kubelet 到 apiserver 之间默认可以不校验证书——配置不当时,这个端口若开放了未授权访问,攻击者就能直接命令 kubelet 去任意 Pod 里执行命令。

  • Elevation:拿下 kubelet ≈ 拿下这个节点上所有 Pod,包括别人的、可能权限更高的 Pod。
  • 防御要点:kubelet 开启鉴权与认证--authorization-mode=Webhook--anonymous-auth=false),关掉不必要的只读端口。

攻击树:从一个 Pod 爬到"城主"

威胁模型告诉你"哪里有风险",攻击树告诉你"攻击者会怎么把这些风险串成一条路"。

攻击树的读法很简单:根节点是攻击者的终极目标,往下是达成它的各种途径,一层层拆到具体手法。 我们设一个最经典的目标——拿到 cluster-admin(当上城主),看攻击者有哪些爬法:

flowchart TD
  GOAL["🎯 终极目标<br/>拿到 cluster-admin"]

  GOAL --> A["路径 A<br/>偷通行证"]
  GOAL --> B["路径 B<br/>从 Pod 里往外爬"]
  GOAL --> C["路径 C<br/>直捣档案馆 etcd"]
  GOAL --> D["路径 D<br/>供应链下毒"]

  A --> A1["翻到泄露的<br/>kubeconfig / Token<br/>(Git、日志、CI 变量)"]
  A --> A2["RBAC 配太松<br/>普通身份能建高权绑定"]

  B --> B1["攻破一个业务 Pod<br/>(Web 漏洞 / RCE)"]
  B1 --> B2["读挂载的<br/>ServiceAccount Token"]
  B1 --> B3["容器逃逸到宿主机<br/>(特权容器 / 挂了<br/>docker.sock / 内核漏洞)"]
  B2 --> B4["Token 权限过大<br/>直接调 apiserver 提权"]
  B3 --> B5["拿到节点<br/>= 拿到本节点所有 Pod"]
  B5 --> B6["攻击 kubelet 10250<br/>横向到其他 Pod"]

  C --> C1["etcd 未加认证 / 暴露"]
  C --> C2["拿到 etcd 快照备份"]
  C1 --> C3["直接读 / 写全部 Secrets"]
  C2 --> C3

  D --> D1["恶意镜像 / 依赖<br/>混进流水线"]
  D --> D2["被投毒的 Helm Chart<br/>/ Operator"]

  A1 --> WIN["👑 达成"]
  A2 --> WIN
  B4 --> WIN
  B6 --> WIN
  C3 --> WIN
  D1 --> WIN
  D2 --> WIN

  classDef goal fill:#7B241C,stroke:#511414,color:#fff;
  classDef win fill:#1E3A5F,stroke:#12263a,color:#fff;
  class GOAL goal;
  class WIN win;

真实世界里,最常走的是路径 B——攻击者很少一上来就能碰到 etcd 或偷到 admin 的 kubeconfig,更现实的是:先靠一个 Web 漏洞拿下一个不起眼的业务 Pod,然后:

  1. 读 Token:Pod 默认会把自己的 ServiceAccount Token 挂在 /var/run/secrets/kubernetes.io/serviceaccount/ 里。攻击者第一件事就是读它。
  2. 看这个 Token 能干啥:如果它权限过大(比如能 list secrets、能 create pod),那基本就赢了一半。
  3. 尝试逃逸:如果这个 Pod 是特权容器privileged: true)、或者挂载了宿主机的 docker.sock、或者挂了宿主机根目录,那攻击者能直接"逃"到宿主机,拿下整个节点。
  4. 横向移动:拿下一个节点后,通过 kubelet 去碰这个节点上别人的 Pod——其中说不定就有个权限大的。

你看,每一步都不算"高深漏洞",但一串小疏忽连起来,就是一条从 Pod 到城主的通天梯。这正是攻击树的价值:它逼你看清那条"链",而不是只盯着单个孤立的洞。


信任边界:真正该设防的那几道"墙"

把攻击树倒过来看,就知道钱该花在哪。K8s 里最关键的几道信任边界(trust boundary,即"跨过这条线就该重新验明正身"的地方):

flowchart LR
  EXT["外部世界<br/>用户 / 互联网"]
  POD["Pod 内部"]
  NODE["宿主机节点"]
  CP["控制平面"]

  EXT -.->|"墙①<br/>认证+RBAC"| CP
  POD -.->|"墙②<br/>容器隔离<br/>防逃逸"| NODE
  NODE -.->|"墙③<br/>kubelet 鉴权"| CP
  CP -.->|"墙④<br/>只有 apiserver<br/>能碰 etcd"| ETCD["etcd"]

  classDef b fill:#FCF3CF,stroke:#B7950B,color:#1a1a1a;
  class EXT,POD,NODE,CP,ETCD b;
  • 墙① 外部 → 控制平面:靠认证 + RBAC。这是最常被配松的一道。
  • 墙② Pod → 宿主机:靠容器隔离,防的就是"逃逸"。别用特权容器、别乱挂 hostPath 和 socket。
  • 墙③ 节点 → 控制平面:靠 kubelet 鉴权,防的是"拿下一个节点后进一步渗透控制平面"。
  • 墙④ 控制平面 → etcd:这本账只能让 apiserver 碰,且必须加密。

加固清单:照着做,堵住攻击树上的主要路径

不求一步到位,按"性价比"排了个序,从最容易翻车、最好补的开始:

第一优先级(几乎人人该做)

  • [ ] RBAC 最小权限:别乱给 cluster-admin;给 ServiceAccount 的权限按需最小化。定期审一遍谁绑了高权角色。
  • [ ] 默认不挂 Token:Pod 用不到 apiserver 就设 automountServiceAccountToken: false,直接砍掉攻击树路径 B 的第一步。
  • [ ] 禁掉特权容器:用准入控制(Pod Security Admission 的 restricted 级别)拦住 privilegedhostPathhostNetwork 这些危险配置。
  • [ ] 别把 kubeconfig / Token 写进 Git、日志、CI 明文变量——这是路径 A 最常见的入口。

第二优先级(进阶加固)

  • [ ] etcd 静态加密 + 双向 TLS + 备份加密:守住那本最值钱的账。
  • [ ] kubelet 关匿名、开鉴权--anonymous-auth=false--authorization-mode=Webhook
  • [ ] apiserver 关匿名访问、开审计日志:让攻击者"抵赖不掉"(对付 STRIDE 里的 R)。
  • [ ] NetworkPolicy 做网络分段:默认 K8s 里 Pod 之间是全通的,加策略限制横向移动。

第三优先级(纵深防御)

  • [ ] 镜像来源可信 + 签名验证:堵供应链路径 D。
  • [ ] 运行时检测(如 Falco 一类):Pod 里突然读 Token、起 shell、连外网,能当场告警。
  • [ ] 定期跑威胁建模复盘:集群在变,威胁模型也得跟着更新——参考 ControlPlane 和官方审计,隔段时间重画一次你自己的攻击树。

收个尾

安全这件事,最怕的不是"有漏洞",而是"不知道自己在守什么、对手会怎么打"。

威胁模型和攻击树的价值,就是把模糊的"不安全焦虑",变成一张能指着说话的图:这是我们的城,这是几道门,这是对手最可能爬的那条梯子,我们先把这几格堵上。

咱们做工程的,都懂一个道理——先想清楚,再动手,比闷头装十个工具管用。K8s 安全也是一样:画出你自己的那棵攻击树,你就知道下一块钱该花在哪把锁上了。

图难于其易,为大于其细。守一座城,从堵住那个不起眼的 Pod 开始。


参考资料:

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。 欢迎在我的个人网站 https://www.fanyamin.com 访问原文并评论。