ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes DaemonSet 后门:持久化攻击机制与防御指南

Kubernetes DaemonSet 后门:持久化攻击机制与防御指南 做 Kubernetes 安全的人都会有同一个感觉Deployment 留意得到StatefulSet 也留意得到但真正让人头疼的是 DaemonSet。这个东西设计初衷特别好——日志采集、监控 agent、网络插件每个节点一份节点加入自动拉起。可一旦被攻击者利用它就成了最理想的“集群级持久化后门”。你以为删掉了可疑 Pod几秒之后它又自己起来了你以为某个节点干净了新节点加入集群又会自动被种上同一套东西。这不是概念演示而是真实攻防里高频出现、防守方又特别容易忽略的一种打法。我想围绕“Kubernetes 集群持久化”这件事完整讲清楚 DaemonSet 为什么容易被选中、恶意 DaemonSet 是怎么被构建和部署的、防守方应该从哪些维度去发现、拦截和清理。适合刚接触容器安全的同学建立威胁模型也适合平台团队拿来对照做加固。1. 为什么攻击者盯上 DaemonSet工作机制与威胁画像1.1 DaemonSet 的调度特性怎么变成“后门温床”先花一分钟把 DaemonSet 的工作机制说透。它的控制器会监听 Node 对象的增删以及 DaemonSet 本身的变更只要集群里有节点满足模板的调度条件控制器就保证这个节点上始终有一个对应的 Pod。节点加入自动创建节点删除自动清理Pod 异常退出控制器立刻重新拉起。换句话说它的期望状态由节点列表决定而不像 Deployment 那样由一个可变的 replicas 数字决定。这个特性拆开看有三点非常特别节点绑定Pod 数量跟着节点走集群规模越大恶意副本越多。自愈重建只要模板还在无论你怎么删 Pod它都会立刻重建而且重建速度极快。全集群覆盖如果 tolerations 写得全它能跑到集群里所有节点上包括控制平面节点。攻击者看中的正是这三点。你如果用 Deployment 做后门replicas 还能缩到 0Pod 被删了可能也就真没了用 CronJob 做后门到点才跑运行窗口固定容易被发现。而 DaemonSet 只要模板在就永远有 Pod 在跑还分布在每一台节点上——天然就是“永恒后门”的形态。防守方如果只盯着某个节点上的异常 Pod删完之后发现过一会儿又冒出来这时候才意识到是控制器在搞鬼往往已经晚了。1.2 受害场景一个 DaemonSet 能造成多大破坏我把最近几年看到的典型场景整理了一下你可以对照着想象一下攻击面。第一个场景是全节点种马。恶意 DaemonSet 在每一台节点上拉起一个挖矿容器CPU 飙高但找不到入口。很多团队第一反应是排查 Deployment、StatefulSet结果列出来全是正常的最后才在 DaemonSet 里发现一个伪装成日志组件的对象。第二个场景是节点级后门。容器里挂载了宿主机根目录攻击者往宿主机写入 crontab、systemd service、ssh authorized_keys。这时候就算你把 DaemonSet 删了后门已经落在节点文件系统上Pod 没了后门照样回连。第三个场景是流量嗅探。hostNetwork 模式下Pod 直接共享宿主机网络命名空间可以监听节点的网卡流量。云环境里 metadata 服务、内网通信、数据库连接全暴露在同一个网络栈里危害程度远高于普通容器逃逸。第四个场景更危险tolerations 写全之后DaemonSet 可以调度到 master 节点。如果容器再把宿主机的 /etc/kubernetes 或数据目录挂载进去攻击者等于拿到了控制平面核心路径的访问能力。这个场景下不只是业务数据整个集群的配置、证书、etcd 数据都在攻击者面前敞开。2. 从权限获取到集群常驻恶意 DaemonSet 的完整攻击链路拆解2.1 前置条件控制平面写入权限从哪来没有控制平面级别的 DaemonSet 创建权限这套攻击就无从谈起。那攻击者是怎么拿到这个权限的我见过的高频路径有这几条Kubeconfig 泄露。开发机 ~/.kube/config 被拖走、CI 系统配置泄露、开源仓库误提交 kubeconfig 文件这些东西一旦暴露等于直接把集群控制面拱手送人。低权限 Pod 里挂载的 ServiceAccount 令牌被滥用。很多业务 Pod 会挂载 SA token如果这个 SA 被绑定了过度的 ClusterRole比如 edit 或者干脆是 cluster-admin攻击者进到容器内就能直接 kubectl 操作集群。Kubelet 未鉴权端口暴露。部分版本或错误配置下10250 端口允许匿名访问攻击者可以直接对节点下发命令进而拿到节点凭证。托管集群的工作节点被攻破后攻击者拿到 kubelet 证书用 system:node 身份越权操作。供应链攻击。恶意镜像里预埋了脚本Pod 一启动就自动尝试调用 API Server发现权限足够就部署恶意 DaemonSet。这些路径不是天方夜谭云原生安全事件里出现过很多次。而且攻击者拿到权限之后一般不会立刻大规模操作而是先观察集群里有没有监控告警、有没有事件通知机制确认安全后再部署持久化后门。2.2 恶意 DaemonSet 是怎么构造的下面这个 YAML 是典型的恶意 DaemonSet 形态。我先说明一下这个配置是我在本地测试环境里为了验证防御规则而构造的样例镜像地址是占位符不指向任何真实仓库生产环境严禁直接套用。apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-agent namespace: kube-system spec: selector: matchLabels: app: fluentd-agent template: metadata: labels: app: fluentd-agent spec: # 容忍所有污点确保 master 节点也能调度 tolerations: - operator: Exists hostNetwork: true hostPID: true hostIPC: true containers: - name: agent image: registry.example.com/persistence-agent:v1 imagePullPolicy: Always securityContext: privileged: true volumeMounts: - name: hostfs mountPath: /host command: - /bin/sh - -c - | # 在宿主机层面写入 crontab 后门仅测试环境 echo */5 * * * * root /root/.x/agent.sh /host/etc/crontab /bin/sleep 3650d volumes: - name: hostfs hostPath: path: /这段配置的每个字段都有明确用意metadata 里把名字伪装成 fluentd-agentnamespace 放进 kube-system就是为了和正常的日志采集组件混在一起。防守方看到 fluentd 相关对象第一反应往往是“这是我们平台自己装的”不会立刻起疑。tolerations 写 operator: Exists意味着容忍所有污点master 节点也会部署。很多防守团队只盯着 worker 节点的监控忽略控制平面节点这个盲区正好被利用。hostNetwork、hostPID、hostIPC 三项全开Pod 直接共享宿主机的网络、进程和 IPC 命名空间。hostPID 尤其危险因为容器内可以看到宿主机上所有进程甚至可以 ptrace 其他进程。securityContext.privileged: true 是特权容器配合 hostPath 挂载宿主机根目录等于容器拿到了宿主机的完整文件系统权限。command 里写入 crontab 后门然后 sleep 挂住。Pod 的存在是为了保持容器不退出真正的工作是那行 crontab。这个 YAML 一旦 apply 成功集群内所有满足条件的节点都会在一分钟左右全部拉起对应 Pod。你可能只看到几个 Pod 在跑但它的影响面是整个集群。2.3 持久化为什么能“永恒”控制器的自愈机制在给攻击者打工理解了构造再深挖一层为什么说这个后门是“永恒”的。第一层删除单个 PodDaemonSet 控制器秒级重建。你手动 kubectl delete pod控制器发现期望状态是有一个 Pod 在跑立刻重新创建而且 Pod 名字会带上新的哈希后缀。这时候你看到的不是“清除成功”而是“又来了一个”。第二层删除整个 DaemonSet节点上的后门文件依然存在。crontab、systemd service、ssh authorized_keys 这些后门不依赖 Kubernetes 对象Pod 没了它们照样在节点上运行。你会面临一个尴尬的情况集群控制面看起来干净了但每台节点还是会定时回连攻击者服务器。第三层就算重置了 kubelet、重装容器运行时只要 API Server 里的 DaemonSet 定义还在新节点一加入集群控制器就会自动把恶意 Pod 拉起来。更麻烦的是攻击者控制的模板里可以包含“每次启动时重新写入宿主机的后门”的逻辑等于控制面的模板和节点上的后门互为备份清掉任何一边另一边都能把后门重新种回来。还有一个容易被忽略的细节攻击者可以在 DaemonSet 里设置 updateStrategy 为 OnDelete降低升级时 Pod 重建的可见性也可以故意让 DaemonSet 只在某一个带特定标签的节点上调度缩小暴露面。这些手段单独看都不起眼组合在一起就让排查变得非常困难。3. 防守侧落地发现、阻断与告警的完整方案3.1 事前预防准入控制和 RBAC 双保险防守的第一步不是检测而是让恶意 DaemonSet 根本创建不出来。Kubernetes RBAC 是 allow 模型没有真正的 deny 规则所以最务实的做法是白名单思维默认不给普通用户和 ServiceAccount 授予 daemonsets 的 create、update、patch、delete 权限只有平台运维组的特定 SA 能操作。下面是限制普通角色创建 DaemonSet 的 RBAC 思路注意这里我写的是一个 ClusterRole 示例实际绑定要结合你自己的账号体系apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: restrict-daemonset-write rules: - apiGroups: [apps] resources: [daemonsets] verbs: [get, list, watch]这段配置只给了 get、list、watch没有 create 等写权限。实际生产环境可以把这个 ClusterRole 绑定给所有业务 namespace 的默认 SA而给专门的运维 SA 额外绑定一个允许写 DaemonSet 的 role。RBAC 之外更推荐用准入控制器做策略拦截。当前主流方案是 Kyverno 或 OPA Gatekeeper。我用 Kyverno 举例它可以直接写一个 ClusterPolicy拒绝任何带 privileged 容器或挂载宿主机根目录的 DaemonSetapiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: block-malicious-daemonset spec: validationFailureAction: Enforce background: false rules: - name: block-privileged-ds match: resources: kinds: - DaemonSet validate: message: DaemonSet with privileged container or hostPath / is blocked. deny: conditions: any: - key: {{ request.object.spec.template.spec.containers[?securityContext.privilegedtrue] | length() }} operator: GreaterThan value: 0 - key: {{ request.object.spec.template.spec.hostPID }} operator: Equals value: true - key: {{ request.object.spec.template.spec.hostNetwork }} operator: Equals value: true这个策略的触发逻辑是当有 DaemonSet 创建或更新时检查模板里是否包含 privileged 容器、hostPID、hostNetwork只要命中其中任意一个条件就拒绝。这里面的 JMESPath 表达式看起来复杂其实核心就是遍历容器数组统计 privileged 为 true 的容器数量。如果数量大于 0deny 条件就成立。需要注意很多正常的监控组件、日志采集组件本身也可能用 privileged 容器比如节点监控 agent所以策略不能一刀切。比较稳妥的做法是先跑一段时间 audit 模式validationFailureAction 设为 Audit观察哪些合法 workload 会被误拦再切成 Enforce 强制模式。3.2 事中检测审计日志、指标、运行时三层布防如果恶意 DaemonSet 还是绕过了准入控制创建了出来就要靠事中检测了。我习惯分三层去看。第一层是 API Server 审计日志。这是发现恶意 DaemonSet 最直接的方式。默认情况下审计策略可能没有覆盖 daemonsets 的写操作需要先在 kube-apiserver 的 --audit-policy-file 里配置。一个精简的审计策略片段如下apiVersion: audit.k8s.io/v1 kind: Policy omitStages: - RequestReceived rules: - level: RequestResponse resources: - group: apps resources: [daemonsets] verbs: [create, update, patch, delete]配置之后所有对 daemonsets 的写操作都会记录完整的请求和响应。事件响应时我一般会先抓三样东西kubectl get daemonsets -A -o yaml ds.yaml kubectl get pods -A -o wide pods.txt kubectl get events -A --sort-by.lastTimestampds.yaml 可以看到当前集群里所有 DaemonSet 的定义pods.txt 可以看到 Pod 分布events 则记录了最近的调度事件。如果审计日志里出现了非预期主体的 create daemonset 操作顺着 user 字段和老事件就能定位到创建来源。第二层是指标监控。Prometheus 里有几个 kube-state-metrics 暴露的现成指标可以用kube_daemonset_status_desired_number_scheduled 表示期望调度的副本数kube_daemonset_status_current_number_scheduled 表示当前调度的副本数kube_daemonset_status_number_ready 表示就绪副本数。当集群里新增了不在预期内的 DaemonSet这几个指标会同时出现异常。一个简单的查询可以先看变化量increase(kube_daemonset_status_desired_number_scheduled[5m]) 0这个查询能捕捉到 5 分钟内 DaemonSet 期望副本数的上涨。不过指标只能作为辅助真正要确认还是得靠审计日志和手动查看对象定义。第三层是运行时检测。Falco 这类工具可以在内核层监控容器行为对特权容器写入宿主机 cron 目录这类异常行为非常敏感。一个简化的 Falco 规则思路如下- rule: Write Below Etc Cron On Host desc: Detect privileged container writing to host cron file condition: evt.type in (open, openat) and evt.dir and container.privilegedtrue and fd.name startswith /etc/cron output: Privileged container writing to host cron file (user%user.name) priority: CRITICAL tags: [persistence, host]这只是一个简化示例真实生产环境还需要结合自己的 Falco 规则集中管理。核心思路是容器写入 /etc/cron 本身不一定是异常但特权容器写 /etc/cron 就是高度可疑值得立即告警。3.3 事后处置拿到恶意 DaemonSet 后的清理流程万一恶意 DaemonSet 已经跑起来了处置流程要按顺序来别一上来就删。第一步确认范围。先把所有 DaemonSet 列表导出来标记可疑对象保留完整 YAML 作为证据。别急着删任何东西先用 kubectl get ds -A 看清楚影响面。第二步阻断回连。如果云环境有安全组先限制可疑 Pod 所在节点到外网的连接避免攻击者继续下载工具或回传数据。这里要注意一个常见误区hostNetwork 模式下 NetworkPolicy 是无效的因为 Pod 跟宿主机共享网络栈不再经过网络插件的数据路径。所以阻断回连只能用云安全组、iptables 规则或者节点级防火墙来做。第三步删除恶意 DaemonSet。执行 kubectl delete ds -n 。如果删除后马上又出现同名 DS说明有更高层级的控制器或自动化流程在重新创建要往上查创建者或者 GitOps 工具。可以先把对应 ServiceAccount 禁用再持续观察一段时间。第四步排查节点后门。这一步最耗时。登录每一台受影响的节点逐项检查crontabcrontab -l 和 /etc/crontab、/etc/cron.d/ 下的文件。systemd service/etc/systemd/system/ 下可疑 unit特别是最近修改过的。ssh authorized_keys~/.ssh/authorized_keys 是否有陌生公钥。静态 Pod 目录/etc/kubernetes/manifests/ 下是否多了奇怪的 yaml静态 Pod 由 kubelet 直接管理不经过 API Server。最近修改的可疑二进制/usr/local/bin/、/opt/ 下是否有近期添加的文件。第五步轮换凭据。如果攻击者拿到过 SA token、kubelet 证书或者 kubeconfig所有相关凭据一律轮换。审计日志里出现的所有身份都要重新评估权限该撤销的撤销。第六步把这次事件中发现的检测规则固化进准入控制和监控告警防止同样手段再次进来。4. 常见问题与排查技巧实录4.1 高频排查问题速查我在实际排查中反复遇到下面几类问题整理成速查表遇到对应症状可以直接对照症状优先排查方向Pod 删除后几秒自动重建查 ownerReferences确认是否由 DaemonSet 控制器管理集群节点 CPU 飙高但 kubectl get pods 看不到野 Pod用 crictl ps -a 或 docker ps 检查节点上所有容器可能有隐藏容器或宿主进程在跑删除可疑 DaemonSet 后节点仍有回连说明后门已落到节点文件系统查 crontab、systemd、ssh key、静态 Pod 目录审计日志里查不到创建 DaemonSet 的记录检查 audit-policy 是否覆盖 apps/daemonsets 的 create 事件如果走了 GitOps 工具记录会挂在工具的 SA 下RBAC 明明做了最小化却还能创建 DS检查 ClusterRoleBinding 和 RoleBinding可能有 cluster-admin 绑给了低权限 SA新节点加入集群后自动出现可疑 Pod高概率是 DaemonSet 在起作用definitely 查 ds -A4.2 日常自查清单下面这份清单我建议平台团队直接放到巡检脚本或安全日报里每周跑一遍# 1. 列出所有 DaemonSet确认每个都在预期内 kubectl get daemonsets -A # 2. 检查高危配置的 DaemonSet kubectl get ds -A -o yaml | grep -B5 -A5 privileged: true # 3. 检查 hostPath 挂载根目录的 DaemonSet kubectl get ds -A -o yaml | grep -B3 -A3 path: / # 4. 检查 cluster-admin 绑定发现非预期主体 kubectl get clusterrolebinding | grep cluster-admin # 5. 检查 kube-system 里新增对象 kubectl get all -n kube-system注意第 2、3 步会打出很多正常组件的配置比如 node-exporter、fluentd 这类组件本身就可能有 hostPath所以要有白名单意识把已知组件排除掉重点看新出现的对象。4.3 几个防患于未然的小技巧最后分享几个我在实战里验证过的小技巧。第一给关键 API 写审计策略时不要只盯着 daemonsets。statefulsets、clusterroles、clusterrolebindings、secrets 同样重要。攻击者可以在拿到权限后创建新的 ClusterRole 和绑定或者直接读 secrets 拖走整个集群的凭据。审计策略覆盖范围要广宁可在存储上花点钱也别在溯源时两眼一抹黑。第二在节点上保留至少一层节点级监控。Pod 删除后容器日志就没了但节点上如果跑着 node-exporter、Falco 或者 osquery能留下进程执行的痕迹。排查时这些痕迹往往比 Kubernetes 审计日志更管用因为攻击者就算清掉了集群对象也很难把节点上的每一条进程记录都抹掉。第三定期做红蓝演练。给蓝队定一个目标部署一个恶意 DaemonSet 并且在 30 分钟内不被发现。这个演练最能暴露检测盲区。很多团队做一次就会发现在主机房里已经躺着好几个“看上去很正常”的 DaemonSet 了。第四维护 DaemonSet 白名单。把允许创建的 DaemonSet 名字、namespace、镜像 sha256 列出来准入控制用白名单模式比黑名单牢靠得多。黑名单永远追不上攻击者的想象力白名单则直接缩小了攻击面。这个思路适用于所有 controllers不只是 DaemonSet。我自己经历过一次线上排查最后一查就是一个伪装成日志采集组件的 DaemonSet 在搞鬼。当时我们删了三遍 Pod它都自己起来了直到有人喊了一句“你看它的 owner 是谁”才意识到问题出在控制器层面。从那以后团队的 checklist 里永远会有一条任何异常 Pod先问它是被谁创建的、是不是有 DaemonSet 在自动调度。这个习惯帮我省了很多次夜深人静的应急。现在分享给你希望你能在集群出问题之前就想到这一层。
返回列表