我讲一个场景,各位应该不陌生:一次授权攻防演练里,业务方把整套微服务跑在K8s上,安全团队花了大价钱买了WAF、HIDS、容器安全平台,告警规则配了几百条。结果红队从一个不怎么起眼的Web漏洞打进Pod,容器逃逸之后在宿主机上蹲了整整三天,期间还通过K8s控制面把自己“复活”了两次,最后直到演练结束,防守方都没发现主机已经被长期驻留。
这是我复盘了多个攻防项目之后最深的体会:在K8s环境里,容器逃逸往往不是终点,而是起点。攻击者真正想要的,是在主机上站稳脚跟,并且让所有告警都“看不见他”。这篇内容不教大家去做坏事,而是从红队攻击路径的视角,把逃逸之后“怎么藏、怎么活、怎么不被发现”的完整逻辑拆开来讲,让做防守的同学清楚攻击者每一步在想什么、踩在哪里、又在躲什么。只有知道敌人在暗处如何行动,防守方的告警规则才不是摆设。适合K8s运维、安全运营、蓝队工程师在授权攻防项目中作为参考。
1. 为什么K8s会成为持久化攻击的“天然跳板”
1.1 从一次授权攻防演练说起的真实案例
有一次我们做红队模拟,初始入口是一个业务系统的RCE漏洞,代码执行发生在Pod里。当时所有人的注意力都在“如何从Pod逃逸到宿主机”,但真正让我意外的不是逃逸本身,而是逃逸之后防守方的反应。
逃逸成功后,我在宿主机上做了常规的权限维持操作,然后故意留下了一些比较明显的痕迹,比如修改了/etc/crontab、新建了一个伪装成系统服务的systemd unit。结果连续两天,防守方没有任何动静。
后来复盘时发现,防守方确实收到了告警,但告警到了SOC平台之后被“降噪”规则直接吞掉了。因为当时容器逃逸产生的行为特征,和某些正常运维操作长得很像——毕竟很多运维同学也喜欢手动改crontab、手动创建systemd服务。这类告警每天产生几百条,安全人员已经看得麻木了。
这个案例说明一个很关键的问题:K8s环境下的持久化,难度不在于“能不能实现”,而在于“能不能长得像正常运维”。攻击者真正花心思的地方,是让自己的一切行为淹没在日常操作的噪声里。
1.2 容器环境的隐蔽优势:为什么攻击者偏爱这里
传统物理机或虚拟机上的持久化,攻击者通常要考虑杀软、EDR、主机加固策略。但在K8s环境里,这些防线被大大削弱了。
原因有三层:
第一,容器本身就是“一次性”的。很多组织默认Pod会被频繁重建,所以安全团队对容器内的异常进程、异常文件没那么敏感,总觉得“反正是无状态的,重启就干净了”。这个认知恰恰给了攻击者空间——他可以在容器里放一些临时脚本,利用工作负载的自动伸缩和调度,让恶意行为分散在多个Pod里执行,每次都是“新容器”,每次都不像长期驻留。
第二,K8s控制面的权限模型非常复杂。ServiceAccount、RBAC、NetworkPolicy、PodSecurityPolicy这些机制一旦配置疏漏,攻击者拿到的权限往往超出预期。很多集群管理员对Pod内的应用权限没有收敛,导致攻击者从容器逃逸后,甚至能直接访问K8s API Server,创建高权限Workload。
第三,多云和混合云架构让主机的归属变得模糊。很多团队对“这个节点属于哪个集群、上面跑了哪些业务”都说不清楚,就更别提对节点上的异常行为做精确判断了。
简单说,K8s把传统的“一台主机一个边界”拆成了“一堆容器N个边界”,边界变多,盲区变多,攻击者找到的可乘之机自然也就多了。
1.3 告警为什么总在逃逸瞬间“失明”
我在很多项目里发现一个共性现象:容器逃逸的过程本身其实是会触发告警的,比如内核态异常、敏感挂载点访问、特权容器启动,这些都有现成的检测规则。但告警发出来之后,往往并没有形成有效响应。
问题出在两个环节。
第一个环节是告警“只报不查”。告警事件只告诉运营人员“有异常”,但没有上下文——它不会告诉你这个异常进程是从哪个Pod里出来的,不会告诉你它和之前的什么操作有关联,也不会告诉你这个进程接下来访问了哪些资源。运营人员看到一条孤零零的告警,根本无法判断严重程度。
第二个环节是“告警洪峰”淹没“关键告警”。在攻防演练期间,防守方往往会在同一时间收到大量误报和低优先级告警,比如容器镜像扫描的漏洞list、Pod频繁重启的warning事件。这些噪声占据了运营人员的注意力,真正关键的逃逸告警反而排在后面,等人看到时,攻击者早就完成了持久化操作。
所以从攻击者的视角来看,逃逸之后的黄金时间窗口非常宝贵——告警可能已经发出,但真正的响应还没开始。他要做的,就是在这个窗口内快速收集信息、落地持久化、清理痕迹,然后“隐身”。
2. 逃逸落地之后:攻击者第一时间的侦察动作
2.1 侦察优先级:内核、权限、挂载点与网络
我见过不少新手在拿到容器权限后,第一件事就是急着传工具、弹Shell,结果很快被防守方抓住。实际上,一个有经验的红队操作者,逃逸成功后会先花几分钟做侦察,搞清楚自己落到了一台什么样的主机上。
按优先级排列,攻击者至少要看这四类信息:
| 侦察对象 | 典型命令/方法 | 攻击者关心的核心问题 |
|---|---|---|
| 内核版本与内核模块 | uname -a, cat /proc/version | 是否存在已知的内核逃逸漏洞,宿主机内核是否较老 |
| 进程与容器身份 | cat /proc/1/cgroup, hostname, cat /etc/hostname | 自己到底在容器里还是宿主机上,是否为特权模式 |
| 挂载点与Docker Socket | mount, ls -la /var/run/docker.sock, cat /proc/mounts | 是否有敏感目录挂载,是否能直接控制宿主机Docker守护进程 |
| 网络与云元数据 | ip addr, cat /proc/net/route, curl云厂商元数据地址 | 主机的网络位置、是否在云上、能否访问内部元数据服务 |
这里我多说一句,攻击者侦察时用的基本都是系统自带命令,不会触发“恶意工具落地”的检测规则。很多防守方只盯可疑文件、可疑进程名,却忽略了“正常命令的异常组合”才是更危险的信号。
2.2 如何判断自己有没有资格触碰K8s API
主机站稳后,攻击者通常会尝试和K8s API Server建立连接。这一步能不能成,取决于容器和节点有没有可用凭据。
最值钱的是这三种:
第一种是容器内置的ServiceAccount。每个Pod在创建时都会自动挂载一个服务账户Token,路径在/var/run/secrets/kubernetes.io/serviceaccount/token。如果这个ServiceAccount被赋予了较高的RBAC权限,攻击者不需要任何额外操作,就能直接用这个Token调K8s API。
第二种是节点上的kubelet证书和Admin Kubeconfig。如果攻击者拿到了节点的root权限,而节点的/etc/kubernetes目录权限配置不当,就能读到kubelet的客户端证书,甚至直接找到admin.conf。这类凭据的权限通常比ServiceAccount高得多。
第三种是云厂商托管的集群凭据。不少团队会把K8s集群的Kubeconfig文件、云访问密钥保存在节点的配置目录、CI脚本或环境变量里。攻击者只要在主机的Shell配置或容器环境变量里翻一翻,经常能有意想不到的收获。
判断自己“有没有资格”碰K8s API,攻击者会先跑一下kubectl auth can-i --list之类的检查,确认自己能对哪些资源做什么操作。这一步非常安静,不产生恶意流量,但对后续的持久化路径选择至关重要。
2.3 从Pod到集群的权限扩展路径
拿到K8s API访问权限之后,攻击者就从一个“单点容器失陷”升级到了“控制面失陷”的层面。这时候他的权限扩展路径大体是这样的:
如果ServiceAccount的RBAC允许创建Deployment或DaemonSet,攻击者就可以直接部署一个新的恶意工作负载,指定它调度到某个目标节点上,并且挂载宿主机的根目录。这种操作在K8s里叫“恶意工作负载”,是攻击者最喜欢的手法,因为一切操作都通过API完成,传统的EDR和主机Agent根本看不到这个“容器”在做什么。
如果RBAC权限不够,攻击者可以尝试创建高权限Pod,利用HostPID、HostNetwork、特权模式等配置来扩大影响面。纵使无法直接控制整个集群,也至少能在节点层面获得一个高权限容器,持续探测其他通道。
更隐蔽的做法是修改现有工作负载的定义。攻击者不会新建一个看起来就很可疑的Deployment,而是在已有的名字、已有的镜像基础之上,注入一个sidecar容器或者修改启动命令。这样,云原生安全平台虽然能看到“工作负载有变化”,但很难判断变化是否是版本发布导致的。
3. 三类“低噪音”持久化手法:攻击者的真实选择
3.1 文件层:无文件落地与隐藏路径复用
传统持久化喜欢写文件,比如往/usr/bin下面丢一个后门文件。但在K8s和容器场景里,写文件的风险很高,因为文件扫描、镜像一致性校验都能发现问题。所以攻击者更偏爱“无文件落地”的方式。
一种常见做法是使用memfd_create直接在内存里创建匿名文件,把恶意代码映射到内存中执行,磁盘上不留痕迹。很多容器镜像本身是只读文件系统,攻击者也会刻意避免写磁盘,把脚本和二进制压缩包放在内存临时文件系统里。
另一种做法是“隐藏路径复用”。在宿主机上,/tmp、/var/tmp、/dev/shm这类目录通常不会被常规文件扫描重点关注。攻击者会把这些目录改成看似正常的名字,比如/tmp/.X11-unix/、/var/tmp/.update-manager/,然后在里面存放持久化脚本。有些攻击者甚至会把恶意文件隐藏在原系统文件的扩展属性或日志文件的空洞中,但这些手法对磁盘空间和清理时机要求太高,不是首选。
我特别提醒防守同学一句:别只盯着进程和网络,容器层和宿主机层那些“几乎没人看的目录”,才是攻击者藏东西的首选。
3.2 进程层:名字、资源与调度器的伪装
进程是HIDS检测的核心对象,攻击者必须在进程层面做好伪装。
最粗暴的方式是起一个和系统进程完全同名的进程,比如把后门进程命名为systemd、kubelet、containerd。这一招在传统主机上挺有用,但在容器环境里很容易露出马脚,因为HIDS一旦发现同名进程数量异常或进程路径不对,就会告警。
更高级的做法是“资源限制伪装”。攻击者会给自己植入的后门进程设置合理的环境变量和资源限制,让它的CPU、内存占用看起来像某个正常业务服务的指标。比如原本业务服务占用内存2GB,后门进程就把自己的内存占用控制在200MB以内,避免触发资源使用的异常告警。
还有一类手法是与正常进程“同生共死”。攻击者会把自己的恶意代码注入到业务进程运行时环境中,或者利用LD_PRELOAD加载恶意共享库,让恶意代码“寄生”在正常进程里。这样即使SOC拿着进程列表一一排查,看到的还是那个合法的Java进程或Nginx进程,很难发现内部已经被劫持。
3.3 控制面层:借K8s控制器实现自动复活
这部分是云原生环境下最有特色的持久化手法,也是很多传统安全人员容易忽略的地方。
攻击者如果拿到了K8s API的写权限,他不会只依赖主机上的cron或systemd,因为一旦Pod被重新调度、节点被重置,宿主机上的持久化就断了。他会直接把“驻留点”部署到K8s控制面上,让集群自己帮他维持存在感。
思路是这样的:
创建一个Deployment或DaemonSet,让它始终维持一定数量的副本数。即使某个Pod被删除,控制器会自动新建一个Pod来补位。这个“自动复活”机制是K8s的正常行为,从事件流上看只是普通的Pod重建,如果Pod名称带上了业务的随机后缀,安全团队很难把它和攻击联系起来。
更隐蔽的变体是利用CronJob。攻击者定时创建新的Job来执行一轮侦察或维持通信的任务,任务执行完Job就结束,看起来像是一次性任务,不会在集群里留下常驻进程。配上比较长的间隔,比如每隔23小时执行一次,这类规律性不强的小任务很难被时序检测发现。
4. 在主机上站稳脚跟:隐匿驻留的时间艺术家
4.1 systemd与cron:传统持久化在云原生下的变体
云原生环境底层也是Linux主机,所以传统的主机持久化手段依然有效,只是需要调整姿势。
systemd服务是攻击者比较喜欢的方式。攻击者会在/etc/systemd/system/下面新建一个服务文件,服务名通常模仿现有服务,比如systemd-networkd-update.service。但单纯新建服务还是太明显,比较好的伪装是修改已有服务的ExecStart,把恶意命令追加进去,让它在系统服务重启时被一并启动。
cron是另一个经典手段,但在容器场景里要注意:很多容器镜像默认不安装cron,宿主机上则一般都有。攻击者会把恶意脚本写到/var/spool/cron/root或其他用户的crontab里,配上随机化的延时,让任务看起来像是正常的系统维护。因为没有在init脚本和常见启动路径中留下痕迹,这类定时任务往往在主机上存活很久。
还有一个细节值得防守方注意:攻击者会花时间把文件的时间戳改成和旁边文件一致。如果你在排查时发现某个cron关联的脚本时间戳和/etc目录里其他文件完全一样,反而可能是刻意对齐的结果。
4.2 通信形态的隐匿:复用云平台与K8s的合法通道
持久化只解决“活下来”的问题,还要解决“怎么通信”。如果恶意程序在主机上拼命外连陌生IP,再隐蔽的持久化也会立刻暴露。所以攻击者会想方设法把自己的流量混进合法通道。
在K8s环境里,最大的合法通道就是K8s API Server本身。如果攻击者能拿到一个有权限的ServiceAccount Token,他完全不需要反向Shell,直接通过HTTP请求以一定时间间隔向API Server查询资源状态即可。这种流量的目的地是内网已知IP,而且是标准的HTTPS请求,很多安全设备都会直接放行。
云平台上还有一类合法通道是对象存储和消息队列。攻击者可以把恶意脚本需要的数据上传到云上的对象存储,然后用畸形的请求头或特定路径来传递控制指令。这类流量在云环境里太常见了,SOC平台不会对“访问对象存储”这个动作单独告警。
我见过比较极端的做法是攻击者把恶意域名解析到CDN节点上,再通过CDN回源到自己的服务器,让外联流量看起来只是用户访问了某个云厂商的静态资源域名。这类通信从网络层看很难和正常业务流量区分开。
4.3 时间与频率控制:让告警降噪帮攻击者“降噪”
很多告警规则是基于频率和周期设计的——某个行为如果在一段时间内频繁发生,就会被标记为异常。攻击者很清楚这一点,所以会刻意控制自己的行为频率。
比如,需要每隔几分钟执行一次的信息收集,攻击者会改成每小时执行一次,甚至每天只在凌晨执行几次。这样在SOC的时间线视图里,这些行为稀疏得像无关紧要的杂音,很难聚合成一条完整的攻击链。
再比如,文件访问类的行为,攻击者会把本来可以在几秒内完成的批量文件遍历,拆成几百个小步骤,分散在一整天里完成。这样IDS和审计系统看到的单次事件都不超过阈值,不会触发聚合告警。
做防守的同学要理解一件事:攻击者永远比你更有耐心。如果告警规则只盯着“高频”“集中”“大流量”,那么一个足够慢的攻击就能毫发无伤地从你眼皮底下经过。这也是为什么我后面会强调,必须把“时间维度”纳入行为建模,而不仅仅是依赖单点特征。
5. 从攻击视角复盘点:告警系统最常见的五处破绽
5.1 特征检测的失效:未知威胁不会遵循签名
很多团队建的告警规则还是老一套:匹配恶意域名、匹配已知漏洞利用特征、匹配文件hash。这套逻辑在传统Web攻击里还有效,但在K8s环境的攻击链里,效果非常有限。
原因很简单:攻击者使用的工具很多是系统自带的,比如curl、wget、kubectl、python,甚至直接使用K8s API的SDK。没有恶意文件名,没有恶意hash,没有明显的漏洞利用特征,所有操作看起来都是“正常运维能做的事”。特征规则库对这种行为基本无能为力。
我并不是说特征检测完全没用,而是它的定位应该是“兜底”,不应该是“主力”。如果防守方主要依赖特征匹配,那遇到一次真正有经验的攻击,可能全程都不会收到有价值的告警。
5.2 时序关联的空窗:逃逸与驻留之间的检测断层
更麻烦的问题在于告警之间缺乏关联。即使防守方同时看到了“容器逃逸”告警和“宿主机出现新systemd服务”告警,如果这两条告警没有在时间上、主机上、账号上被关联起来,运营人员依然无法意识到这是同一次攻击的两步。
攻击者最擅长的就是利用这个空窗——他逃逸后不会马上做持久化,而是先等一等,让逃逸告警的注意力过去,或者先做点低风险操作,给安全团队制造“这可能是误报”的错觉。等运营人员把告警关闭,再在几个小时后去落地驻留脚本。
所以,时序关联能力比单点告警重要得多。发现逃逸事件后,至少应该在24小时内持续关注该节点的异常行为,而不是把逃逸当作一个孤立事件来处理。
5.3 日志采集的盲区:被刻意清空的取证线索
攻击者站稳脚跟之后,通常会做一件事:清理历史命令记录和执行痕迹。他会把~/.bash_history删掉,或者干脆设置一个不记录历史的Shell环境。更彻底的是查看并篡改/var/log下的审计日志和系统日志,把和自己相关的行删除。
这给防守方提出了一个硬性要求:日志必须集中外发。如果日志只存在本地,攻击者拿到root权限后可以随意抹除,事后连溯源都做不了。很多K8s集群的容器日志默认只存在节点本地,Pod一旦被删除,日志文件随之被回收。安全团队如果依赖容器日志做检测,等于主动把证据交给攻击者销毁。
真正有效的做法是,至少把K8s审计日志、kubelet日志、关键节点的主机日志实时转发到独立的日志中心,并且对日志写入做权限隔离,让容器里的进程没有权限删除远端日志。
5.4 告警降噪的副作用:高基数误报掩盖真实攻击
我之前提到过的组织里的“告警降噪”问题,这里再展开讲一下。不少团队为了让告警数量看起来“好看”,配置了大量规则把某些事件直接抑制掉,或者把多个事件聚合成一条告警。
常见的降噪规则包括“同一账号重复触发N次后静默”“同一IP触发规则后24小时内不再告警”“仅严重级别为高危才推送到群”等等。这些规则在削减噪声的同时,也给攻击者提供了“豁免”机会。
攻击者可以在真正动手前故意触发几次低危告警,把某个IP、某个账号或某个文件路径“喂”进降噪名单里。一旦降噪规则生效,他再利用同一个入口做高危险动作,告警就被自动吞掉了。
做降噪不是不行,但绝不能做“一刀切”式的静默。推荐的做法是降噪只合并重复事件,不丢弃不同维度的告警;同一来源的告警可以降频推送,但要保留在完整事件流里供事后追溯。
5.5 云原生安全平台自身的盲区
很多组织采购了容器安全平台,以为装上就万事大吉。实际从攻击者的角度来看,这类平台有几个很常见的盲区。
第一,镜像扫描只负责“进集群前”那道关。攻击者如果在运行时往容器里写入恶意脚本,扫描器不会每次都重新扫描运行中的容器文件系统。
第二,运行时防护策略如果配得太严,会导致大量业务容器被误杀,所以很多团队会把策略调成“仅监控”。也就是说,平台看到了异常,但不会阻断,只会记录。攻击者只要确认当前集群处于“监控模式”,就可以放心大胆地执行后续操作。
第三,平台自身的Agent在容器里通常以Sidecar形式运行,权限受限,有时候连读取宿主机关键目录都做不到。攻击者只要避开Agent可见的进程和文件,就能保持在盲区里活动。
6. 防守方反制:在攻击者必走的路上埋下六颗雷
6.1 强制开启的审计对象清单
说了这么多攻击者的思路,最后落到防守方怎么干。第一步不是加规则,而是把该看的日志全部看全。我建议至少覆盖以下审计对象:
- Kubernetes API Server审计日志:记录所有对API的请求,包括谁在什么时候创建了什么资源。这是发现恶意工作负载的基石。
- kubelet日志:连接节点和容器的桥梁,容器逃逸前后kubelet会留下大量有价值的线索。
- 关键节点的安全日志(auth.log、syslog):覆盖SSH登录、sudo执行、cron运行等行为。
- Docker/containerd的event日志:记录容器的创建、销毁、挂载等操作。
- 云平台的操作审计(CloudTrail/ActionTrail):记录所有云上控制台和API的调用。
这几份日志必须实时外传到独立存储,并且设置至少180天的保留周期。只要日志是完整的,攻击者抹掉本地痕迹也没用。
6.2 行为基线与异常建模:告别裸奔式特征告警
特征规则要保留,但行为基线才是K8s环境检测的核心。
具体做法是先用一段时间记录集群里的正常行为,再基于这些数据建立基线。例如,每个命名空间下通常有哪些工作负载、每个工作负载的进程列表是什么、节点上什么时候会批量拉取镜像、哪个账号经常在什么时间调用API。基线建立后,凡是不符合基线的行为,无论有没有匹配恶意特征,都值得关注。
举个例子:某个Deployment平时从来不访问云元数据服务,突然某个Pod开始频繁请求元数据接口,哪怕流量不大,也应该引发告警。再比如,某个ServiceAccount平时只读Pod,突然尝试创建Deployment,这就是明显的权限异常。
行为基线不需要太复杂的算法,从CPU、内存、进程、网络连接、API调用几个维度做统计建模就能覆盖80%的异常。重点是把“偏离正常”作为告警触发条件,而不是只依赖已知威胁情报。
6.3 运行时阻断与最小权限的落地优先级
光检测不阻断,攻击者照样能把日子过下去。运行时阻断策略应该优先覆盖这几个高风险动作:
| 防护动作 | 推荐策略 | 优先级 |
|---|---|---|
| 禁止特权容器 | PodSecurity Admission启用restricted级别 | 最高 |
| 禁止挂载Docker Socket | 在Pod创建阶段校验hostPath | 最高 |
| 禁止容器内新增高权限systemd服务 | 运行时检测主机侧unit文件变化 | 高 |
| 禁止容器访问云元数据 | NetworkPolicy阻断169.254.x.x | 高 |
| 禁止非白名单进程写入cron | 主机Agent监控crontab文件变化 | 中 |
同时,RBAC权限要做到最小化。每个ServiceAccount只授权它真正需要访问的资源,Pod的automountServiceAccountToken尽量设为false,从源头上减少攻击者拿到K8s API凭据的可能性。
一个比较容易忽略的点是,PodSecurityPolicy已经废弃,现在应该用Pod Security Admission或OPA/Gatekeeper这类策略引擎来强制约束。策略最好在CI/CD阶段就嵌入,而不是只靠集群运行时拦截。
6.4 定期“敌情演练”:用攻击视角检验防御体系
再完善的告警规则,不经过实战检验也无法证明有效。我非常建议每个K8s集群的维护团队定期做一次“敌情演练”,也就是红队蓝队对抗。
演练不需要搞得很复杂,可以只是内部模拟几个典型场景:模拟容器逃逸后新建systemd服务、模拟攻击者用ServiceAccount创建恶意Deployment、模拟攻击者清理本地日志。看这些动作能否在可接受的时间内被检测到、响应流程是否能跑通。
每次演练结束,把防守方的盲区和响应延迟记录成问题清单,逐条修复。这样反复几轮,告警体系的实战能力会明显提升,也远比临到攻防演练时手忙脚乱要好。
我在多支团队里推行过这种演练方式,最大的收获是:告警规则不是上线就完事,而是要在一次次攻防中不断校正。很多规则配置之初看起来合理,但只在理论推演里有效,真正模拟攻击时才发现日志采集缺了一环。
写在最后:攻防视角是防守方最好的老师
在我做攻防项目的这些年里,一个反复验证的经验是:防守方如果不了解攻击者的真实路径,配置再多告警规则也像是在黑屋子里找猫。与其买更多工具,不如先把K8s环境里的这条攻击链从头到尾走一遍——想清楚数据库和代码里最值钱的资产会放在哪、攻击者最可能从哪里进来、他进来之后会碰哪些东西、又会在哪里长期躲藏。
这篇文章里的内容,全部来自授权攻防演练和真实安全事件复盘后的总结。任何技术都像一把工具,用对了方向才能创造价值。希望读到这里的同学,尤其是正在负责K8s集群安全的同行,能把文章里的攻击路径当作“敌情报告”来研究,顺着这些思路去检查自己的审计日志、RBAC权限和运行时策略。防守和攻击从来不是孤立的事情,真正有效的云原生安全体系,永远是站在对方的角度思考出来的。