Kubernetes… alalala, on tremble de passion en entendant ce nom. Dans la conclusion de mon dernier article qui date de pas mal de temps, je commençais à parler d’orchestration de conteneurs toussa toussa pour faire de « vraies » infras et créer des architectures complexes, et bien « nous y est » ! Kubernetes ou encore « K8S » pour les intimes est une techno qui a le vent en poupe pour l’orchestration de conteneurs. Après plus de 3 ans à faire de la sécurité cloud native et avoir audité plein de clusters, des petits, des gros, des mignons. J’avais besoin d’une thérapie pour exprimer ma détresse.
Tout le monde adopte cette techno sans réellement se poser des questions sur les difficultés qu’il peut y avoir en termes d’administration et maintenance. Aujourd’hui, vous apprendrez pourquoi K8S est un enfer à gérer dans le domaine de la sécurité. Les ingénieurs SSI, ne soyez pas timides, je sais que ça fait mal, pleurer c’est naturel.
Architecture interne
Avant de parler des faiblesses de K8S, il faut bien comprendre l’architecture interne. Je ne ferai pas un cours car il existe de la documentation et des blogs qui en parlent donc je vous invite fortement à lire les concepts : https://kubernetes.io/fr/docs/concepts/
Il existe aussi un très bon article d’un blog que j’apprécie énormément sur les sujets cloud native : https://blog.stephane-robert.info/docs/conteneurs/orchestrateurs/kubernetes/introduction/
En définitive, K8S est composé d’un control plane et d’un data plane. Sur le control plane, nous retrouvons les composants qui participent au fonctionnement intrinsèque du cluster comme « etcd » ou encore le « kube-apiserver ». Sur le data plane, on y retrouve les pods qui vont exécuter les applications métiers. Pour que les composants dont les pods puissent communiquer entre eux sur les différents noeuds, il existe un réseau « overlay » qui n’est pas accessible depuis l’extérieur et géré par une CNI. Il est également possible d’exposer des applications depuis l’extérieur du cluster avec des services tels que NodePort et LoadBalancer. L’architecture d’un cluster K8S ressemble en réalité à un véritable système d’information. Maintenant que nous avons survolé l’architecture, intéressons-nous aux problématiques de sécurité.

Le système
K8S fait de l’orchestration de conteneurs et quand on fait de la conteneurisation, on utilise quoi ?
Plot twist : Des container runtimes (Containerd, CRI-O, podman, etc…)
Tous les noeuds s’appuient sur un container runtime pour exécuter des pods qui représentent un ensemble de conteneurs qui partagent les mêmes namespaces Linux. Un pod peut-être constitué d’un ou plusieurs conteneurs.
Les faiblesses de sécurité connues et inconnues de la conteneurisation remontent dans le concept de Pod. Par exemple, un cluster expose mon application web avec un middleware vulnérable à une exécution de commandes. Un attaquant peut recevoir un reverse shell qui lui donnera l’accès au pod qui porte mon application vulnérable. Si j’ai administré une mauvaise configuration système à mon pod, l’attaquant pourra s’échapper vers le noeud sur lequel il s’exécute en tant que « root » et s’attaquer aux autres pods présents sur le noeud. Mon précédent article sur la sécurité Docker en parle très bien. Un gros défaut de Kubernetes est qu’il n’existe pas une fonctionnalité comme « userns-remap » qui permet de remapper l’utilisateur du pod à un utilisateur non privilégié sur l’hôte, le namespace user du noeud est partagé avec le pod donc root dans mon pod = root sur mon noeud (uid 0). Un administrateur devra toujours éviter l’utilisation de root dans ses conteneurs mais aussi durcir fortement le conteneur pour éviter toute escalade de privilèges vers root. On peut utiliser des solutions comme Kyverno ou OPA Gatekeeper pour créer des politiques de sécurité (bloquer le déploiement d’un pod privilégié ou avec l’utilisateur root par exemple) dans le cluster mais il est parfois possible de les contourner car il faut bien que les pods s’exécutent afin que les applicatifs métiers soient accessibles.
Le réseau
On l’a vu précédemment, il existe un réseau « overlay » et celui-ci est à plat. Kubernetes ne bénéficie pas de règles de filtrage qui empêchent la communication des pods sur le data plane avec l’API ou encore du filtrage inter-pods. Il faut choisir une CNI qui supporte les objets « NetworkPolicies » pour les implémenter et je peux vous dire qu’ils sont complexes à manipuler. Je travaille avec beaucoup d’experts de la techno et d’expérience, très peu maîtrisent réellement ces objets pour le filtrage. Si on prend le scénario précédent si mon pod est compromis, je peux accéder au control plane mais aussi aux autres pods (MariaDB, Redis, SSH, etc…). Pire encore, lorsque mes noeuds sont sur des sous-réseaux différents, les flux de mon réseau « overlay » seront encapsulés au hasard hein… par du VXLAN. Vous devinerez que c’est un moyen de contourner les règles de filtrage mise en place par un pare-feu, il devient totalement inutile donc n’essayez pas. Le pare-feu n’est pas capable de désencapsuler les entêtes VXLAN et analyser les flux à l’intérieur du tunnel. Les NetworkPolicies sont l’unique moyen de filtrer de flux provenant du réseau interne K8S.
La gestion des privilèges sur l’API
Attendez deux minutes, je sors crier. Les RBAC sont un véritable enfer dans cette techno, tu fais une erreur et t’es mort, le Phénix ne renaîtra pas de ses cendres. Dans K8S, pour certaines raisons, il est possible d’attribuer des privilèges à ses applicatifs métiers pour effectuer des actions sur l’API comme :
- Lire des secrets
- Créer/patcher des pods
- Créer/patcher des RoleBindings et ClusterRoleBindings
Ces trois privilèges sont les plus dangereux qu’il est possible d’exploiter méchamment pour devenir cluster-admin. Ici, la notion de ServiceAccount est importante car elle permet à un pod d’obtenir des privilèges sur l’API. Le ServiceAccount dispose d’un token qui est stocké dans les secrets K8S et ce dit token est monté en volume dans le pod à /var/run/kubernetes.io/serviceaccount/token.
Un pod compromis qui dispose des privilèges pour lire tous les secrets du cluster peu importe le namespace, permet de passer cluster-admin instantanément, par exemple les Ingress NGINX se déploient avec ce privilège par défaut.
Un pod compromis qui peut créer des pods, on ne va pas faire un dessin. Un petit déploiement de pods privilégiés sur le control plane (si possible) sinon sur le data plane avec un mount à la racine de l’hôte puis on accède au noeud en tant que « root » et on récupère le fichier kubeconfig.
Un pod compromis qui peut lier des Roles et ClusterRoles peut attribuer des privilèges cluster-admin.
Exploitation Helmv2
Les techs, c’est votre moment, de la technique youpi ! Helm est le gestionnaire de packages ultime sur K8S, il permet d’améliorer drastiquement la rapidité de déploiement des applications. Il existe plusieurs versions dont la V2 qui est vulnérable. Quand on installe Helm dans notre cluster, il ne vient pas seulement avec un binaire mais aussi avec un composant qui se nomme Tiller, celui-ci bénéficie des privilèges les plus élevés sur l’API, le ClusterRole cluster-admin qui lui permet de déployer tout et n’importe quoi. Malheur pour les ingénieurs DevOps, il expose un service gRPC qui permet d’effectuer des actions non authentifiées. Souvenez-vous de ce que j’ai dit précédemment, à l’intérieur du cluster, il n’y a pas de règles de filtrage par défaut ! (vous sentez le truc venir hein…)
On va donc se créer un beau petit chart qui déploiera un pod avec la racine du système de fichiers d’un noeud control plane et pour être bien sûr de mettre le cluster à terre on lui ajoute un ServiceAccount avec les privilèges cluster-admin. Voici la composition de mon chart :
$ tree
├── Chart.yaml
├── templates
│ ├── pod.yaml
│ └── serviceaccount.yaml
└── values.yaml
Nous avons là tous les ingrédients d’une mauvaise configuration d’un pod (déjà vu ahah) mais il a pour objectif de compromettre le noeud, c’est normal. Ce pod est priviligié et dispose d’un ServiceAccount avec les privilèges cluster-admin. Dans cette configuration /node_fs est relié au volume share qui a pour chemin la racine du noeud, ça signifie que pour accéder au système de fichiers du noeud on devra se rendre dans le répertoire /node_fs. Afin de récupérer la main directement au démarrage le pod va initier un reverse shell vers mon poste.

Partons du principe que nous avons eu une exécution de commandes sur le pod et que nous avons téléchargé le binaire kubectl. On constate que celui-ci a seulement le droit de consulter ses privilèges et lister des secrets. Par principe, on essaye de lister les pods et ça ne fonctionne pas car notre ServiceAccount thor n’est pas autorisé.

Tiller expose un service gRPC non authentifié et accessible de partout à l’intérieur du cluster et bien on ne va pas s’en priver. Par défaut, un cluster K8S a toujours un DNS installé, généralement ce sont des pods CoreDNS situés dans le control plane qui sont chargés de la résolution de noms. On est capable de trouver un service de cette manière <service_name>.<namespace>.svc.cluster.local et on s’aperçoit que le service tiller-deploy a le port 44134 ouvert par défaut, le fameux service gRPC. Dès lors, l’utilisation du binaire helm pour déployer notre application malicieuse et récupérer la main pour élever nos privilèges est possible.

On constate une élévation de privilèges système du pod vers le noeud car on accède au système de fichiers de celui-ci en tant que root, on peut y faire ce qu’on veut. Asseyez-vous ce n’est pas fini, il y a une deuxième privesc qui est le passage de thor à cluster-admin, et oui, nous avons tous les droits sur l’API du cluster maintenant. Dans l’exemple suivant, il est possible de lister des pods mais la commande kubectl auth can-i montre les ressources à *.* et les verbs (get,watch,create,list,etc…) à [*] aussi. Perso, j’aurais été ingé DevOps et un mec me fait ça en audit, je flippe… Vous me direz « oe oe on est à Helm V3 aujourd’hui ». Effectivement mais ça montre à quel point les différents composants dans K8S peuvent être vulnérables car la techno repose sur la CNCF qui référencent plein d’autres technos gérées par plusieurs acteurs qui ne sont pas de maturité équivalente en matière de sécurité.

Exploitation d’une mauvaise configuration des RBAC
On ne change pas une équipe qui gagne, on reste sur le même pod « pod-secrets-demo ». Comme on l’a vu précédemment le ServiceAccount thor est capable de lire la totalité des secrets du cluster. Je le répète mais c’est le comportement par défaut d’un Ingress NGINX déployé avec Helm…
Les secrets K8S stockent tout ce qui est sensible, les certificats, les clés SSH, les clés privées et bien évidemment le token des ServiceAccounts. Ce token est en réalité un JWT token encodé en base64 qui contient les infos du ServiceAccount et ses privilèges. On constate que thor est encore une fois incapable de lister les pods et bien nous allons nous intéresser aux secrets. Désolé, je vais tricher, « god » a un token qui porte les privilèges cluster-admin. En réalité, un auditeur aurait dû tester les secrets un à un car il ne connaît pas leurs privilèges en avance.

On exécute la commande avec l’option –token pour contacter l’API avec le ServiceAccount « god ». À partir de là, on bénéficie des privilèges de ce ServiceAccount pour effectuer les actions que l’on souhaite. Par exemple, déployer un Daemonset (instancie un pod sur chacun des noeuds du cluster) qui initiera un reverse shell vers le poste de l’auditeur et donnera une exécution de commande distante sur tous les noeuds du cluster. Hormis le problème de l’Ingress cité précédemment, un auditeur est souvent amené à réaliser plusieurs privesc avec les secrets pour devenir cluster-admin (Je suis capable de lire ce secret qui me permet de lire un autre secret d’un namespace différent qui lit lui-même un autre secret dans son namespace qui me permet de m’attribuer des privilèges). Un autre exemple serait de s’échaper d’un pod vers un worker node et aspirer les secrets de tous les autres pods s’exécutant sur ce noeud. Heureusement, Kubernetes avance en matière de sécurité et ne stocke plus les tokens directement dans le pod mais dans des projected volumes avec une expiration dépendamment de la version.

Vous pouvez retrouver le déploiement automatisé du lab vulnérable sur mon github :
https://github.com/p0sql/DVCN
Si vous avez besoin de techniques d’exploitation en plus, n’hésitez pas à m’envoyer un message sur LinkedIN :)
Comment durcir mon cluster K8S ?
Bon courage, merci et au revoir.
Plus sérieusement, un cluster K8S est très complexe à sécuriser mais c’est possible et ce n’est pas avec mes 2 ou 3 recos que ce sera le cas. Néanmoins, je vous propose tout de même les recos qui me semblent les plus importantes.
- Tout d’abord, la sécurité du cluster va s’appuyer avant tout sur l’hygiène des pods. Il est essentiel de suivre les recommandations de sécurité liées à la conteneurisation. Il faut garder en tête que certains applicatifs auront besoin de capabilities. On ne pourra « jamais » avoir un cluster complètement exempt de conteneurs ayant des privilèges et c’est une porte d’entrée. Si vous avez réussi à le faire dans un cluster de plus de 200 pods, je veux bien la recette.
- Il est nécessaire de couper l’accès à l’API aux pods qui n’en ont pas le besoin avec une règle de filtrage et utiliser l’option « automountServiceAccountToken: false » qui permet de ne pas monter le ServiceAccount par défaut dans les pods.
- Le principe du moindre privilège prend tout son sens lorsqu’on travaille avec K8S. Il faut définir CORRECTEMENT les RBAC car on peut régulièrement faire des erreurs sur le périmètre d’application des Roles et ClusterRoles. On peut aussi facilement attribuer des privilèges dangereux sans s’en rendre compte.
https://kubernetes.io/fr/docs/reference/access-authn-authz/rbac/ - Chiffrer et authentifier les communications inter-pods pour éviter des attaques MiTM qui permettraient la latéralisation depuis un pod vers un autre en récupérant des tokens ou des mots de passe. Par exemple, un endpoint qui nécessite une authentification à l’extérieur du cluster mais une fois à l’intérieur, il y à boire et à manger.
- Mettre en oeuvre des règles de filtrage dans le réseau « overlay », c’est certes complexe mais très important pour réduire la surface d’attaque des pods car tous les composants peuvent communiquer entre eux.
https://kubernetes.io/docs/concepts/services-networking/network-policies/ - L’API K8S permet de réaliser des actions d’administration donc mettez celle-ci dans votre réseau d’admin et ne l’exposez pas sur internet ou dans le réseau où s’exécutent vos applicatifs métiers, la porte sera grande ouverte pour taper dessus.
Conclusion
Kubernetes est un très bon produit mais pensez fortement à votre ingénieur SSI qui devra gérer la sécurité de ce joyeux bordel et s’il vous dit « on prend un cluster dédié dans un cloud public, on ne veut pas de mutualisation avec d’autres clients », écoutez-le un peu ! Dans un cloud privé, c’est pareil, il ne faut pas mutualiser dans un même cluster des applicatifs de sensibilités différentes. La sécurisation d’un cluster K8S est complexe à réaliser, ne serait-il pas judicieux de dédier des clusters spécifiques par zones et usages ? Je vous laisse mijoter un peu sur ce lien : https://kubernetes.io/docs/concepts/security/multi-tenancy/

Laisser un commentaire