K8S节点路由:Pod网络的隐性可达性与安全隐患
实验环境
利用 kind 快速搭建 v1.27.1 的集群,一个 Master Node,两个 Worker Node。
kind create cluster --config - --image kindest/node:v1.27.1 <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: demo
nodes:
- role: control-plane
- role: worker
- role: worker
EOF
向 kind 的节点中导入 alpine 镜像后,创建一个测试 pod,
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: my-alpine-pod
spec:
containers:
args:
alpine
image: docker.io/library/alpine:latest
imagePullPolicy: IfNotPresent
name: pod
command:
"sleep"
"infinity"
EOF
新建 pod 的 ip 是 10.244.2.3,
kubectl get pod my-alpine-pod -o custom-columns=POD-IP:.status.podIP
而节点的 ip 是 172.18.0 段的,
kubectl get nodes -o custom-columns=NAME:.metadata.name,INTERNAL-IP:.status.addresses[0].address
可得,Pod 网络与 Node 网络不同:在 K8S 集群中,Pod 通常会被分配到一个独立的网络(例如 10.244.2),与 Node 所在的网段不同(例如 172.18.0)。
kind 创建集群的基本原理是通过使用物理机上的 Docker 容器模拟 Node
所以我当前的物理机是可以访问到 Node 网络的,
但是访问不了 Pod 网络。
可得,Pod 的网段不会自动暴露给外部网络(集群外部)。
节点路由
K8S 节点本身可以作为路由器。节点有能力将流量从外部网络(集群外部)转发到 Pod 网络中。通过在外部机器上添加路由条目,将 Pod 网络的流量定向到节点后,外部机器就可以访问 Pod 中的服务。
本实验中物理机可以作为外部机器,在物理机上添加路由条目,将前往 Pod 网络 10.244.0.0/16 的流量引导到节点 172.18.0.4 上,再次访问 Pod 网络,成功。
sudo route add -net 10.244.0.0 netmask 255.255.0.0 gw 172.18.0.4
进入节点并查看其路由表,以下是对 Pod 流量的路由。节点负责根据其路由表转发流量,确保流量能够顺利到达 Pod。
该节点上,数据包转发需要已启用。
在集群外部能访问到集群内部的 Pod 或 Service 网络,则可进行网络扫描获取集群内资产,具体请看下文【异步网络隧道:利用 VXLAN/IP-in-IP 穿透节点防火墙】中实验。
参考文章:https://raesene.github.io/blog/2021/01/03/Kubernetes-is-a-router/
提取证书中泄露的信息
实战中,在集群外部利用【节点路由】的特性去建立集群内部资源的通信,需要:① 获取若干节点IP;② 获取 Pod 或者 Service 网络大致范围。才能确保请求能够正确转发到集群内部的 Pod 或 Service。
当我们处于外部网络(集群外部,当前即是公网)时,通过 OpenSSL 连接到集群外部IP的 6443 端口,可获取其 SSL/TLS 证书信息,其中会泄露集群中一些 DNS 名称和 API 服务器监听的 IP 地址(默认情况下,Kubernetes 会将 API 服务器的所有监听 IP 地址分配到证书的 SAN 字段中)。这些 IP 包括服务 IP 和节点 IP,借此可以确认此集群服务(Service)的网络范围。
openssl s_client -showcerts -connect [集群外部IP]:6443 </dev/null 2>/dev/null | openssl x509 -text -noout
利用以下 nmap 脚本自动化获取上述信息,https://gist.github.com/jpts/5d23bfd9b8cc08e32a3591c8195482a8
nmap.exe -sTC -p6443 --script=./scripts/kubernetes-info.nse -Pn
之前的文章【Kubernetes Service 资产发现】中提到过:默认情况下,Kubernetes Service 资源的 IP 范围为 10.96.0.0/12。https://mp.weixin.qq.com/s/uUPSqhThajYm7tJiFB2AmQ
因此,上图中 10.96.0.1 和 10.103.97.2 刚好落在默认服务网段范围内(10.96.0.0/12),这表明它们很有可能是一个服务 IP,而 10.10.x.x 很可能是节点网络的一部分,因为它符合典型的私有网络分配规则。
结论
虽然 Kubernetes 默认情况下不允许从集群外部直接访问 Pod 资源,但这并不意味着 Pod 是安全的(例如使用节点路由便可访问到)。使用网络策略可以缓解这种风险。
参考:https://www.youtube.com/watch?v=7iwnwbbmxqQ
异步网络隧道:利用 VXLAN/IP-in-IP 穿透节点防火墙
实验环境
利用 k3s 快速搭建单节点集群,其中 master 节点 IP(192.168.92.138)、ubuntu-pod IP(该 pod 在 80 端口启动了 http 服务),ubuntu-service(10.43.116.172,为ubuntu-pod 提供负载均衡服务,并将该 pod 暴露给外部访问)
另有一台机器A(作为攻击机),与 master 节点同网段,处于集群外部,IP 为 192.168.92.137,很明显,集群A无法直接访问到集群内部的 ubuntu-service。
集成工具
在机器A(作为攻击机)中安装 encap-attack。https://github.com/WithSecureLabs/encap-attack
-
encap-attack detect:嗅探封装的网络流量
监听网络上的封装流量,并提取有关所使用的封装信息。只有在检测到封装流量或以详细模式运行时,才会返回相关信息,具体请看后面实验。
-
encap-attack kubeintel:获取Kubernetes集群的信息并进行攻击
encap-attack kubeintel guess-cidr 根据 K8S API 服务器证书猜测 Service IP 范围,原理就是上文中介绍的【提取证书中泄露的信息】,输出结果与实验环境一致,其中集群 Service 的网段很重要:10.43.0.0/12
encap-attack kubeintel guess-cidr 192.168.92.138 -p 6443
route add -net 10.32.0.0 netmask 255.240.0.0 gw 192.168.92.138
显然,此时机器A可以通集群 Service了,
此时,可以利用如 fscan、nmap 等扫描工具对集群 Service 网段进行资产扫描,这里偷懒就直接写集群的 DNS Service IP 进行 53 端口扫描了,实际情况是对网段扫描,
nmap -Pn -n -sT -p53 -T4 10.43.0.10 --open
也可以使用 k8spider 快速获取集群 Service 的资产,原理请看【Kubernetes Service 资产发现】https://mp.weixin.qq.com/s/uUPSqhThajYm7tJiFB2AmQ。

可以看出,节点路由是存在较大隐患的,一般来说,在节点防火墙上设置规则以阻止陌生 IP 访问节点确实可以有效减少潜在的安全风险,但是仍并不安全,攻击者可以利用 VXLAN 或者 IP-in-IP 进行网络封装。
① IP-in-IP 是一种在网络层进行流量封装的技术,它将一个 IP 数据包放置在另一个 IP 数据包内部。下图是标准数据包与使用 IP-in-IP 的数据包的比较,
节点防火墙通常只会检查外层 IP 数据包的头部信息,而不会深入解析内层 IP 数据包的内容,最终返回的结果是内层数据包的请求,故可以绕过防火墙,便可成功使用节点路由。
② VXLAN(虚拟扩展局域网)更为复杂,因为它在数据链路层操作。它为每个数据包增加了多个额外的头部信息,包括源/目的 MAC 地址、源/目的 IP 地址、UDP VXLAN 隧道头和 VXLAN 头。下图是标准数据包与使用 VXLAN 的数据包的比较,也是使用类似技术绕过节点防火墙。
-
encap-attack ipip:IP-in-IP的功能套件
K3s 默认内置了 Flannel 作为其网络插件,默认情况下使用的是 vxlan,并且不支持 IP-in-IP 模式的。所以本环境无法做 IP-in-IP 的实验。(Calico 默认使用 IP-in-IP 封装)
在机器A(攻击机)执行,
encap-attack ipip -d 192.168.92.138 request -di 10.43.116.172 http "GET / HTTP/1.1rnHost: 10.43.116.172"
encap-attack ipip -d [某节点IP] request -di [目标 Pod/Service IP] http "GET / HTTP/1.1rnHost: [目标 Pod/Service IP]"
外层是 机器A 192.168.92.137 -> Master节点 192.168.92.138,内层是 机器A 192.168.92.137 -> ubuntu-service 10.43.116.172,
如果 Master 节点防火墙配置只允许其他节点(比如 IP 为 192.168.92.1)访问,则可以在命令中添加参数 -s,
encap-attack ipip -d 192.168.92.138 -s 192.168.92.1 request -di 10.43.116.172 http "GET / HTTP/1.1rnHost: 10.43.116.172"
encap-attack ipip -d [某节点IP] -s [伪造任意的IP] request -di [目标 Pod/Service IP] http "GET / HTTP/1.1rnHost: [目标 Pod/Service IP]"
外层变成了 白名单IP 192.168.92.1 -> Master节点 192.168.92.138
查看详细的包,encap-attack --verbose detect
参考:https://labs.withsecure.com/tools/encap-attack?utm_source=tldrsec.com&utm_medium=referral&utm_campaign=tl-dr-sec-250-cnapp-guide-openai-s-o1-vs-ctfs-cloud-logging-tips
未完待续。
封面图:图源 https://www.youtube.com/watch?v=7iwnwbbmxqQ
原文始发于微信公众号(安全小将李坦然):K8S节点路由的隐患&异步隧道穿透节点防火墙
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论