Vos secrets Docker sont scannés en moins de trois minutes : enquête de terrain
Publier une image Docker sur un dépôt public, c'est exposer ses secrets à une flotte de robots outillés — en quelques minutes, sans le moindre partage. Nous l'avons vérifié sur le terrain avec trois images piégées. Voici ce que révèle l'enquête, comment ces scanners procèdent techniquement, et les réflexes qui protègent réellement vos identifiants.
Le mystère des « pulls fantômes »
Beaucoup d'équipes qui publient une image sur un dépôt Docker public font un jour la même observation, intrigante : « notre image reçoit des dizaines de téléchargements alors que nous n'avons donné le lien à personne ». Personne ne l'a partagée, elle n'est référencée nulle part, et pourtant le compteur grimpe. D'où viennent ces « pulls fantômes » ?
La réponse est simple, et elle mérite qu'on s'y arrête : un dépôt public se découvre tout seul. L'API de Docker Hub expose la liste des dépôts récemment modifiés, avec leurs tags et leurs manifests. Des robots interrogent ce flux en continu. Votre image toute neuve y apparaît quelques secondes après le push, et elle est aussitôt inspectée. La vraie question n'est donc pas si on est scanné, mais à quelle vitesse, par quelles techniques, et pour en faire quoi.
Pour répondre concrètement, nous avons monté une petite expérience de terrain. Le principe : publier des images d'apparence banale, mais truffées de faux secrets traçables, puis observer qui vient les chercher, comment, et ce qu'il en fait. Cet article raconte ce que nous avons vu — et surtout ce qu'il faut en retenir pour ne pas offrir ses vrais identifiants au premier robot venu.
Le dispositif : trois images-appâts et des « canaris »
Un pot de miel (honeypot, en anglais) est un piège volontairement exposé pour attirer et observer les attaquants. Nous en avons déployé trois, chacun imitant une image populaire et crédible : un WordPress, un PostgreSQL et un Grafana. De l'extérieur, rien ne les distingue d'un déploiement réel.
À l'intérieur, chaque image contient six faux secrets uniques, placés à des endroits différents. Chaque secret est ce qu'on appelle un canari (canarytoken) : un identifiant piégé qui ne donne accès à rien, mais qui déclenche une alerte dès que quelqu'un l'utilise. L'image du canari dans la mine est exactement la bonne : le secret ne sert à rien d'autre qu'à mourir bruyamment pour signaler un danger.
Comment un canari « chante ». Nos faux secrets sont des clés AWS piégées. Quand un scanner trouve une clé, son réflexe standard est de vérifier si elle est valide en appelant l'API d'Amazon (l'opération sts:GetCallerIdentity, l'équivalent d'un « qui suis-je ? »). Cet appel est journalisé côté compte-piège et déclenche immédiatement une alerte. Celle-ci nous livre l'horodatage, l'adresse IP source, la géolocalisation, le User-Agent (la signature de l'outil utilisé) et même l'identifiant du compte AWS depuis lequel la vérification a été lancée — c'est-à-dire, en creux, l'infrastructure de l'attaquant.
L'intérêt de placer six canaris à six endroits distincts, c'est que l'endroit qui chante révèle la technique employée. Chaque emplacement correspond à une façon différente d'extraire un secret d'une image :
| Où le secret est caché | Ce qu'un déclenchement révèle sur le scanner |
|---|---|
Variable d'environnement (ENV) |
Il lit le manifest de l'image, sans même la télécharger |
Étiquette de métadonnées (LABEL) |
Il scanne les métadonnées et teste les URL qu'il y trouve |
Fichier .env dans l'image |
Il télécharge l'image complète et fouille son système de fichiers |
Fichier ~/.aws/credentials |
Il connaît les chemins conventionnels où l'on range des clés |
| Secret écrit puis supprimé dans une couche | Il analyse l'historique des couches, pas seulement l'état final |
| Fichier de configuration d'outil (backup, webhook) | Il inspecte les configs applicatives à la recherche d'URL secrètes |
Les images ont été poussées un après-midi, sans jamais être partagées, et nous avons regardé les alertes tomber. Elles sont tombées vite.
Ce que nous avons observé
La découverte est quasi immédiate — et s'accélère. La latence entre le push et la première alerte se compte en minutes. Plus troublant : d'un déploiement au suivant, la fenêtre d'activité s'est resserrée — environ 9 minutes sur la première image, un peu plus de 5 sur la deuxième, moins de 3 sur la troisième. Comme si, une fois notre espace de noms repéré, nos publications suivantes étaient trouvées plus vite.
L'extraction des clés AWS a été exhaustive. Les quatre emplacements de clés ont été touchés sur les trois images, sans exception. Aucune cachette n'a été négligée. En revanche — détail instructif sur lequel nous revenons plus loin — aucun des faux secrets de type URL ne s'est déclenché.
L'ordre de découverte trahit l'écosystème. Sur l'image WordPress, le premier secret touché était la variable d'environnement (lue dans le manifest, sans téléchargement). Sur Grafana, le premier était celui rangé dans provisioning/datasources — précisément le dossier où Grafana stocke réellement ses identifiants de connexion. Un scanner qui priorise ce chemin connaît la structure de Grafana : ce n'est pas une recherche aveugle, c'est un extracteur spécialisé.
Le point technique à retenir : supprimer une couche ne supprime rien
C'est l'enseignement le plus important de l'enquête, et celui qui surprend le plus de développeurs.
Une image Docker n'est pas un dossier plat : c'est un empilement de couches (layers). Chaque instruction du Dockerfile (COPY, RUN, ADD…) crée une nouvelle couche, et toutes les couches sont conservées dans l'image finale, comme les strates d'un terrain. L'image que vous voyez « à la fin » n'est que la superposition visible de cet historique — mais l'historique complet, lui, voyage avec l'image et reste lisible.
La conséquence est contre-intuitive. Beaucoup pensent régler le problème en copiant un secret, en s'en servant, puis en le supprimant dans la foulée :
# NE PROTÈGE RIEN — le secret reste dans la couche du COPY
COPY id_rsa /root/.ssh/id_rsa
RUN git clone git@github.com:mon-org/repo-prive.git \
&& rm -f /root/.ssh/id_rsa
Le fichier id_rsa n'apparaît plus dans l'image finale. Mais la couche créée par le COPY existe toujours dans l'historique, et le secret y est intact. N'importe qui peut « remonter le temps » couche par couche et le récupérer.
Notre canari « couche supprimée » l'a confirmé sans ambiguïté : il s'est déclenché sur les trois images, et souvent parmi les tout premiers. Analyser l'historique des couches n'est plus le fait d'un attaquant sophistiqué — c'est devenu un comportement de base, une commodité que tous les scanners appliquent par défaut.
À graver dans le marbre : COPY secret . && RUN rm secret n'offre aucune protection. Un secret qui a été écrit une seule fois dans une couche est récupérable pour toujours. La seule parade réelle est de ne jamais matérialiser le secret dans une couche.
La bonne approche consiste à isoler l'étape qui a besoin du secret dans un conteneur intermédiaire jetable (multi-stage build), ou à monter le secret le temps d'une commande seulement, sans qu'il soit jamais écrit dans une couche, grâce à BuildKit :
# syntax=docker/dockerfile:1
FROM alpine AS build
# Le secret est monté le temps du RUN, jamais persisté dans une couche
RUN --mount=type=secret,id=ssh_key \
git clone git@github.com:mon-org/repo-prive.git
FROM alpine
# L'image finale ne contient que le résultat, aucune trace du secret
COPY --from=build /app /app
Ici, la clé n'existe que dans la mémoire du build, pendant l'exécution de la commande. Elle n'est inscrite dans aucune couche et ne part donc pas avec l'image.
Une petite économie spécialisée, pas un robot unique
Derrière ces pulls fantômes, on imagine volontiers un gros robot monolithique qui aspire tout. La réalité est plus intéressante : nous avons identifié au moins trois acteurs distincts, chacun reconnaissable à sa signature technique (version d'outil, système d'exploitation, hébergeur) et spécialisé sur une technique d'extraction précise.
| Acteur | Signature de l'outil | Spécialité | Présence |
|---|---|---|---|
| A | TruffleHog + script Boto3 (Python), VM louée en Allemagne |
Manifest et couches supprimées | 3 images / 3 |
| B | python-requests, VM louée au Canada |
Fouille du système de fichiers | 3 images / 3 |
| C | Terraform, sortie changeante (Norvège, puis VPN suédois) |
Chemins conventionnels ~/.aws |
2 images / 3 |
Deux détails méritent l'attention. D'abord, l'acteur A affiche ouvertement TruffleHog comme signature : c'est un outil de détection de secrets open source, parfaitement légitime et public, ici retourné à des fins offensives. Ensuite, l'acteur C change de point de sortie entre deux images (un hébergeur norvégien, puis un VPN suédois) tout en conservant exactement la même version d'outil — preuve que l'adresse IP identifie un point de sortie loué, pas une personne. La signature de l'outil est un bien meilleur indice d'identité.
Cette stabilité est le trait le plus frappant de l'enquête : mêmes signatures, mêmes spécialisations, à une heure d'intervalle et quel que soit l'écosystème imité. On n'observe pas un essaim indifférencié, mais un petit nombre d'opérateurs méthodiques qui ratissent le dépôt en continu, chacun dans son couloir.
Bonne nouvelle défensive, au passage : cette menace est industrialisée, pas élitiste. Ce sont des outils publics et standardisés, assemblés en chaîne. La barrière technique est nulle — n'importe qui peut monter ce pipeline — mais cela signifie aussi que contrer des outils connus est bien plus tractable que contrer un adversaire sur mesure.
Que font-ils des clés ? Ils évaluent le butin
Une fois une clé validée, les scanners ne s'arrêtent pas à « elle marche ». Nous avons observé, dans la foulée, une reconnaissance large du compte compromis : inventaire des bases de données, des clusters, des dépôts d'images, des fonctions serverless, des secrets stockés… L'attaquant dresse la carte de ce à quoi la clé donne accès.
Le détail le plus parlant : certains appels servent à estimer le budget du compte (une prévision de coûts AWS). Autrement dit, l'attaquant évalue la « valeur » du compte qu'il vient de compromettre, sans doute pour prioriser ses cibles. Le secret volé n'est pas une fin, c'est le début d'un triage industriel.
Cette immédiateté condamne toute réaction humaine : entre la publication et l'exploitation, il s'écoule quelques minutes. Le temps de s'apercevoir d'une fuite et de faire tourner une clé manuellement, elle a déjà été utilisée.
Pourquoi les faux liens n'ont, eux, jamais été suivis
Souvenez-vous : nos secrets de type URL n'ont déclenché aucune alerte, contre 12 clés AWS sur 12. L'asymétrie est totale, et elle est riche d'enseignement. La population de robots que nous avons rencontrée est faite de valideurs de secrets, pas de visiteurs de liens. Ils cherchent des identifiants à tester contre une API, pas des URL à ouvrir.
Pour un défenseur qui veut instrumenter cette menace, la leçon est directe : les canaris de type « clé AWS » sont l'instrument décisif ; les faux liens passifs sont quasi inutiles. Et pour tout le monde, un rappel : le danger immédiat, ce sont les identifiants exploitables (clés cloud, jetons d'API, mots de passe de base de données), bien plus qu'une URL interne qui traînerait dans une étiquette.
Les réflexes qui protègent réellement
L'enquête débouche sur une hiérarchie claire des défenses. Aucune n'est exotique ; toutes tiennent à la discipline de fabrication des images.
- Ne jamais écrire de secret dans une couche. C'est la règle mère. Utilisez les multi-stage builds et les montages de secrets BuildKit (
--mount=type=secret), injectez les identifiants à l'exécution (variables d'environnement au lancement, gestionnaire de secrets, volumes montés), jamais au build. - Bannir les secrets des variables d'environnement de l'image. Un
ENVest le chemin de fuite le plus rapide : il est lisible dans le manifest sans même télécharger l'image. Les variables d'environnement se définissent au moment d'exécuter le conteneur, pas dans leDockerfile. - Vérifier ses images avant publication. Faites passer vos propres images au détecteur de secrets (les mêmes outils que les attaquants, comme TruffleHog, mais du bon côté) dans votre chaîne d'intégration continue. Un
.dockerignoresoigné évite d'embarquer par erreur un.envou un~/.aws. - Miser sur des identifiants éphémères et surveillés. Puisque la validation est automatique et immédiate, la vraie mitigation est d'avoir des clés à courte durée de vie et une alerte sur tout usage anormal. C'est exactement le mécanisme que nos canaris exploitent — retournez-le à votre avantage sur vos vrais comptes.
- Traiter toute fuite comme immédiate. Si un secret a été poussé dans une image publique, ne serait-ce qu'une seconde, considérez-le comme compromis et révoquez-le — le supprimer de l'image ne suffit pas, l'historique reste lisible et il a probablement déjà été aspiré.
Le réflexe d'ensemble : traitez la publication d'une image comme une prise de parole publique définitive. Tout ce qu'elle contient, y compris dans ses couches intermédiaires, est immédiatement lu, indexé et testé par des robots. La sécurité ne se rattrape pas après le push ; elle se construit au moment du build.
Monter en compétence sur ces sujets
Ces réflexes ne s'improvisent pas : ils s'apprennent et se mettent en pratique sur des cas concrets. C'est précisément l'objet de notre formation DEVOPS-001 — Conteneurisation avec Docker, dont un module entier est consacré à la sécurité et aux bonnes pratiques : multi-stage builds, images minimales, exécution non-root et — au cœur de cet article — la règle « ne jamais stocker de credentials dans une image » et la bonne gestion des secrets. Les équipes qui orchestrent leurs conteneurs à plus grande échelle prolongeront naturellement avec DEVOPS-010 — Orchestration Kubernetes, où la gestion des secrets se pose à l'échelle du cluster.
Au-delà de l'outillage, la fuite de secrets est d'abord une question d'hygiène numérique — les mêmes principes qui protègent un poste de travail protègent une chaîne de fabrication d'images. Notre formation CYBE-001 — Hygiène numérique pose ces fondamentaux pour l'ensemble des équipes, techniques ou non. Et pour ceux qui repensent leur infrastructure en profondeur, le choix du modèle d'isolation (abordé dans notre article VM, micro-VM ou conteneur) et de l'hébergeur, y compris souverain, complète le tableau.
Ce qu'il faut retenir
Publier une image Docker sur un dépôt public, c'est l'exposer immédiatement à une flotte de robots spécialisés qui la découvrent, la fouillent couche par couche et valident ses secrets en moins de trois minutes, sans qu'aucun lien n'ait jamais été partagé. Supprimer un secret dans une couche ultérieure ne protège rien : l'historique reste lisible. La seule défense solide est l'hygiène au build — ne jamais matérialiser un secret dans une couche, bannir les identifiants des variables d'environnement, vérifier ses images avant publication — complétée par des identifiants éphémères et surveillés. La menace est industrialisée et outillée avec des logiciels publics, ce qui la rend d'autant plus reproductible… et d'autant plus contrable par des équipes qui en maîtrisent les mécanismes.