摘要 :
本文复盘了一次真实的 Kubernetes 生产环境安全事故。攻击者利用了极其隐蔽的 RBAC 配置错误(匿名用户绑定管理员权限),在核心的
kube-system、kube-node-lease和kube-public命名空间下部署了伪装性极强的恶意 DaemonSet 和 Deployment。病毒利用
quay.io/pubproxy/pause恶意镜像,伪装成 K8s 基础架构容器,在全集群节点上疯狂占用资源挖矿。本文将带你重走排查之路:从 CPU 报警、与"不死"容器的拉锯战,到最终揪出那个致命的
anonymous-admin绑定。

一、 突发:CPU 飙升与诡异的"系统进程"
一切始于凌晨的监控告警。安全中心突然发出红色警报,集群内的多台 Worker 节点 CPU 占用率瞬间飙升至 99%。
1. 异常现象
登录到其中一台受损节点(Node-1),top 命令赫然显示一个名为 pause 的进程占据了榜首。
告警详情:
- 样本家族 :
Miner:Linux/BitCoinMiner.gyf - 进程路径 :
/usr/local/bin/pause - 执行命令 :
./pause
2. 初步困惑
熟悉 Kubernetes 的人都知道,pause 容器(Infra Container)是 K8s Pod 的网络和存储"挂架",它的逻辑非常简单,通常处于休眠状态,CPU 占用率应该接近于 0。
一个疯狂消耗 CPU 的 pause 进程?这绝对不是正经的 K8s 组件。这就像是一个穿着警察制服的暴徒。
二、 深入:剥开伪装的外衣
1. 节点层面的取证
为了搞清楚这个进程的来历,我在节点上查看了进程树:
bash
# 查看进程树
pstree -p 1009349
结果显示,这个挖矿进程 PID 1009349 的父进程是一个 entrypoint.sh 脚本,而这个脚本运行在一个 Docker 容器中。进一步检查该容器的信息:
bash
crictl inspect <Container-ID>
关键发现:
- 镜像名称 :
quay.io/pubproxy/pause:latest - 注意 :官方或阿里云的 pause 镜像通常是
registry.k8s.io/pause或registry.aliyuncs.com/google_containers/pause。pubproxy这个名字本身就透着一股可疑的味道。
2. 发现"内鬼" Pod
通过容器 ID 反查 Kubernetes Pod,结果让人背脊发凉。这些恶意容器并非孤立存在,而是归属于 Kubernetes 管理的 Pod。
bash
kubectl get pods -A | grep -E 'api-proxy|kube-controller-scheduler|nginx-deploys|kube-publice'
输出中混杂着大量看似极其"正规"的恶意 Pod,分布在多个系统命名空间:
kube-system:api-proxy-*(DaemonSet)kube-controller-scheduler-*(DaemonSet) ------ 听起来像是 Controller Manager 和 Scheduler 的合体,极易误导新手。
kube-node-lease:nginx-deploys-*(Deployment) ------ 这个命名空间本应只存放 Lease 对象,不应有 Pod!
kube-public:kube-publice-*(Deployment) ------ 注意publice多了个 'e',典型的拼写混淆。
攻击者的伪装艺术:
- 利用盲区 :他们将 Pod 部署在
kube-system、kube-node-lease等系统命名空间,运维人员通常不敢随意清理这里的资源。 - 名称混淆:使用听起来合法的名称。
- 镜像伪装 :使用名为
pause的二进制文件挖矿,企图在进程列表中蒙混过关。
三、 拉锯:杀不死的"僵尸"容器
在找到恶意 Pod 后,我尝试在节点上直接清理。
第一回合:手动删除容器
bash
docker rm -f <Malicious-Container-ID>
结果:无效。几秒钟后,一个新的容器凭空出现,继续挖矿。
第二回合:删除本地镜像
bash
docker rmi quay.io/pubproxy/pause:latest
结果:失败。提示镜像正被运行中的容器占用。即便强制删除,Kubelet 也会立即重新拉取镜像。
分析 :
这不仅仅是简单的容器入侵。这些 Pod 的 OwnerReferences 显示它们是由 DaemonSet 和 Deployment 控制的。
K8s 的控制器会不知疲倦地将它们"复活",确保副本数达到预期。
四、 溯源:谁给黑客开了后门?
既然恶意对象是 DaemonSet,那么是谁创建了它们?攻击者是如何拥有 kube-system 的写权限的?
我检查了集群的 RBAC(基于角色的访问控制)配置,试图寻找权限过大的 ServiceAccount 或 User。
bash
kubectl get clusterrolebindings -o wide | grep admin
输出结果揭示了那个致命的漏洞:
text
NAME ROLE AGE SUBJECTS
anonymous-admin ClusterRole/cluster-admin 22d system:anonymous
真相大白!
- 绑定名称 :
anonymous-admin - 权限 :
cluster-admin(集群最高权限,相当于 Linux 的 Root) - 主体 :
system:anonymous(匿名用户)
这意味着,互联网上任何一个人,只要能 ping 通这个集群的 API Server (6443 端口),不需要证书、不需要 Token、不需要密码,就被默认视为管理员,拥有对集群生杀予夺的大权。
攻击路径复盘:
- 攻击者扫描全网,发现开放的 6443 端口。
- 尝试匿名访问,发现拥有管理员权限。
- 利用权限在
kube-system下创建恶意 DaemonSet,在kube-node-lease下创建恶意 Deployment。 - 控制器指挥所有节点拉取恶意镜像进行挖矿。
五、 歼灭:雷霆手段与清理流程
找到根源后,修复工作必须迅速且彻底。
1. 堵住漏洞(关门)
立刻删除那个致命的 ClusterRoleBinding:
bash
kubectl delete clusterrolebinding anonymous-admin
此时,攻击者已经失去了对集群的控制权。
2. 清除毒源(斩首)
在 Master 节点删除所有恶意的控制器。这会触发级联删除,K8s 会自动向所有节点发送停止恶意 Pod 的指令。
bash
kubectl delete daemonset api-proxy -n kube-system
kubectl delete daemonset kube-controller-scheduler -n kube-system
kubectl delete deployment nginx-deploys -n kube-node-lease
kubectl delete deployment kube-publice -n kube-public
3. 节点扫尾(清场)
虽然控制层面删除了,但为了防止节点上有残留进程或僵尸容器,我们在所有 Worker 节点(Node-1, Node-2, Node-3)上执行了强制清理命令:
bash
# 停止并删除所有基于恶意镜像的容器
docker ps -a --filter ancestor=quay.io/pubproxy/pause:latest --format '{{.ID}}' | xargs -r docker rm -f
# 彻底删除本地的恶意镜像
docker rmi -f quay.io/pubproxy/pause:latest
4. 绝杀(占位防御)
为了防止某种未知的机制(如被篡改的 Kubelet 配置)再次尝试拉取该恶意镜像,我们使出了一招"偷梁换柱":
我们在节点上拉取了一个极小的 alpine 镜像,并将其强行标记为恶意镜像的名字。
bash
docker pull alpine:latest
docker tag alpine:latest quay.io/pubproxy/pause:latest
这样,即便病毒试图"借尸还魂",它启动的也只是一个只有几 MB 的、人畜无害的 Alpine 容器,而不再是挖矿程序。
六、 总结与安全建议
这次事件给所有 K8s 运维人员敲响了警钟。Kubernetes 的强大也意味着配置错误的代价是巨大的。
入侵指标 (IOCs) 速查:
- 恶意镜像 :
quay.io/pubproxy/pause:latest - 恶意对象 :
- DaemonSet:
api-proxy,kube-controller-scheduler(inkube-system) - Deployment:
nginx-deploys(inkube-node-lease),kube-publice(inkube-public) - ClusterRoleBinding:
anonymous-admin
- DaemonSet:
给你的 K8s 集群做个体检吧:
- 立刻检查 RBAC :
- 运行
kubectl get clusterrolebindings。 - 确保没有将
cluster-admin绑定给system:anonymous或system:unauthenticated。
- 运行
- 网络隐身 :
- 不要让 API Server (6443) 裸奔在公网上。
- 使用安全组限制特定 IP 访问,或通过 VPN/堡垒机访问。
- 关注系统命名空间 :
kube-node-lease和kube-public通常不应包含大量的 Deployment 或 Pod。定期审计这些"角落"。
- 镜像准入控制 :
- 部署 OPA Gatekeeper 或 Kyverno,禁止从非受信的镜像仓库拉取镜像。
写在最后:安全没有银弹,只有一次次在攻防中筑起的防线。希望这篇复盘能帮助你的集群避开同样的坑。