1. 云原生不是“上云”,是攻击面的一次彻底重构
先讲一个在攻防演练里真实发生过的场景:演习开始第一天,传统边界防护设备连一条像样的Web攻击告警都没触发,可攻击队已经通过一个被遗忘了三年的K8s集群匿名接口拿到了内部某业务系统的命名空间权限。后续复盘发现,这个集群没有任何业务流量经过,连防火墙日志上都看不到它的存在。这件事让我意识到,云原生安全攻防和传统安全的思考起点完全不一样——我们在讨论的不再是“如何打穿一层边界”,而是“在一个没有明确边界的环境里,信任链哪里断了哪里就是入口”。
云原生环境的攻击面重构,本质上是因为运行单元变小、变化速度变快、依赖关系变复杂。传统架构下的攻击面相对清晰:对外暴露的Web端口、中间件服务、数据库、运维跳板机,这些资产是有限且相对稳定的。但在云原生体系里,一次部署可能同时拉起几十个Pod,每个Pod里有多个容器,每个容器都带有一整套镜像依赖和配置项,每套K8s集群都有一套RBAC权限模型和网络策略。资产不再是一个固定清单,而是动态生成的、带标签的一组对象,攻击面变成了一个持续变动的图。
这个范式转移带来的直接后果是:安全团队过去的防御经验——加固主机、封禁端口、部署WAF、定期漏扫——在云原生场景里仍然有用,但已经远远不够。攻击者不再需要和你的防火墙硬碰硬,他只需要在镜像仓库里找到一个可以被利用的旧版本依赖,或者在集群里找到一个配了cluster-admin权限却无人使用的ServiceAccount,就能完成从边缘接入点到核心控制面的跨越。整个过程中,你几乎看不到传统意义上的“攻击流量”。
所以我把云原生安全攻防的关键词总结为三个:信任链、配置漂移、默认能力。传统攻防拼的是漏洞利用技巧和WAF绕过能力,云原生攻防拼的是谁能更快发现信任关系中的裂缝,谁能利用“默认开启但没人检查”的能力完成跳板。这篇文章我会从攻击者视角把这套思路完整拆给你看,包括镜像供应链怎么介入、容器逃逸背后的底层杠杆、K8s集群权限滥用的典型链条,以及拿到集群后如何进一步摸到云平台账号。其中涉及的所有手法都来自授权攻防演练、靶场环境的验证,不是在鼓励你对生产系统做违规测试,而是希望通过理解攻击路径来把防御补丁打到正确的位置。
文中提到的大量配置、原理和步骤,我会尽量给出可直接落地的检查方法与部署建议。下面内容比较长,建议对着自己的集群边看边查,效果会比单纯读一遍好得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 攻击者的第一站:镜像供应链与启动链路的篡改点
很多人以为云原生攻击的第一步是扫描暴露的6443端口或者找Kubelet漏洞,但在实战里,大量攻击都是从“你已经信任的东西”里长出来的。镜像供应链是攻击者最愿意花成本去摸的一条路径,因为镜像一旦被污染,所有运行它的集群会同时中招,而且整个链条看起来完全正常。
2.1 基础镜像与依赖锁定:信任根一旦失守,全链路遭殃
先看一个最简单也最容易被忽略的点:你构建镜像用的基础镜像。很多团队Dockerfile第一行写着FROM node:16或者FROM python:3.9-slim,没有任何摘要(digest)锁定。这意味着下次构建时,你拉到的可能已经不是上次那个镜像了,只要上游镜像仓库里的同名标签被更新或篡改,你的新镜像就带着未知内容进入生产环境。攻击者在攻防演练中最喜欢做的就是先摸清目标团队用了哪些公共镜像源,然后判断这些源是否可污染。
还有一个很隐蔽的依赖层问题:包管理器锁定文件被篡改。Team在CI里跑npm install或者pip install时,如果package-lock.json、poetry.lock、requirements.txt这些文件能被人改动而不触发任何告警,那就等于给了攻击者一个免费的后门植入点。依赖源如果走的是私有镜像站,这个私有站的安全性就又成了一个信任节点。我在一次授权测试里见过一个客户,他们搭建了自己的Harbor仓库,但把npm都配置成直接走公网registry,并且没有做完整性校验。我在报告里给出的建议是:所有依赖下载必须走内部源,并开启锁文件的哈希校验,同时基础镜像必须用digest而不是tag来引用。道理大家一听都懂,可真到落地时能坚持按digest部署的团队非常少。
2.2 Dockerfile与Helm Chart中的高风险配置
镜像供应链的污染不只是“换源”这一种,Dockerfile本身的写法也会放大攻击面。最常见的高风险写法有三个。
第一个是直接以root用户运行容器。容器内虽然是隔离的用户空间,但root身份会显著放大逃逸后的权限收益。攻击者只要能找到内核漏洞或者错误挂载点,以root进入宿主机的概率和难度都会低很多。
第二个是把敏感凭据构建进镜像层。有些人图省事,构建时不走Secrets机制,直接在Dockerfile里ENV MYSQL_PASSWORD=xxxxx,或者把SSH私钥、云厂商AK/SK通过COPY命令打进去。镜像在仓库里放着还好,一旦被推送到公共仓库或者被集群内其他人拉取,凭据就完全暴露了。更麻烦的是镜像层是持久化的,就算你删掉了后面几层,底层那层里的密钥依然能被docker history翻出来。
第三个是没有把.kube/config这类文件排除在构建上下文之外。有时候开发机的~/.kube/config会被意外打包到镜像里,这个文件等同于集群管理员的钥匙串,拿到它的人可以直接访问集群的API Server。我在攻防演练里真的遇到过几次这种情况——本来只是拿一个Web应用的RCE,结果在容器里一翻发现/root/.kube/config静静躺在那里,直接省掉了后面所有逃逸和横向移动的工作。
Helm Chart的高风险配置同样值得关注。Chart作为K8s的打包分发机制,本身没有问题,但默认值文件里经常藏着一些在开发环境可用、生产环境却很危险的能力。比如默认打开了hostNetwork、privileged、hostPID,或者把serviceAccountName设成了default。以我的经验,这类问题不是Chart作者故意留后门,而是他们只关心“应用能不能跑通”,没有把安全基线当成一个交付条件。
2.3 镜像仓库权限与拉取链路的劫持空间
镜像仓库是整个供应链的心脏。很多企业内部Harbor只做了简单的账号验证,没有细分到项目和镜像级别的权限,结果就是任何一个开发人员都能拉取和推送任意镜像。攻击者只要拿到一个低权限账号,就能往业务镜像仓库里推送一个同名标签的恶意镜像,接下来要做的事就是等某个节点的kubelet重新拉取这个镜像。更有意思的是,很多集群的镜像拉取策略默认是IfNotPresent,如果节点上已经缓存了同名镜像但没做签名验证,新的或者被篡改的镜像未必会立刻被拉取,这时攻击者还可以主动触发Pod重建或者节点重置来加速这个过程。
针对镜像层面的防御,现在业内比较成熟的做法是引入镜像签名和策略引擎,例如使用Cosign做签名,用Kyverno或者OPA Gatekeeper在准入控制阶段强制校验签名。但这套体系在实际落地时经常因为性能顾虑、流程复杂度等原因被简化。在攻击视角看下来,供应链上任何一个“简化”的点,都是攻击者的机会。
3. 回到运行时:容器逃逸攻击面与Kubelet的信任滥用
镜像供应链说完,攻击的行程就推进到了运行时阶段。如果攻击者已经在一个Pod里拿到代码执行权限,接下来要判断的就是:到底需不需要逃逸? 这是很多攻击者在实战里会犯的方向性错误——一进来就想往宿主机逃,结果反而把自己暴露在安全监控下。而实际上,不少业务场景里,攻击者只需要利用K8s元数据服务或者内部网络就能完成核心目标,根本不需要逃逸。逃逸是手段,不是目的,这个逻辑在攻防演练里尤其重要。
3.1 容器逃逸的底层三类杠杆全解
如果确实需要逃逸,攻击者通常会在容器内做一轮快速检查,核心是看三个杠杆。
第一个杠杆是内核漏洞。容器与宿主机共享同一个内核,所以一旦内核出现可利用的提权或逃逸漏洞,所有在该节点上运行的容器都会受影响。这类漏洞靠的是攻击者平时的积累和快速匹配能力,通常是CVE-PoC的匹配过程。防御上比较有效的做法是把节点的内核升级节奏纳入安全运维流程,并且对高危CVE保持跟踪。
第二个杠杆是错误配置的能力。最典型的三个配置面是:容器以privileged模式运行,容器挂载了宿主机的/var/run/docker.sock,容器开启了CAP_SYS_ADMIN、CAP_SYS_PTRACE等危险能力。这三个配置之所以危险,是因为它们把容器与宿主机的隔离边界实质性消解了:privileged容器几乎拥有宿主机的全部设备访问权限;docker.sock挂载意味着容器内可以直接通过Docker API操作宿主机上的容器;CAP_SYS_ADMIN配合一些内核机制可以实现mount类型的逃逸。攻击者的检查逻辑是:先看cat /proc/1/status里的CapEff字段,再看有没有挂载socket文件,然后查一下当前用户可以调用哪些系统调用。防御方的操作则是反过来的:在Pod安全标准里禁用特权容器、约束能力集、不挂载宿主机敏感路径。
第三个杠杆是错误挂载。最常见的是把宿主机的/etc或者根目录挂载进容器。有些运维为了排查问题方便,会把宿主机的目录以读写方式挂载进一个临时Pod,排查完忘了清理,这个Pod就变成了一个暴露在集群里的管理后门。攻击者一旦发现/etc可以被读写,直接往宿主机/etc/crontab或者/root/.ssh/authorized_keys里写内容就完成了持久化。这类误挂载比privileged更隐蔽,因为它不需要任何内核漏洞,完全靠的是运维管理上的疏漏。
3.2 Kubelet端口与认证的边界:这里的问题比你想的普遍
从容器逃逸成功后,攻击者获得了节点上的操作系统权限,但还没获得集群的控制权。这里攻击者会立刻检查一件事:Kubelet这个组件的暴露面。Kubelet默认监听在节点上的10250端口,负责处理来自API Server的指令。这个端口如果开启了匿名认证或者使用了过弱的认证方式,攻击者就可以从节点本地甚至通过集群网络直接与Kubelet通信,执行Pod级别的操作。
更经典的路径是:利用Kubelet的/run/{namespace}/{pod}/{container}接口执行容器内命令,然后借助Kubelet的CSI或者Device Plugin机制在节点上创建特权Pod,从而实现从节点到集群的进一步跃迁。整个过程如果只看网络流量,会显得非常像正常的运维操作,因为Kubelet本身就是被设计来干这些事的。这也是为什么我在写防御建议时总强调,Kubelet的认证配置和网络策略是必须被严格审计的对象,它的暴露面往往比API Server更容易被忽视,但危害级别毫不逊色。
3.3 节点上的关键信息提取与持久化思路
拿到节点后,攻击者的第一反应不是去装rootkit,而是先做信息收集。因为K8s节点上通常会驻留大量对集群管理有价值的配置和数据:kubelet的配置文件(/var/lib/kubelet/config.yaml)、kubeconfig文件、云实例的元数据服务地址、挂载的存储卷等。拿到这些信息后,攻击者能在几分钟内判断这个节点在这个集群里的“社会地位”。
持久化层面,云原生环境的持久化思路和传统后门不一样:攻击者更倾向于创建一个高权限的Deployment/StatefulSet,让控制器帮他把后门Pod维持在运行状态。这样即使某个节点上的Pod被删除,控制器也会立刻重新调度一个。这种持久化方式更隐蔽,也更能适应K8s的声明式管理逻辑。
4. 集群控制层攻击:API Server、ServiceAccount与RBAC的越权链条
如果说逃逸是从容器到节点的跳板,那集群控制层攻击就是整个云原生攻防的高潮部分。攻击者的最终目标通常不是某一台服务器,而是整个集群的控制能力。K8s的控制面由API Server、etcd、Controller Manager和Scheduler构成,攻击者一旦能对API Server发起有效请求,就掌握了集群的调度和资源管理权限,相当于一个管理员拿着总控台钥匙进了机房。
4.1 ServiceAccount自动挂载与匿名请求
每个Pod在创建时,只要没有显式指定automountServiceAccountToken: false,K8s就会自动把该命名空间下的ServiceAccount令牌以只读方式挂载到容器内的/var/run/secrets/kubernetes.io/serviceaccount/token路径。这个设计初衷是方便Pod访问K8s API,但在安全视角下,它等于给每个容器发了一张集群的临时出入证。攻击者一旦拿到Pod的代码执行权限,就能直接读取这个令牌文件,然后以Pod对应ServiceAccount的身份向API Server发请求。
默认的ServiceAccount权限通常不高,但还是有相当多的集群把需要权限的Pod和不需要权限的Pod放在同一个命名空间,甚至共用同一个ServiceAccount。更需要注意的是匿名请求,如果集群配置了system:anonymous用户并授予了角色(比如常见的system:discovery被扩展成能读Pod的权限),那么攻击者连令牌都不用读,直接访问API Server的请求就能生效。这类问题在自建的、使用旧版本K8s的集群里出现概率更高。
4.2 从读Secrets到建Pod:一条标准的越权链路
拿到一个ServiceAccount令牌后,攻击者会按顺序试探几个高频动作,这也是我建议每个K8s管理员都该自查的路径。
首先是读Secrets。攻击者会尝试kubectl get secrets --all-namespaces,如果RBAC角色里包含了secrets资源的get/list权限,那这个集群的秘密基本就拱手让人了。很多角色的设计者没有意识到,get一个Secret和get一个ConfigMap完全是两个级别的风险,但RBAC规则里它们经常被放在同一个宽泛的“只读”策略下。
其次是创建Pod/Deployment。如果ServiceAccount具备create pods权限,攻击者可以直接创建一个特权容器,挂载宿主机的根目录,然后从容器内完成节点逃逸,进而控制整个节点。这比先找一个Web漏洞再利用的路径效率高得多。更关键的是,这种操作在API Server的审计日志里可能只表现为一次普通的资源创建事件,如果团队没有建立异常行为基线,根本不会被发现。
最后是横向扩散。如果一个ServiceAccount可以读取所在命名空间的Service列表,攻击者就能把内网横向移动的路径拼接出来。许多微服务之间的信任关系是“同一个命名空间就彼此信任”,这让攻击者能在命名空间内向其他服务发起代理跳板的请求。
4.3 etcd:比API Server更值钱的数据枢纽
控制层里还有一个经常被忽略的顶级目标——etcd。etcd保存了集群的所有对象定义,包括Secret、ConfigMap、Role、ServiceAccount令牌等敏感数据。如果攻击者能直接读取etcd(通常需要节点访问权或者某个高权限Pod的协助),那么整个集群的管理配置就在他眼前一览无余。
从攻击链路来看,etcd通常监听在2379端口。如果该端口暴露在集群网络之外或者未启用TLS客户端认证,攻击者可以直接通过etcd客户端读取所有数据。即便启用了TLS,只要攻击者获取了节点的证书文件和etcd的CA,同样可以完成认证。这也是为什么在生产环境里,etcd一般被要求部署在独立节点上,并且关闭外部访问。
5. 云原生时代特有的“最后一跳”:从集群到云账号
在传统攻防里,拿到一台服务器root权限,攻击基本就接近尾声。但在云原生场景下,拿到集群和节点权限往往只是中间态。因为K8s集群几乎都是构建在云平台上的,集群和底层云账号之间存在着一条隐形的信任链,攻击者会想尽办法跨越这条链,把战果从集群放大到整个云账号。
5.1 云Metadata服务与本机角色的错配
几乎所有云平台都为实例提供了元数据服务,地址通常是169.254.169.254。攻击者在节点上通过curl http://169.254.169.254/latest/meta-data/iam/security-credentials/就能获取当前实例的角色名,再进一步读取指定角色的临时凭据。这意味着,如果这个节点绑定的实例角色权限过大(比如具备sts:AssumeRole或者某些存储桶的读写权限),攻击者就拿到了云账号层面的临时钥匙。
这条路径在攻防演练里几乎每次都能奏效,因为很多团队为ECS配置角色时遵循的是“能用就行”的原则,节点上的实例角色往往同时具备多个服务的高权限。我在实际加固建议里一般会强调三条:一是节点实例角色必须单独创建,不和其他业务共享;二是对Metadata服务的访问要做网络层面的限制;三是定时审计实例角色所绑定的策略,缩小权限范围。
5.2 Secret真的是“秘密”吗?密钥泄露的实际通道
K8s的Secret对象在大多数人的认知里是安全的,但攻击者的视角完全不同。Secret只是做了Base64编码,并没有加密。它真正的安全依赖两点:etcd的加密存储配置,以及RBAC对secrets资源的访问控制。在真实的集群中,两者同时做得好的比例并不高。
攻击者获取Secret的渠道通常有三个:一是通过容器内挂载的ServiceAccount令牌读取API Server中的Secret对象;二是直接查看etcd中的数据;三是从镜像构建过程的环境变量、日志文件、CI/CD产物中翻出Secret明文。在攻防演练中,第三种渠道的命中率反而更高,因为它绕过了K8s本身的权限体系,进的是研发流程和配置文件这些更容易被忽视的环节。
5.3 从CI/CD到生产集群:一条被反复利用的信任通道
CI/CD流水线是云原生攻击里最被低估的横向移动通道。现在很多团队实现的是“代码提交后自动构建、自动测试、自动部署”,整个链路中涉及多个系统的凭据:代码仓库的token、镜像仓库的账号密码、K8s集群的kubeconfig。攻击者只要能攻破其中一个环节,就能顺着流水线向下游生产集群渗透。
典型路径是:攻击者先找到一个暴露的GitLab或Jenkins服务,通过弱口令或未授权访问进入CI系统,读取流水线配置里保存的K8s部署凭据,然后直接向生产集群发起部署操作。更隐蔽的做法是篡改流水线的构建脚本,加入一个恶意步骤,在正常部署流程里悄悄植入后门镜像。这种攻击从审计日志上看起来和正常发布完全一样,非常难被发现。
6. 从攻击链路反推:四个必须做的加固动作
前面花了大量篇幅讲攻击者视角,目的是把链路彻底铺开,接下来要从这些攻击路径倒推防御策略。我不会列一份八股式的安全清单,只讲四个从攻击链路里直接反推出来的、性价比最高的加固方向。
6.1 关掉默认能力:验证过才保留,没用的全部关掉
从镜像供应链到K8s运行时,很多攻击路径之所以走得通,核心原因是“默认能力”没有被收敛。K8s和容器技术为了易用性,默认开启了很多能力:默认自动挂载ServiceAccount、默认允许特权容器(在未设置Pod Security Admission的环境)、默认允许匿名访问部分API、默认信任同一命名空间内的服务。攻击者打的就是这些“没人检查过但默认打开”的口子。
加固动作是:一是在命名空间级别启用Pod Security Admission并设为restricted或至少baseline;二是在Pod模板里显式设置automountServiceAccountToken: false,只有真正需要访问API Server的Pod才开启;三是关闭匿名认证或严格限制匿名用户的RBAC权限;四是使用准入控制器强制校验镜像签名,把没有签名的镜像挡在集群外。
6.2 按信任域拆分:别再让“能访问”等于“能控制”
云原生环境下,网络策略(NetworkPolicy)是实现信任域拆分最关键的工具,但很多集群根本没有启用。没有NetworkPolicy时,集群内所有Pod默认可以互相访问,攻击者拿到任意一个Pod后,就能对整个集群内部展开扫描。启用NetworkPolicy后,可以做到“只有流量白名单内的Pod才能互相通信”,攻击者的横向移动成本会大幅上升。
关于权责划分,跨命名空间的访问也必须有红线。如果业务A不需要访问业务B,那它们之间就不该有网络通路,更不该共享ServiceAccount。攻击者横向扩散的路径,往往就是团队为了方便而开放的过度连接。
6.3 日志审计不能只看API Server,节点侧与网络侧一样重要
很多安全团队在云原生环境里把大量精力放在API Server的审计日志上,这当然是对的,但攻击链里的多个关键环节不会触发API Server审计。比如攻击者在容器内读取镜像文件、查看环境变量、扫描内部网络,这些行为只发生在节点或者容器runtime层面,API Server根本看不到。所以节点侧的审计(syscall级别的检测,如Falco)和网络侧的流量记录必须同步建立起来。Falco可以基于系统调用规则捕捉“在一个容器内发现了读取宿主机敏感路径”这类异常行为,这类规则对实战攻防中高频出现的逃逸前置动作非常有效。
6.4 常态化攻防验证:别等演练结束才补洞
最后一条是安全团队最容易忽略的:防御方案如果没有经过攻击链路的验证,永远不知道哪里会断。我在给客户做攻防验证时,习惯先把上面提到的攻击链路完整走一遍,分别验证供应链、逃逸、RBAC、Secret、Metadata这几层,每一层都记录“攻破所需条件”和“检测可见性”。然后逐项对照,看防御措施是否堵住了攻击入口、监控规则是否覆盖了攻击步骤。这套流程不是一锤子买卖,集群的变更频率非常高,每上线一个新的工作负载或者每改一次RBAC,攻击面都可能发生变化。建议至少每个季度做一次针对性的攻防验证,并且把验证结果回填到安全基线里。
写在最后的一点实际体会
这篇文章从镜像供应链、容器逃逸、K8s控制面,一直写到了云账号和CI/CD链路,基本上把云原生攻击者视角下最容易走的几条路都走了一遍。我在带攻防项目时经常跟队员说一句话:攻击者不需要同时打破所有防线,他只需要找到某条没人维护的信任链,顺着它一路走到底。云原生环境的复杂度让信任链变得非常多,每条链上的节点又经常处于“能跑但没人管”的状态,这正是问题的根源。
站在防御者的位置上,我认为最高性价比的事不是追求安全产品的堆量,而是把默认能力收敛好、把权限边界理清楚、把关键行为检测建起来,然后定期用攻防验证去检验这些措施是否真的有效。这几件事每一件做起来都不算难,难的是长期坚持和持续迭代。希望这篇内容能给你一些可以直接借鉴的思路,也欢迎在实践中遇到具体问题后,带着场景来继续讨论。
