<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hadi Mouter</title>
    <description>The latest articles on DEV Community by Hadi Mouter (@hadimouter).</description>
    <link>https://dev.to/hadimouter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3914403%2F5ad5f07e-ca6e-4c4b-8510-d1cce91b9b08.jpeg</url>
      <title>DEV Community: Hadi Mouter</title>
      <link>https://dev.to/hadimouter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hadimouter"/>
    <language>en</language>
    <item>
      <title>Durcir un Active Directory de A à Z : construire, attaquer, défendre, auditer</title>
      <dc:creator>Hadi Mouter</dc:creator>
      <pubDate>Fri, 26 Jun 2026 09:13:31 +0000</pubDate>
      <link>https://dev.to/hadimouter/durcir-un-active-directory-de-a-a-z-construire-attaquer-defendre-auditer-3b4c</link>
      <guid>https://dev.to/hadimouter/durcir-un-active-directory-de-a-a-z-construire-attaquer-defendre-auditer-3b4c</guid>
      <description>&lt;p&gt;Cet article retrace un cycle complet de durcissement Active Directory, monté de bout en bout sur un lab Azure. On construit l'infrastructure, on applique le tiering, on durcit par GPO, on audite avec PingCastle, puis on remédie. La différence avec un guide classique : pour chaque mesure défensive, je montre d'abord l'attaque qu'elle bloque. Si vous administrez un AD et que vous voulez comprendre pourquoi on durcit, pas seulement quoi cocher, c'est pour vous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi cet article
&lt;/h2&gt;

&lt;p&gt;Active Directory est la cible numéro un en environnement Windows. Une fois le domaine compromis, c'est toute l'organisation qui tombe. Pourtant la plupart des guides de durcissement se contentent d'une liste : désactivez SMBv1, activez LSA Protection, forcez NTLMv2. On exécute, on coche, mais on ne comprend pas ce qu'on bloque.&lt;/p&gt;

&lt;p&gt;Le souci, c'est qu'on durcit à l'aveugle. On ne sait pas distinguer la mesure critique de la mesure cosmétique, ni défendre un arbitrage quand la production impose une contrainte. Tant qu'on ignore quelle attaque une mesure neutralise, on ne sait ni la prioriser ni l'expliquer.&lt;/p&gt;

&lt;p&gt;J'ai pris le problème dans l'autre sens. Pour chaque protection, je commence par l'attaque : ce que fait vraiment un adversaire, et comment la mesure le bloque. C'est de mon avis la seule façon de durcir intelligemment.&lt;/p&gt;

&lt;p&gt;Le tout est démontré sur un lab réel, avec les preuves à chaque étape. Pas "la mesure est censée marcher", mais "voici l'output qui montre qu'elle marche".&lt;/p&gt;

&lt;h2&gt;
  
  
  Le terrain : un lab volontairement minimal
&lt;/h2&gt;

&lt;p&gt;Le lab tient sur trois machines hébergées sur Azure, organisées selon les trois niveaux du modèle de tiering Microsoft.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Machine&lt;/th&gt;
&lt;th&gt;Rôle&lt;/th&gt;
&lt;th&gt;IP privée&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tier 0&lt;/td&gt;
&lt;td&gt;DC01&lt;/td&gt;
&lt;td&gt;Contrôleur de domaine &lt;code&gt;lab.local&lt;/code&gt; (AD DS + DNS)&lt;/td&gt;
&lt;td&gt;172.16.0.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tier 1&lt;/td&gt;
&lt;td&gt;SRV01&lt;/td&gt;
&lt;td&gt;Serveur membre&lt;/td&gt;
&lt;td&gt;172.16.0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tier 2&lt;/td&gt;
&lt;td&gt;WS01&lt;/td&gt;
&lt;td&gt;Poste de travail (Windows 11)&lt;/td&gt;
&lt;td&gt;dynamique&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ndnvs0v7hlq3vcdh8s0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ndnvs0v7hlq3vcdh8s0.png" alt="Le groupe de ressources Azure avec les 3 machines du lab" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Soyons honnêtes tout de suite : ce lab n'est pas une architecture de production. C'est un environnement d'apprentissage minimal, fait pour démontrer les mécanismes. Les écarts assumés avec une vraie prod :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un seul contrôleur de domaine. En prod, jamais moins de deux, sinon le domaine s'arrête quand le DC tombe. Ici, un seul, pour limiter le coût.&lt;/li&gt;
&lt;li&gt;Pas de bastion ni de poste d'administration dédié (PAW). L'accès admin passe par du RDP filtré sur IP, là où une prod imposerait un point d'entrée séparé.&lt;/li&gt;
&lt;li&gt;Pas de vraie segmentation réseau entre les tiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pourquoi le dire ? Parce que savoir nommer ces écarts fait partie du métier. Un audit signalera "un seul DC" comme un risque, et la bonne réponse n'est pas de paniquer mais de documenter un risque accepté en connaissance de cause. On y revient plus bas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le tiering : la fondation de tout le reste
&lt;/h2&gt;

&lt;p&gt;Avant de durcir quoi que ce soit, il faut comprendre comment un attaquant remonte dans un AD. Sans ça, le durcissement n'est qu'un tas de réglages sans logique.&lt;/p&gt;

&lt;h3&gt;
  
  
  Le chemin d'escalade
&lt;/h3&gt;

&lt;p&gt;Voici le scénario classique, étape par étape.&lt;/p&gt;

&lt;p&gt;D'abord, un attaquant compromet un poste de travail par hameçonnage. C'est la machine la plus exposée, celle d'un employé lambda. À ce stade il a les identifiants de l'employé, rien de plus. Peu de dégâts.&lt;/p&gt;

&lt;p&gt;Plus tard, un administrateur se connecte sur ce poste pour le dépanner. S'il le fait avec un compte à hauts privilèges, ses identifiants restent en mémoire sur la machine, dans le processus LSASS.&lt;/p&gt;

&lt;p&gt;L'attaquant, toujours présent sur le poste, lit cette mémoire (un dump LSASS, typiquement avec mimikatz) et récupère les identifiants de l'admin. Si c'était un compte du domaine, c'est terminé : il contrôle l'AD entier.&lt;/p&gt;

&lt;p&gt;Le point à retenir : l'attaquant n'a pas besoin que l'admin soit connecté en permanence. Il suffit qu'un compte à privilège se soit connecté une seule fois sur la machine compromise.&lt;/p&gt;

&lt;h3&gt;
  
  
  La parade : segmenter par niveau
&lt;/h3&gt;

&lt;p&gt;Le tiering répond exactement à ce chemin. Il sépare comptes et machines en trois niveaux de sensibilité, avec une règle stricte : un compte d'un tier ne doit jamais pouvoir s'authentifier sur une machine d'un autre tier.&lt;/p&gt;

&lt;p&gt;Concrètement, le compte qui administre le contrôleur de domaine (Tier 0) ne doit jamais ouvrir de session sur un poste de travail (Tier 2). Comme ça, même si ce poste est compromis, aucun identifiant Tier 0 ne s'y trouve jamais. Le chemin d'escalade est coupé.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mise en œuvre : OU, groupes, GPO
&lt;/h3&gt;

&lt;p&gt;La structure repose sur des unités d'organisation (OU) qui matérialisent les tiers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lab.local
├── OU=Admins
│     ├── OU=Tier0   → compte adm-t0, groupe T0-Admins
│     ├── OU=Tier1   → compte adm-t1, groupe T1-Admins
│     └── OU=Tier2   → compte adm-t2, groupe T2-Admins
├── OU=Servers       → SRV01
├── OU=Workstations  → WS01
└── Domain Controllers (OU par défaut) → DC01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3576p0rf96x7ycr11ne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3576p0rf96x7ycr11ne.png" alt="L'arborescence des OU dans Active Directory Users and Computers" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Première bonne pratique : on cible des groupes, pas des comptes individuels. Les règles s'appliquent à &lt;code&gt;T1-Admins&lt;/code&gt;, pas à &lt;code&gt;adm-t1&lt;/code&gt;. Demain un nouvel admin serveur rejoint le groupe, et toutes les règles s'appliquent à lui automatiquement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;New-ADGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"T0-Admins"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-GroupScope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OU=Tier0,OU=Admins,DC=lab,DC=local"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-ADGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"T1-Admins"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-GroupScope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OU=Tier1,OU=Admins,DC=lab,DC=local"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-ADGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"T2-Admins"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-GroupScope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OU=Tier2,OU=Admins,DC=lab,DC=local"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Add-ADGroupMember&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"T0-Admins"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adm-t0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-ADGroupMember&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"T1-Admins"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adm-t1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-ADGroupMember&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"T2-Admins"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adm-t2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffamec4natwigjrtc4ptq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffamec4natwigjrtc4ptq.png" alt="Les trois groupes de tier peuplés (Get-ADGroupMember)" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Un piège qui en piège beaucoup : quand une machine rejoint le domaine, elle atterrit par défaut dans le conteneur &lt;code&gt;CN=Computers&lt;/code&gt;, sur lequel on ne peut pas lier de GPO. La première chose à faire après un join, c'est donc de la déplacer dans la bonne OU.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Move-ADObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CN=SRV01,CN=Computers,DC=lab,DC=local"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;-TargetPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OU=Servers,DC=lab,DC=local"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sans ce déplacement, vos GPO de tiering ne s'appliqueront jamais. Elles cibleront une OU vide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Imposer le tiering techniquement
&lt;/h3&gt;

&lt;p&gt;Le tiering devient réel grâce aux logon rights, des droits de connexion qu'on pilote par GPO. On crée une GPO par OU, qui interdit aux comptes du mauvais tier de se connecter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GPO&lt;/th&gt;
&lt;th&gt;Liée à l'OU&lt;/th&gt;
&lt;th&gt;Comptes refusés (Deny log on locally + RDS)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tier0-Deny-LowerTiers&lt;/td&gt;
&lt;td&gt;Domain Controllers&lt;/td&gt;
&lt;td&gt;T1-Admins, T2-Admins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tier1-Deny-OtherTiers&lt;/td&gt;
&lt;td&gt;Servers&lt;/td&gt;
&lt;td&gt;T0-Admins, T2-Admins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tier2-Deny-OtherTiers&lt;/td&gt;
&lt;td&gt;Workstations&lt;/td&gt;
&lt;td&gt;T0-Admins, T1-Admins&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Le chemin dans l'éditeur de GPO :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Computer Configuration
└─ Policies &amp;gt; Windows Settings &amp;gt; Security Settings
   └─ Local Policies &amp;gt; User Rights Assignment
      ├─ Deny log on locally
      └─ Deny log on through Remote Desktop Services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyemn6qb86b2i40ol4aav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyemn6qb86b2i40ol4aav.png" alt="La GPO Tier0 refusant T1 et T2 sur le contrôleur de domaine" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Règle de sécurité non négociable : ne mettez jamais dans le deny le compte que vous utilisez pour administrer, ni le tier légitime de la machine. Sinon vous vous verrouillez hors de votre propre infra. Gardez toujours une session admin ouverte et testez après chaque GPO. Jamais de modification en masse.&lt;/p&gt;

&lt;h3&gt;
  
  
  La preuve que ça marche
&lt;/h3&gt;

&lt;p&gt;On tente une connexion RDP sur le contrôleur de domaine avec un compte Tier 2 (&lt;code&gt;adm-t2&lt;/code&gt;). Résultat :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkdy6q437pqspqbhvuu84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkdy6q437pqspqbhvuu84.png" alt="Error 0x2407 : connexion refusée par le tiering" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L'erreur 0x2407 ("You might not have permission to sign in remotely") est la preuve visuelle. Détail important : le mot de passe a été accepté, l'authentification a réussi, mais la session est refusée par la GPO. Ce n'est pas un message "identifiants incorrects", qui voudrait dire mauvais mot de passe.&lt;/p&gt;

&lt;p&gt;En clair : si un attaquant vole les identifiants &lt;code&gt;adm-t2&lt;/code&gt; sur un poste compromis, ils sont inutilisables pour atteindre le contrôleur de domaine. Le chemin d'escalade est coupé techniquement, pas par une consigne qu'on espère respectée.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le durcissement : chaque défense face à son attaque
&lt;/h2&gt;

&lt;p&gt;Le tiering protège qui peut se connecter où. Le durcissement, lui, désactive les protocoles et configurations faibles qui servent de vecteurs d'attaque. Voici le tableau central, chaque mesure reliée à l'offensive qu'elle bloque.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mesure&lt;/th&gt;
&lt;th&gt;Réglage&lt;/th&gt;
&lt;th&gt;Attaque neutralisée&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LSA Protection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RunAsPPL = 1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dump LSASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WDigest off&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UseLogonCredential = 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mots de passe en clair en mémoire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMBv1 off&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SMB1 = 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EternalBlue, propagation latérale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMB signing&lt;/td&gt;
&lt;td&gt;"Digitally sign communications (always)"&lt;/td&gt;
&lt;td&gt;NTLM relay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLMNR off&lt;/td&gt;
&lt;td&gt;"Turn off multicast name resolution"&lt;/td&gt;
&lt;td&gt;Responder / poisoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NTLM hardening&lt;/td&gt;
&lt;td&gt;NTLMv2 only, refuse LM &amp;amp; NTLM&lt;/td&gt;
&lt;td&gt;Pass-the-hash, cracking offline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No LM hash&lt;/td&gt;
&lt;td&gt;"Do not store LAN Manager hash"&lt;/td&gt;
&lt;td&gt;Cracking offline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Le combo anti-LSASS
&lt;/h3&gt;

&lt;p&gt;Les deux premières lignes méritent qu'on s'y arrête, parce qu'elles forment le cœur de la défense contre le vol d'identifiants.&lt;/p&gt;

&lt;p&gt;LSASS est le processus Windows qui garde les identifiants en mémoire. C'est lui que vise un dump : on lit sa mémoire pour en sortir hash et mots de passe. Deux mesures le verrouillent.&lt;/p&gt;

&lt;p&gt;LSA Protection (&lt;code&gt;RunAsPPL = 1&lt;/code&gt;) transforme LSASS en processus protégé. Le système refuse alors qu'un autre processus lise sa mémoire, même en administrateur. Le dump échoue.&lt;/p&gt;

&lt;p&gt;WDigest off (&lt;code&gt;UseLogonCredential = 0&lt;/code&gt;) empêche le stockage des mots de passe en clair en mémoire. Une précision qui compte : sur Windows Server 2016 et plus récent, ce réglage est déjà à 0 par défaut. On ne referme donc pas un trou ouvert, on épingle le réglage pour qu'un attaquant ne puisse pas le réactiver. Repasser &lt;code&gt;UseLogonCredential&lt;/code&gt; à 1 est une technique de persistance connue, qui rendrait à nouveau les mots de passe lisibles en clair après un dump. C'est de la défense en profondeur et de l'anti-tampering, pas une correction de défaut.&lt;/p&gt;

&lt;p&gt;L'un rend la mémoire illisible, l'autre garantit qu'on ne pourra pas y réintroduire de mot de passe en clair. Ensemble ils ferment la porte au scénario d'escalade vu plus haut.&lt;/p&gt;

&lt;p&gt;Ces réglages se poussent par GPO via les préférences de registre.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7yotpezb2keyfuaq0ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7yotpezb2keyfuaq0ux.png" alt="L'entrée registre RunAsPPL dans la GPO LSA Protection" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Côté méthode : on empile toutes les GPO de durcissement, puis on fait un seul redémarrage à la fin. On ne redémarre pas un contrôleur de domaine à chaque mesure, parce que pendant son reboot l'authentification de tout le domaine est coupée.&lt;/p&gt;

&lt;h3&gt;
  
  
  La preuve que LSA Protection est actif
&lt;/h3&gt;

&lt;p&gt;Poser la clé ne suffit pas. LSA Protection ne s'active qu'au démarrage suivant, quand LSASS se lance en mode protégé. On vérifie donc que c'est vraiment effectif, pas seulement configuré.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-WinEvent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LogName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*LSASS*protected*"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TimeCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzpij5mdw8vex9itj312w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzpij5mdw8vex9itj312w.png" alt="Event système : LSASS started as a protected process, level 4" width="799" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le message "LSASS.exe was started as a protected process with level: 4" confirme. À partir de là, un dump LSASS sur cette machine se heurte au processus protégé et échoue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Couper les protocoles faibles
&lt;/h3&gt;

&lt;p&gt;Les autres mesures suivent la même logique.&lt;/p&gt;

&lt;p&gt;SMBv1 est un protocole obsolète, vecteur d'EternalBlue (le mécanisme de propagation de WannaCry). Aucune raison de le garder.&lt;/p&gt;

&lt;p&gt;SMB signing signe les paquets SMB, ce qui casse le NTLM relay : un paquet d'authentification falsifié est rejeté.&lt;/p&gt;

&lt;p&gt;LLMNR est un protocole de résolution de noms de secours qu'un outil comme Responder exploite pour usurper des réponses et capturer des hash. On le coupe, en gardant en tête que LLMNR n'est qu'une partie du problème. NBT-NS et mDNS sont d'autres vecteurs de Responder, à traiter séparément.&lt;/p&gt;

&lt;p&gt;Le durcissement NTLM force NTLMv2 et refuse LM/NTLMv1. Ces vieux protocoles produisent des empreintes cassables hors ligne en quelques minutes, et ouvrent la voie au pass-the-hash.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9y09fa1fcf4or8cxubu4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9y09fa1fcf4or8cxubu4.png" alt="Le niveau d'authentification LAN Manager forcé en NTLMv2 only" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une prudence pour la prod : couper NTLM totalement peut casser des applications anciennes. L'approche raisonnable, c'est de durcir (NTLMv2 seulement) sans tout casser, puis de mener un projet séparé d'audit avant un éventuel blocage complet.&lt;/p&gt;

&lt;h2&gt;
  
  
  L'audit : mesurer avec PingCastle
&lt;/h2&gt;

&lt;p&gt;Une fois le durcissement en place, comment savoir où on en est ? PingCastle est un outil d'audit AD gratuit, devenu un standard. Il scanne le domaine et sort un score de risque sur quatre axes, avec un rapport HTML détaillé.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\PingCastle.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--healthcheck&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lab.local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lire le score correctement
&lt;/h3&gt;

&lt;p&gt;Voici le résultat initial sur le lab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh0l7bews0o80skij6ms6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh0l7bews0o80skij6ms6.png" alt="Le rapport PingCastle : score global 55/100" width="799" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le point le plus important pour interpréter PingCastle : le score global est le pire des quatre axes, pas leur moyenne.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axe&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stale Objects&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privileged Accounts&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trusts&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Anomalies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GLOBAL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ici, le 55 vient entièrement de l'axe Anomalies. Pour faire baisser le global, c'est donc cet axe qu'il faut attaquer en premier. Optimiser un autre axe ne changerait rien au global tant qu'Anomalies reste à 55.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7hq9lfqzdepo5lif1z17.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7hq9lfqzdepo5lif1z17.png" alt="La liste des findings PingCastle déclenchés" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Un point qui rassure
&lt;/h3&gt;

&lt;p&gt;Avant même la remédiation, regardez ce que PingCastle ne signale pas : aucun finding SMBv1, aucun sur le dump LSASS, aucun NTLMv1. Pourquoi ? Parce que ces points ont été corrigés avant l'audit, pendant la phase de durcissement. L'audit confirme le travail déjà fait.&lt;/p&gt;

&lt;h2&gt;
  
  
  La remédiation : corriger n'est pas tout corriger
&lt;/h2&gt;

&lt;p&gt;C'est là qu'on sépare l'amateur du pro. Un rapport PingCastle brut fait peur, tout semble rouge. Le réflexe du débutant, c'est de vouloir tout corriger. C'est une erreur.&lt;/p&gt;

&lt;p&gt;Un rapport d'audit n'est pas une liste de cases à cocher en entier. C'est une base de priorisation. On trie les findings en trois catégories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Catégorie 1 : les vraies failles, corrigées
&lt;/h3&gt;

&lt;p&gt;Cinq findings exploitables, corrigés en priorité.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Attaque visée&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A-DC-Spooler&lt;/td&gt;
&lt;td&gt;Désactiver le service Print Spooler sur le DC&lt;/td&gt;
&lt;td&gt;PrintNightmare, coercition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A-RootDseAnonBinding&lt;/td&gt;
&lt;td&gt;Bloquer le LDAP anonyme&lt;/td&gt;
&lt;td&gt;Reconnaissance non authentifiée&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A-AuditDC / A-AuditPowershell&lt;/td&gt;
&lt;td&gt;Audit avancé + journalisation PowerShell&lt;/td&gt;
&lt;td&gt;Aucune détection sans logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A-MinPwdLen&lt;/td&gt;
&lt;td&gt;Longueur minimale de mot de passe = 14&lt;/td&gt;
&lt;td&gt;Force brute, devinette&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S-AesNotEnabled&lt;/td&gt;
&lt;td&gt;Forcer Kerberos AES (RC4 off)&lt;/td&gt;
&lt;td&gt;Kerberoasting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Le service Spooler sur un contrôleur de domaine est un vecteur connu (PrintNightmare, et certaines techniques de coercition d'authentification). Un DC n'imprime rien, on coupe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Stop-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Spooler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Spooler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartupType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Disabled&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffl5itc1sdp8kvproqczl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffl5itc1sdp8kvproqczl.png" alt="Le service Spooler passé à Stopped / Disabled" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L'audit avancé mérite une insistance, parce qu'il fait le lien avec la détection, le travail d'un SOC. Sans politique d'audit, le domaine est aveugle : aucune trace d'un Kerberoasting, d'une connexion suspecte ou d'une commande PowerShell malveillante. On l'active par GPO, ce qui couvre tous les contrôleurs de domaine, au lieu d'une commande locale qui ne couvrirait qu'une machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftbyfg70e2zptobry192v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftbyfg70e2zptobry192v.png" alt="La GPO d'audit avec les sous-catégories Kerberos et Logon activées" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On ajoute la journalisation PowerShell (Module Logging et Script Block Logging), pour tracer l'exécution de scripts offensifs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Catégorie 2 : les risques acceptés, documentés
&lt;/h3&gt;

&lt;p&gt;Certains findings ne sont pas des failles à corriger, mais des choix assumés.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;A-NotEnoughDC&lt;/code&gt; : un seul contrôleur de domaine. C'est notre décision de lab, pas une faille.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;A-LAPS-Not-Installed&lt;/code&gt; : LAPS est un projet à part entière, hors périmètre.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;S-DC-SubnetMissing&lt;/code&gt;, &lt;code&gt;P-RecycleBin&lt;/code&gt; : cosmétique pour un environnement isolé.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En audit réel, on consigne ces findings comme risque accepté, avec leur justification. Le rôle de l'auditeur n'est pas de tout corriger aveuglément, mais de savoir quoi corriger et quoi assumer selon le contexte.&lt;/p&gt;

&lt;h3&gt;
  
  
  Catégorie 3 : les délais d'application
&lt;/h3&gt;

&lt;p&gt;Une subtilité que beaucoup ignorent : certaines remédiations sont posées mais ne deviennent effectives qu'après un événement. Le finding &lt;code&gt;S-AesNotEnabled&lt;/code&gt; en est l'exemple parfait. La GPO force AES, mais les clés AES des comptes ne se génèrent qu'au prochain changement de mot de passe. Juste après la GPO, PingCastle continue donc de remonter le finding. Ce n'est pas un échec, c'est un délai normal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Le verdict : avant / après
&lt;/h3&gt;

&lt;p&gt;On relance PingCastle après remédiation et on compare.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyc046lqujwa535ny235z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyc046lqujwa535ny235z.png" alt="Le rapport PingCastle après remédiation : score 40/100" width="799" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axe&lt;/th&gt;
&lt;th&gt;Avant&lt;/th&gt;
&lt;th&gt;Après&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stale Objects&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privileged Accounts&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Anomalies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GLOBAL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Le score global passe de 55 à 40, soit 15 points de moins, portés par une baisse de 20 points sur l'axe Anomalies, exactement l'axe prioritaire repéré au départ.&lt;/p&gt;

&lt;p&gt;Et c'est là que le principe "le score est le pire des axes" se vérifie en direct. En faisant tomber Anomalies de 55 à 35, on a fait disparaître l'axe qui plafonnait le score. Mais un nouveau plafond apparaît tout seul : Privileged Accounts, resté à 40, est maintenant l'axe qui détermine le global. C'est donc lui la cible de la prochaine itération : nettoyer les délégations, les membres de Schema Admins, protéger les OU. La boucle se referme concrètement. On traite l'axe prioritaire, le plafond se déplace, on recommence.&lt;/p&gt;

&lt;p&gt;La leçon centrale : la remédiation ne fait pas tomber le score à zéro, et c'est normal. Trois cas coexistent. Corrigé (Spooler, audit, longueur de mot de passe). Effectif après délai (AES attend un renouvellement de secret). Risque accepté documenté (un seul DC). Un audit se mesure à la baisse du score sur les axes prioritaires, pas à un 0/100 illusoire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Les leçons à retenir
&lt;/h2&gt;

&lt;p&gt;Au-delà des commandes, voilà ce qui se transpose à n'importe quel environnement.&lt;/p&gt;

&lt;p&gt;Les bonnes pratiques :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cibler des groupes, jamais des comptes individuels. Une règle s'applique à un rôle, pas à une personne.&lt;/li&gt;
&lt;li&gt;Ranger les machines dans la bonne OU. Une GPO ne s'applique qu'à l'OU où elle est liée, et toute machine jointe atterrit d'abord dans &lt;code&gt;Computers&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Imposer les règles techniquement, pas par consigne. Une restriction dans une GPO est appliquée. Une règle écrite dans un document est ignorée.&lt;/li&gt;
&lt;li&gt;Garder une session admin ouverte et tester après chaque GPO. Le verrouillage accidentel est un vrai risque.&lt;/li&gt;
&lt;li&gt;Empiler les GPO, un seul redémarrage à la fin. On ne redémarre pas un DC à chaque mesure.&lt;/li&gt;
&lt;li&gt;Prouver l'effet par un output. Event "LSASS protected", &lt;code&gt;auditpol /get&lt;/code&gt;, erreur 0x2407. Ne jamais supposer.&lt;/li&gt;
&lt;li&gt;Trier un rapport d'audit. Risque accepté documenté contre faille exploitable corrigée. Ne pas viser le 0/100.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Les mauvaises pratiques à éviter :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Réutiliser le même mot de passe partout. Un compromis, et tout tombe.&lt;/li&gt;
&lt;li&gt;Connecter un compte à hauts privilèges sur une machine exposée. Les identifiants restent en mémoire, prêts à être dumpés.&lt;/li&gt;
&lt;li&gt;Exposer le RDP directement sur Internet. On filtre par IP source, ou on passe par un bastion ou un VPN d'administration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Durcir un Active Directory n'est pas une case à cocher, c'est un cycle. On construit, on durcit en pensant comme l'attaquant, on audite pour mesurer, on remédie en priorisant, puis on recommence. Le score ne descendra jamais à zéro, et ce n'est pas le but. L'objectif, c'est de fermer méthodiquement les chemins d'attaque les plus exploitables et de documenter en conscience ce qu'on accepte.&lt;/p&gt;

&lt;p&gt;L'angle attaque/défense n'est pas un gadget. Quand on sait précisément quelle technique une mesure neutralise, on sait aussi la prioriser, la justifier et l'expliquer. C'est la différence entre appliquer une checklist et sécuriser vraiment son environnement.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Lab monté de bout en bout sur Azure. Tous les outils sont gratuits : Active Directory, la console de gestion des stratégies de groupe, et PingCastle en édition gratuite (usage non commercial). Les commandes sont reproductibles telles quelles.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>activedirectory</category>
      <category>security</category>
      <category>windows</category>
      <category>devops</category>
    </item>
    <item>
      <title>Docker hardening : durcir un conteneur en profondeur, du Dockerfile au daemon</title>
      <dc:creator>Hadi Mouter</dc:creator>
      <pubDate>Thu, 07 May 2026 07:07:17 +0000</pubDate>
      <link>https://dev.to/hadimouter/docker-hardening-durcir-un-conteneur-en-profondeur-du-dockerfile-au-daemon-3blm</link>
      <guid>https://dev.to/hadimouter/docker-hardening-durcir-un-conteneur-en-profondeur-du-dockerfile-au-daemon-3blm</guid>
      <description>&lt;p&gt;Voici un Dockerfile qu'on retrouve dans la majorité des dépôts publics :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:latest&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six lignes, ça compile, ça tourne, le CI passe.&lt;/p&gt;

&lt;p&gt;Maintenant, supposons qu'une vulnérabilité dans une dépendance npm donne à un attaquant un RCE dans ce conteneur. Voici ce qu'il a effectivement entre les mains :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un shell qui tourne en root (UID 0)&lt;/li&gt;
&lt;li&gt;L'ensemble des Linux capabilities que Docker conserve par défaut, dont CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_NET_RAW et CAP_KILL&lt;/li&gt;
&lt;li&gt;Un système de fichiers entièrement inscriptible&lt;/li&gt;
&lt;li&gt;L'intégralité des ressources CPU et mémoire de l'hôte (aucune limite n'est posée par défaut)&lt;/li&gt;
&lt;li&gt;Une image dont le contenu peut changer entre deux pulls (&lt;code&gt;:latest&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Une surface d'attaque qui inclut un OS Debian complet, package manager compris&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aucun de ces points n'est un bug. Ce sont les valeurs par défaut.&lt;/p&gt;

&lt;p&gt;Docker est conçu pour être ergonomique, pas pour être sûr, et ces deux objectifs ne sont pas alignés.&lt;/p&gt;

&lt;p&gt;C'est l'idée centrale de cet article : &lt;strong&gt;la sécurité conteneur n'est pas une couche qu'on ajoute, c'est un ensemble de défauts qu'on retire&lt;/strong&gt;. Le travail consiste à partir d'une configuration permissive et à la fermer méthodiquement, en sachant à chaque fois ce qu'on protège et contre quoi.&lt;/p&gt;

&lt;p&gt;On va parcourir le sujet à trois niveaux :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Le &lt;strong&gt;runtime&lt;/strong&gt; (la commande &lt;code&gt;docker run&lt;/code&gt; ou son équivalent compose)&lt;/li&gt;
&lt;li&gt;L'&lt;strong&gt;image&lt;/strong&gt; (le Dockerfile lui-même)&lt;/li&gt;
&lt;li&gt;Le &lt;strong&gt;démon&lt;/strong&gt; (la configuration du daemon Docker côté hôte)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;À la fin, tu auras une config de référence applicable telle quelle, et surtout les justifications pour chaque ligne. C'est ce qui permet de l'adapter intelligemment à un workload qui sort du cas standard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Le modèle mental : isolation vs hardening
&lt;/h2&gt;

&lt;p&gt;Avant les commandes, deux distinctions qui changent tout.&lt;/p&gt;

&lt;h3&gt;
  
  
  VM vs conteneur
&lt;/h3&gt;

&lt;p&gt;Une machine virtuelle s'appuie sur un hyperviseur qui émule du matériel et fournit à chaque OS invité son propre noyau. La frontière entre invité et hôte est une frontière matérielle simulée, et la franchir demande de casser l'hyperviseur.&lt;/p&gt;

&lt;p&gt;Un conteneur, lui, partage le noyau de l'hôte.&lt;/p&gt;

&lt;p&gt;L'isolation se fait au niveau processus, via les namespaces et cgroups du kernel Linux. La frontière est logicielle, et elle s'effondre dès qu'on trouve une vulnérabilité dans le runtime (runC, containerd) ou dans le kernel lui-même.&lt;/p&gt;

&lt;p&gt;CVE-2019-5736 sur runC en est l'exemple le plus connu : un conteneur non-privilégié pouvait écraser le binaire runC sur l'hôte et obtenir l'exécution de code en tant que root au prochain &lt;code&gt;docker exec&lt;/code&gt;. CVE-2024-21626 ("Leaky Vessels", début 2024) a démontré que le sujet reste d'actualité.&lt;/p&gt;

&lt;p&gt;La conséquence est importante. La surface d'attaque réelle d'un conteneur n'est pas le conteneur lui-même, c'est l'ensemble noyau hôte plus runtime. Tout ce qu'on appelle "container hardening" consiste à réduire cette surface, soit en limitant ce que le processus peut atteindre, soit en limitant ce qu'il peut faire avec ce qu'il atteint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolation vs hardening
&lt;/h3&gt;

&lt;p&gt;Ces deux mots sont souvent confondus alors qu'ils renvoient à deux mécanismes distincts.&lt;/p&gt;

&lt;p&gt;L'&lt;strong&gt;isolation&lt;/strong&gt; détermine ce que le conteneur &lt;strong&gt;voit&lt;/strong&gt;. Elle est assurée par les namespaces Linux :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PID (arbre des processus)&lt;/li&gt;
&lt;li&gt;NET (pile réseau)&lt;/li&gt;
&lt;li&gt;MNT (système de fichiers)&lt;/li&gt;
&lt;li&gt;IPC (System V / POSIX)&lt;/li&gt;
&lt;li&gt;UTS (hostname)&lt;/li&gt;
&lt;li&gt;USER (mapping UID/GID)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Casser un namespace, c'est élargir la vue du conteneur jusqu'à inclure des choses qui appartiennent à l'hôte ou à un autre conteneur.&lt;/p&gt;

&lt;p&gt;Le &lt;strong&gt;hardening&lt;/strong&gt; détermine ce que le conteneur peut &lt;strong&gt;faire&lt;/strong&gt;, même s'il voit quelque chose. Il s'appuie sur les capabilities (sous-ensembles du privilège root), les profils seccomp (filtre de syscalls), les politiques MAC (AppArmor/SELinux) et les cgroups (limites de ressources).&lt;/p&gt;

&lt;p&gt;Les deux sont nécessaires.&lt;/p&gt;

&lt;p&gt;Une isolation parfaite avec aucun hardening permet à un conteneur compromis de saturer le CPU de l'hôte. Un hardening agressif avec une isolation cassée (par exemple &lt;code&gt;--pid=host&lt;/code&gt;) laisse le conteneur lire les processus de l'hôte. La défense en profondeur consiste à empiler les deux, et à empiler plusieurs couches dans chaque catégorie.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trois scénarios pour rendre ça concret
&lt;/h2&gt;

&lt;p&gt;Avant de plonger dans la config, trois scénarios qui justifient chaque ligne qu'on va écrire.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container escape via vulnérabilité runtime
&lt;/h3&gt;

&lt;p&gt;CVE-2019-5736 (runC) en est le cas d'école. Un conteneur non-privilégié peut, dans certaines conditions, écraser le binaire &lt;code&gt;/usr/bin/runc&lt;/code&gt; sur l'hôte au moment où un &lt;code&gt;docker exec&lt;/code&gt; est lancé.&lt;/p&gt;

&lt;p&gt;Au prochain démarrage de conteneur, c'est le code de l'attaquant qui s'exécute en tant que root sur la machine.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mitigation :&lt;/em&gt; user namespaces (root du conteneur != root de l'hôte), patches kernel à jour, profils MAC actifs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Abus de capabilities
&lt;/h3&gt;

&lt;p&gt;Un conteneur démarré avec &lt;code&gt;--privileged&lt;/code&gt; désactive tous les mécanismes de sécurité Docker en bloc : seccomp désactivé, AppArmor désactivé, toutes les capabilities ajoutées, accès aux périphériques de l'hôte.&lt;/p&gt;

&lt;p&gt;À partir de là, un attaquant peut monter le filesystem de l'hôte (&lt;code&gt;mount /dev/sda1 /mnt&lt;/code&gt;), écrire dans &lt;code&gt;/etc/shadow&lt;/code&gt;, ou abuser des cgroups v1 release_agent pour faire exécuter un script en tant que root sur l'hôte.&lt;/p&gt;

&lt;p&gt;Même sans &lt;code&gt;--privileged&lt;/code&gt;, certaines capabilities suffisent. CAP_SYS_ADMIN à elle seule ouvre la voie à des dizaines d'évasions documentées.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mitigation :&lt;/em&gt; &lt;code&gt;--cap-drop=ALL&lt;/code&gt; par défaut, ajouts ciblés et justifiés.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposition du socket Docker
&lt;/h3&gt;

&lt;p&gt;Le démon Docker écoute sur &lt;code&gt;/var/run/docker.sock&lt;/code&gt;. Toute personne qui peut écrire dans ce socket peut piloter le démon, donc lancer n'importe quel conteneur, y compris un conteneur &lt;code&gt;--privileged&lt;/code&gt; qui monte &lt;code&gt;/&lt;/code&gt; de l'hôte.&lt;/p&gt;

&lt;p&gt;Monter ce socket dans un conteneur (&lt;code&gt;-v /var/run/docker.sock:/var/run/docker.sock&lt;/code&gt;) est une pratique courante chez les devs qui veulent faire du Docker-in-Docker pour leur CI ou leur Portainer. C'est l'équivalent d'un accès root effectif à l'hôte pour le processus du conteneur.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mitigation :&lt;/em&gt; ne jamais monter le socket en production. Si vraiment nécessaire (CI builders), utiliser un proxy comme &lt;code&gt;docker-socket-proxy&lt;/code&gt; qui filtre les API exposées.&lt;/p&gt;

&lt;p&gt;Ces trois scénarios ne sont pas exhaustifs, mais ils couvrent l'essentiel : kernel/runtime, privilèges trop larges, exposition d'API. Chaque ligne de la config qui suit répond à au moins l'un des trois.&lt;/p&gt;




&lt;h2&gt;
  
  
  Anatomie d'une config durcie, ligne par ligne
&lt;/h2&gt;

&lt;p&gt;Voici la commande de référence. On va la décortiquer en six blocs thématiques.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user&lt;/span&gt; 1000:1000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--read-only&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp:rw,noexec,nosuid,size&lt;span class="o"&gt;=&lt;/span&gt;64m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-drop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-opt&lt;/span&gt; no-new-privileges &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-opt&lt;/span&gt; &lt;span class="nv"&gt;seccomp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/docker/seccomp/default.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-opt&lt;/span&gt; &lt;span class="nv"&gt;apparmor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker-default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pids-limit&lt;/span&gt; 100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt; 512m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory-swap&lt;/span&gt; 512m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpus&lt;/span&gt; 1.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; myapp-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; myapp-data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /etc/myapp/config.yml:/app/config.yml:ro &lt;span class="se"&gt;\&lt;/span&gt;
  myapp@sha256:a1b2c3...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tu remarqueras que &lt;code&gt;--cap-add=NET_BIND_SERVICE&lt;/code&gt;, qu'on voit partout dans les configs partagées en ligne, n'est plus là. On y revient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bloc 1 : identité
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;--&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;:&lt;span class="m"&gt;1000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Par défaut, le processus à l'intérieur du conteneur tourne en root (UID 0). Si l'image n'a pas d'instruction &lt;code&gt;USER&lt;/code&gt; et que les user namespaces ne sont pas actifs, ce root du conteneur est aussi root sur l'hôte.&lt;/p&gt;

&lt;p&gt;Une évasion donne alors directement les droits root de la machine.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--user 1000:1000&lt;/code&gt; force le processus à tourner avec un UID/GID arbitraire non-privilégié.&lt;/p&gt;

&lt;p&gt;Cette protection est complétée au niveau du démon par les &lt;strong&gt;user namespaces&lt;/strong&gt; (&lt;code&gt;userns-remap&lt;/code&gt; dans &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;), qui remappent l'intégralité des UIDs du conteneur vers une plage non privilégiée sur l'hôte. C'est la protection la plus efficace contre les évasions, et elle est désactivée par défaut. On verra la config dans la section dédiée au démon.&lt;/p&gt;

&lt;p&gt;Attention : &lt;code&gt;--user&lt;/code&gt; ne suffit pas si l'image elle-même a besoin d'écrire dans des chemins appartenant à root. C'est pour ça que l'instruction &lt;code&gt;USER&lt;/code&gt; doit aussi figurer dans le Dockerfile, pour que les permissions de l'image soient cohérentes avec l'UID runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bloc 2 : capabilities
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;--&lt;span class="n"&gt;cap&lt;/span&gt;-&lt;span class="n"&gt;drop&lt;/span&gt;=&lt;span class="n"&gt;ALL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linux fragmente le privilège root en une quarantaine de capabilities (CAP_CHOWN, CAP_NET_RAW, CAP_SYS_ADMIN, etc.).&lt;/p&gt;

&lt;p&gt;Docker conserve par défaut un sous-ensemble qui inclut encore CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_FOWNER, CAP_NET_RAW, CAP_KILL, CAP_SYS_CHROOT et quelques autres. Ce n'est pas anodin. CAP_NET_RAW permet par exemple de forger des paquets ARP et de lancer du spoofing sur le réseau du conteneur. CAP_DAC_OVERRIDE permet de contourner les permissions DAC sur les fichiers.&lt;/p&gt;

&lt;p&gt;La règle est simple : &lt;code&gt;--cap-drop=ALL&lt;/code&gt; au démarrage, puis &lt;code&gt;--cap-add&lt;/code&gt; pour rajouter ce qui est strictement nécessaire et seulement ça.&lt;/p&gt;

&lt;h4&gt;
  
  
  Le piège classique : NET_BIND_SERVICE
&lt;/h4&gt;

&lt;p&gt;On voit souvent &lt;code&gt;--cap-add=NET_BIND_SERVICE&lt;/code&gt; dans les configs partagées en ligne. Cette capability autorise le processus à se binder sur un port &amp;lt; 1024.&lt;/p&gt;

&lt;p&gt;Elle n'est utile que si ton processus écoute directement sur le port 80 ou 443 à l'intérieur du conteneur.&lt;/p&gt;

&lt;p&gt;Or dans une architecture moderne, ton app écoute sur 8080 ou 3000. C'est Docker qui fait le mapping &lt;code&gt;-p 80:8080&lt;/code&gt; côté hôte. Le bind sur le port 80 est fait par le démon, pas par ton processus.&lt;/p&gt;

&lt;p&gt;Si en plus tu as un reverse proxy (nginx, Traefik, Caddy) devant, ce dernier gère les ports privilégiés, et ton app n'a pas du tout besoin de cette capability.&lt;/p&gt;

&lt;p&gt;Conclusion pragmatique : démarre avec &lt;code&gt;--cap-drop=ALL&lt;/code&gt; sans rien ajouter. Si ton app crashe, le message d'erreur te dira ce qui manque, et tu ajouteras la capability ciblée. Ne pré-ajoute rien "au cas où".&lt;/p&gt;

&lt;h3&gt;
  
  
  Bloc 3 : syscalls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;--&lt;span class="n"&gt;security&lt;/span&gt;-&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="n"&gt;seccomp&lt;/span&gt;=/&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;docker&lt;/span&gt;/&lt;span class="n"&gt;seccomp&lt;/span&gt;/&lt;span class="n"&gt;default&lt;/span&gt;.&lt;span class="n"&gt;json&lt;/span&gt;
--&lt;span class="n"&gt;security&lt;/span&gt;-&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;-&lt;span class="n"&gt;new&lt;/span&gt;-&lt;span class="n"&gt;privileges&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seccomp filtre les appels système que le processus peut faire au noyau.&lt;/p&gt;

&lt;p&gt;Le profil par défaut de Docker bloque environ 44 syscalls jugés dangereux ou inutiles pour la majorité des workloads applicatifs : &lt;code&gt;keyctl&lt;/code&gt; (manipulation du keyring kernel), &lt;code&gt;unshare&lt;/code&gt; (création de nouveaux namespaces), &lt;code&gt;mount&lt;/code&gt;, &lt;code&gt;pivot_root&lt;/code&gt;, &lt;code&gt;kexec_load&lt;/code&gt;, &lt;code&gt;bpf&lt;/code&gt;, et d'autres. En pratique, ce profil est suffisant pour 95 % des applications.&lt;/p&gt;

&lt;p&gt;Préciser explicitement &lt;code&gt;--security-opt seccomp=...&lt;/code&gt; plutôt que de se reposer sur le défaut implicite a deux utilités. D'abord, ça rend la configuration auditable (un grep sur ton infra te dit immédiatement quels conteneurs ont seccomp activé). Ensuite, ça évite que quelqu'un retire silencieusement la protection.&lt;/p&gt;

&lt;p&gt;Voir &lt;code&gt;--security-opt seccomp=unconfined&lt;/code&gt; quelque part en production est un drapeau rouge immédiat. La seule raison légitime de désactiver seccomp est le debug ponctuel d'un syscall bloqué, et ça doit revenir à &lt;code&gt;default&lt;/code&gt; aussitôt après.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--security-opt no-new-privileges&lt;/code&gt; applique le flag &lt;code&gt;PR_SET_NO_NEW_PRIVS&lt;/code&gt; au processus. Cela empêche le processus et tous ses descendants d'élever leurs privilèges via un binaire SUID/SGID.&lt;/p&gt;

&lt;p&gt;Sans cette option, un binaire SUID root présent dans l'image (&lt;code&gt;ping&lt;/code&gt;, &lt;code&gt;mount&lt;/code&gt; dans certains cas) peut être exploité pour regagner des privilèges malgré &lt;code&gt;--user&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Le coût opérationnel est nul, le bénéfice est réel, donc on l'active toujours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bloc 4 : MAC (Mandatory Access Control)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;--&lt;span class="n"&gt;security&lt;/span&gt;-&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="n"&gt;apparmor&lt;/span&gt;=&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AppArmor (Ubuntu, Debian) et SELinux (RHEL, Fedora, CentOS) sont des modules kernel qui appliquent des politiques d'accès &lt;strong&gt;au niveau de l'hôte&lt;/strong&gt;, en plus des permissions DAC standards.&lt;/p&gt;

&lt;p&gt;Docker installe un profil par défaut (&lt;code&gt;docker-default&lt;/code&gt; pour AppArmor, label &lt;code&gt;container_t&lt;/code&gt; pour SELinux) qui restreint les conteneurs au-delà de ce que les capabilities et seccomp couvrent : interdiction d'écrire dans &lt;code&gt;/proc/sysrq-trigger&lt;/code&gt;, dans &lt;code&gt;/sys/firmware&lt;/code&gt;, dans certains chemins sensibles du sysfs.&lt;/p&gt;

&lt;p&gt;Préciser &lt;code&gt;apparmor=docker-default&lt;/code&gt; explicitement a la même logique que pour seccomp : auditabilité et défense contre une dérive de configuration. Sur un hôte SELinux, l'équivalent est &lt;code&gt;--security-opt label=type:container_t&lt;/code&gt; (généralement appliqué automatiquement par le runtime).&lt;/p&gt;

&lt;p&gt;Important : ces protections supposent que le module est activé sur l'hôte. Sur une distribution sans AppArmor ni SELinux, l'option est ignorée silencieusement. C'est pourquoi le hardening conteneur n'a de sens qu'avec un hôte lui-même durci.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bloc 5 : système de fichiers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;--&lt;span class="n"&gt;read&lt;/span&gt;-&lt;span class="n"&gt;only&lt;/span&gt;
--&lt;span class="n"&gt;tmpfs&lt;/span&gt; /&lt;span class="n"&gt;tmp&lt;/span&gt;:&lt;span class="n"&gt;rw&lt;/span&gt;,&lt;span class="n"&gt;noexec&lt;/span&gt;,&lt;span class="n"&gt;nosuid&lt;/span&gt;,&lt;span class="n"&gt;size&lt;/span&gt;=&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;
-&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt;-&lt;span class="n"&gt;data&lt;/span&gt;:/&lt;span class="n"&gt;data&lt;/span&gt;
-&lt;span class="n"&gt;v&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;myapp&lt;/span&gt;/&lt;span class="n"&gt;config&lt;/span&gt;.&lt;span class="n"&gt;yml&lt;/span&gt;:/&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;config&lt;/span&gt;.&lt;span class="n"&gt;yml&lt;/span&gt;:&lt;span class="n"&gt;ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--read-only&lt;/code&gt; rend le rootfs du conteneur immuable. Un attaquant ne peut plus modifier les binaires du conteneur ni y déposer un implant persistant.&lt;/p&gt;

&lt;p&gt;La majorité des applications web n'écrivent jamais sur leur rootfs, donc cette option passe sans douleur.&lt;/p&gt;

&lt;p&gt;Pour le cas où l'app a besoin d'un répertoire temporaire, on remonte &lt;code&gt;/tmp&lt;/code&gt; en &lt;code&gt;tmpfs&lt;/code&gt; (donc en mémoire, pas sur disque), avec deux options de durcissement :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;noexec&lt;/code&gt; : pas d'exécution de binaire depuis ce mount&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nosuid&lt;/code&gt; : les bits SUID/SGID y sont ignorés&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La taille est bornée pour éviter qu'un attaquant ne sature la RAM via ce mount.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-v myapp-data:/data&lt;/code&gt; utilise un &lt;strong&gt;named volume&lt;/strong&gt; géré par Docker. C'est nettement préférable à un bind mount d'un chemin de l'hôte. Les named volumes vivent dans &lt;code&gt;/var/lib/docker/volumes/&lt;/code&gt;, sont gérés par le démon, et n'exposent pas de structure du filesystem hôte au conteneur.&lt;/p&gt;

&lt;p&gt;Les bind mounts (&lt;code&gt;-v /home/user/data:/data&lt;/code&gt;) sont l'un des principaux vecteurs d'évasion. Un bind mount sur &lt;code&gt;/&lt;/code&gt;, sur &lt;code&gt;/etc&lt;/code&gt;, ou sur &lt;code&gt;/proc&lt;/code&gt; casse l'isolation du namespace MNT.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-v /etc/myapp/config.yml:/app/config.yml:ro&lt;/code&gt; est le seul bind mount toléré ici, et il est en lecture seule (&lt;code&gt;:ro&lt;/code&gt;). C'est un pattern courant : la configuration est sur l'hôte (gérée par Ansible, Terraform, ou montée depuis un secret manager), le conteneur la lit, point.&lt;/p&gt;

&lt;p&gt;Le &lt;code&gt;:ro&lt;/code&gt; est non négociable.&lt;/p&gt;

&lt;p&gt;Et le grand absent : &lt;strong&gt;jamais de &lt;code&gt;-v /var/run/docker.sock:...&lt;/code&gt;&lt;/strong&gt; en production. Comme vu dans la section menaces, ça équivaut à donner les clés du démon, donc un accès root effectif à l'hôte.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bloc 6 : ressources
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;--&lt;span class="n"&gt;pids&lt;/span&gt;-&lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
--&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;
--&lt;span class="n"&gt;memory&lt;/span&gt;-&lt;span class="n"&gt;swap&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;
--&lt;span class="n"&gt;cpus&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ces options reposent sur les cgroups Linux et limitent ce que le conteneur peut consommer. Par défaut, Docker n'impose aucune limite : un conteneur peut allouer toute la RAM de l'hôte, faire un fork bomb, saturer le CPU.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--pids-limit 100&lt;/code&gt; borne le nombre de processus/threads que le conteneur peut créer. C'est la protection la plus simple contre les fork bombs, qu'elles soient malveillantes ou accidentelles (un bug dans ton code qui boucle sur &lt;code&gt;fork()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--memory 512m&lt;/code&gt; plafonne la RAM. Au-delà, le conteneur se fait OOM-killer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--memory-swap 512m&lt;/code&gt; réglé à la même valeur que &lt;code&gt;--memory&lt;/code&gt; désactive le swap pour ce conteneur. Cela évite des comportements de performance imprévisibles et coupe une voie d'épuisement du swap de l'hôte.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--cpus 1.0&lt;/code&gt; fixe un quota CPU équivalent à un cœur. Sur une machine multi-cœurs, ça laisse les autres conteneurs et l'hôte respirer même si le conteneur boucle.&lt;/p&gt;

&lt;p&gt;L'argument sécurité est moins évident que pour les capabilities, mais il est réel. Un conteneur compromis ne devient pas un vecteur de DoS sur les voisins ou sur l'hôte. C'est de la résilience, et la résilience fait partie de la sécurité.&lt;/p&gt;

&lt;h3&gt;
  
  
  Et le réseau
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;--&lt;span class="n"&gt;network&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt;-&lt;span class="n"&gt;net&lt;/span&gt;
-&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;:&lt;span class="m"&gt;8080&lt;/span&gt;:&lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--network myapp-net&lt;/code&gt; place le conteneur sur un bridge user-defined plutôt que sur le bridge par défaut (&lt;code&gt;docker0&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Le bridge par défaut autorise tous les conteneurs à se parler. Un bridge dédié couplé à &lt;code&gt;icc: false&lt;/code&gt; dans le démon (voir plus loin) coupe cette communication non sollicitée.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-p 127.0.0.1:8080:8080&lt;/code&gt; est un détail qui change tout. Sans préfixe d'IP, &lt;code&gt;-p 8080:8080&lt;/code&gt; bind sur &lt;code&gt;0.0.0.0&lt;/code&gt;, donc sur toutes les interfaces de l'hôte, y compris celles exposées sur Internet.&lt;/p&gt;

&lt;p&gt;Sur un VPS bien configuré côté firewall, ce n'est pas catastrophique. Sur un poste de dev ou un serveur sans firewall strict, ça expose ton conteneur au monde.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;127.0.0.1:8080:8080&lt;/code&gt; ne bind que sur loopback, ce qui est ce que tu veux dans 90 % des cas (typiquement quand un reverse proxy en frontal route vers le conteneur).&lt;/p&gt;




&lt;h2&gt;
  
  
  Côté image : le Dockerfile
&lt;/h2&gt;

&lt;p&gt;Le runtime n'est que la moitié du sujet. L'autre moitié est ce qui est dans l'image elle-même, et ça se construit dans le Dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-ldflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-s -w"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /out/myapp ./cmd/myapp

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/static-debian12:nonroot&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; org.opencontainers.image.source="https://github.com/org/myapp"&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /out/myapp /usr/local/bin/myapp&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nonroot:nonroot&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/usr/local/bin/myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quatre points méritent qu'on s'y arrête.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-stage
&lt;/h3&gt;

&lt;p&gt;L'étape &lt;code&gt;builder&lt;/code&gt; contient toute la toolchain : compilateur, dépendances, code source. L'image finale n'embarque que le binaire compilé.&lt;/p&gt;

&lt;p&gt;Conséquence directe : un attaquant qui obtient un RCE dans le conteneur n'a pas de &lt;code&gt;gcc&lt;/code&gt;, de &lt;code&gt;git&lt;/code&gt;, de &lt;code&gt;curl&lt;/code&gt;, ni de &lt;code&gt;npm&lt;/code&gt; à sa disposition pour construire son outillage post-exploitation.&lt;/p&gt;

&lt;p&gt;La surface est fortement réduite.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distroless
&lt;/h3&gt;

&lt;p&gt;L'image finale dérive de &lt;code&gt;gcr.io/distroless/static-debian12&lt;/code&gt;. Pas de shell, pas de package manager, pas d'utilitaires Unix. Pas de &lt;code&gt;sh&lt;/code&gt;, pas de &lt;code&gt;bash&lt;/code&gt;, pas de &lt;code&gt;apt&lt;/code&gt;, pas de &lt;code&gt;wget&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Un attaquant qui aurait quand même un RCE doit travailler uniquement avec les capacités de ton binaire et celles du kernel.&lt;/p&gt;

&lt;p&gt;La variante &lt;code&gt;:nonroot&lt;/code&gt; fournit en plus un utilisateur UID 65532, qu'on active avec &lt;code&gt;USER nonroot:nonroot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour un binaire statique compilé en Go, Rust ou C, c'est l'image la plus minimale possible. Pour Node.js ou Python, regarder du côté de &lt;code&gt;gcr.io/distroless/nodejs&lt;/code&gt; ou &lt;code&gt;gcr.io/distroless/python3&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Épinglage par digest, pas par tag
&lt;/h3&gt;

&lt;p&gt;Dans l'exemple ci-dessus, &lt;code&gt;gcr.io/distroless/static-debian12:nonroot&lt;/code&gt; est un tag mutable. Son contenu change quand Google republie l'image (ce qui est fréquent, pour de bonnes raisons de patches sécurité).&lt;/p&gt;

&lt;p&gt;Pour une chaîne de build reproductible et auditable, on remplace le tag par un digest immuable :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/static-debian12@sha256:6b3b06...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le digest est obtenu via &lt;code&gt;docker pull &amp;lt;image&amp;gt;:tag&lt;/code&gt; puis &lt;code&gt;docker inspect&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Au prix d'un peu de discipline (mettre à jour le digest à chaque audit), on garantit qu'aucune supply chain attack ne peut substituer une image silencieusement entre deux builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Souvent oublié, parfois critique.&lt;/p&gt;

&lt;p&gt;Sans &lt;code&gt;.dockerignore&lt;/code&gt;, &lt;code&gt;COPY . .&lt;/code&gt; copie tout le contexte de build dans l'image, y compris &lt;code&gt;.git/&lt;/code&gt;, &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;node_modules/&lt;/code&gt; (si tu builds en local avant), et tout fichier sensible que tu aurais laissé traîner.&lt;/p&gt;

&lt;p&gt;Un &lt;code&gt;.dockerignore&lt;/code&gt; minimal contient au moins :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.git
.env*
*.log
node_modules
.DS_Store
.idea
.vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ce n'est pas du hardening au sens strict, c'est de l'hygiène. L'oubli est un classique des audits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Le démon Docker : &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Tout ce qu'on a vu jusqu'ici se configure par conteneur. Certains réglages se font une seule fois, au niveau du démon, et s'appliquent à tous les conteneurs.&lt;/p&gt;

&lt;p&gt;C'est la couche que les développeurs ignorent parce qu'elle relève de l'admin de l'hôte. Et c'est précisément pour ça qu'elle mérite d'être traitée.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userns-remap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"no-new-privileges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"live-restore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"log-driver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json-file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"log-opts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max-size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max-file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;userns-remap: default&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Active les user namespaces sur l'ensemble des conteneurs.&lt;/p&gt;

&lt;p&gt;Root (UID 0) à l'intérieur d'un conteneur est mappé vers un UID non privilégié (par exemple 100000) sur l'hôte. Une évasion qui aboutit avec UID 0 dans le conteneur se retrouve avec UID 100000 sur l'hôte. Elle ne peut donc pas écrire dans &lt;code&gt;/etc/shadow&lt;/code&gt;, ni monter de filesystem, ni faire quoi que ce soit qui demande root réel.&lt;/p&gt;

&lt;p&gt;C'est la protection la plus efficace contre les container escapes.&lt;/p&gt;

&lt;p&gt;Elle est désactivée par défaut parce qu'elle introduit quelques contraintes sur les volumes (les UIDs des fichiers doivent être adaptés). Le coût en vaut largement la peine.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;no-new-privileges: true&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Au niveau démon, applique le flag &lt;code&gt;PR_SET_NO_NEW_PRIVS&lt;/code&gt; à tous les conteneurs sans qu'on ait besoin de le préciser à chaque &lt;code&gt;docker run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C'est de la défense en profondeur : si un dev oublie l'option dans une commande, le démon la rajoute.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;icc: false&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Désactive l'inter-container communication sur le bridge par défaut.&lt;/p&gt;

&lt;p&gt;Sans cette option, deux conteneurs sur &lt;code&gt;docker0&lt;/code&gt; peuvent se parler librement. Avec &lt;code&gt;icc: false&lt;/code&gt;, ils sont isolés sauf si on les place explicitement sur le même réseau user-defined.&lt;/p&gt;

&lt;p&gt;C'est de la segmentation réseau gratuite.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;live-restore: true&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Permet aux conteneurs de continuer à tourner si le démon redémarre (pour une mise à jour, par exemple).&lt;/p&gt;

&lt;p&gt;Ce n'est pas du hardening au sens strict, mais ça réduit la fenêtre où on serait tenté de désactiver des protections "le temps d'un fix urgent". Moins de pression opérationnelle = moins de raccourcis sécurité.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rotation des logs
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;max-size&lt;/code&gt; et &lt;code&gt;max-file&lt;/code&gt; préviennent qu'un conteneur bavard remplisse &lt;code&gt;/var/lib/docker/containers/...&lt;/code&gt; et finisse par saturer le disque de l'hôte.&lt;/p&gt;

&lt;p&gt;Saturer le disque hôte est un vecteur de DoS comme un autre.&lt;/p&gt;




&lt;h2&gt;
  
  
  La checklist, en synthèse
&lt;/h2&gt;

&lt;p&gt;Voici la grille d'évaluation qui résume tout. Elle peut servir de checklist d'audit, ou de gabarit pour un linter d'images comme &lt;code&gt;hadolint&lt;/code&gt;, &lt;code&gt;trivy config&lt;/code&gt; ou &lt;code&gt;checkov&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drop privileges&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exécution non-root (&lt;code&gt;--user&lt;/code&gt;, &lt;code&gt;USER&lt;/code&gt; dans le Dockerfile)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--cap-drop=ALL&lt;/code&gt; puis ajouts ciblés&lt;/li&gt;
&lt;li&gt;User namespaces actifs au niveau démon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Restrict syscalls&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Profil seccomp explicite (le default suffit dans la majorité des cas)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-new-privileges&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Apply MAC&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AppArmor &lt;code&gt;docker-default&lt;/code&gt; ou SELinux &lt;code&gt;container_t&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Vérifier que le module est actif sur l'hôte&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limit volume risk&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Named volumes plutôt que bind mounts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:ro&lt;/code&gt; quand l'écriture n'est pas nécessaire&lt;/li&gt;
&lt;li&gt;Jamais de &lt;code&gt;/var/run/docker.sock&lt;/code&gt;, &lt;code&gt;/proc&lt;/code&gt;, &lt;code&gt;/sys&lt;/code&gt; montés dans un conteneur applicatif&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Runtime options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--read-only&lt;/code&gt; + tmpfs pour les chemins inscriptibles&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--memory&lt;/code&gt;, &lt;code&gt;--cpus&lt;/code&gt;, &lt;code&gt;--pids-limit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bind sur &lt;code&gt;127.0.0.1&lt;/code&gt; plutôt que &lt;code&gt;0.0.0.0&lt;/code&gt; quand un reverse proxy est devant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Build minimal images&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-stage&lt;/li&gt;
&lt;li&gt;Distroless ou scratch pour les binaires statiques&lt;/li&gt;
&lt;li&gt;Épinglage par digest, pas par tag&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.dockerignore&lt;/code&gt; à jour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La formule à retenir : &lt;strong&gt;chaque case décochée doit être un choix conscient, pas un défaut hérité&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En entretien comme en code review, la mauvaise réponse est "je ne savais pas". La bonne réponse est "j'ai évalué que pour ce workload, le coût opérationnel dépasse le bénéfice, et voici la mitigation alternative en place".&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cas concret : Mini Secrets Manager&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;J'ai appliqué cette checklist à un projet personnel, un mini gestionnaire de secrets en NestJS/PostgreSQL. Le pipeline CI/CD intégrait Trivy pour le scan d'image, Snyk pour les dépendances, et Trufflehog pour la détection de secrets en commit.&lt;/p&gt;

&lt;p&gt;Au premier passage, Trivy a remonté une CVE de niveau HIGH sur l'image de base que j'avais épinglée trois semaines plus tôt. Le correctif a consisté à bumper le digest, valider que les tests passent, republier.&lt;/p&gt;

&lt;p&gt;Sans ce pipeline, la vulnérabilité serait restée en prod jusqu'au prochain audit manuel, c'est-à-dire potentiellement jamais.&lt;/p&gt;

&lt;p&gt;La leçon n'est pas que ma config était mauvaise (elle suivait la checklist ci-dessus), c'est qu'une config durcie sans détection continue est une photographie qui périme. Les deux vont ensemble.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Ce que cet article ne couvre pas
&lt;/h2&gt;

&lt;p&gt;Le sujet "sécuriser un conteneur" déborde largement de Docker en mode standalone. Quatre prolongements importants qu'il faut au moins savoir nommer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubernetes
&lt;/h3&gt;

&lt;p&gt;En production sérieuse, Docker seul est rare.&lt;/p&gt;

&lt;p&gt;Kubernetes ajoute des primitives spécifiques qui transposent les concepts vus ici :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;securityContext.capabilities&lt;/code&gt; pour les capabilities&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;securityContext.runAsNonRoot&lt;/code&gt; pour l'identité&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;seccompProfile&lt;/code&gt; pour les syscalls&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NetworkPolicies&lt;/code&gt; pour la segmentation réseau&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et surtout les Pod Security Standards (Restricted, Baseline, Privileged) qui codifient des baselines applicables au niveau namespace. La philosophie du moindre privilège transpose directement, les commandes changent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rootless Docker
&lt;/h3&gt;

&lt;p&gt;Docker peut tourner entièrement sans privilèges (le démon lui-même tourne en utilisateur non-root).&lt;/p&gt;

&lt;p&gt;Ça change le modèle de menace : l'évasion de conteneur ne donne plus root sur l'hôte par construction. Le coût est en compatibilité (certaines fonctionnalités réseau et de stockage sont limitées).&lt;/p&gt;

&lt;p&gt;Podman, l'alternative de Red Hat, fonctionne en rootless par défaut.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sandboxing renforcé
&lt;/h3&gt;

&lt;p&gt;Pour les workloads multi-tenant ou les environnements à très haute sensibilité, le partage du noyau hôte reste un risque.&lt;/p&gt;

&lt;p&gt;gVisor (Google) interpose un "noyau utilisateur" entre le conteneur et le kernel hôte, qui filtre les syscalls.&lt;/p&gt;

&lt;p&gt;Kata Containers va plus loin et lance chaque conteneur dans une micro-VM légère (KVM).&lt;/p&gt;

&lt;p&gt;Le coût en performance n'est plus négligeable, mais l'isolation se rapproche du niveau VM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supply chain
&lt;/h3&gt;

&lt;p&gt;Le hardening d'un conteneur est inutile si l'image qu'on déploie a été substituée en amont.&lt;/p&gt;

&lt;p&gt;Les outils à connaître :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cosign&lt;/code&gt; pour signer cryptographiquement les images&lt;/li&gt;
&lt;li&gt;Les attestations SLSA pour prouver la provenance d'un build&lt;/li&gt;
&lt;li&gt;Les SBOM (Software Bill of Materials, format SPDX ou CycloneDX) pour documenter les composants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sigstore et Notary v2 sont les écosystèmes de référence.&lt;/p&gt;




&lt;p&gt;Chacun de ces sujets mérite son propre article. Mais le socle reste celui qu'on a parcouru ici : sans une config conteneur correctement durcie, ajouter Kubernetes, gVisor ou cosign ne sert pas à grand-chose. C'est l'ordre qui compte.&lt;/p&gt;

&lt;p&gt;Le réflexe à garder : avant de copier une config trouvée en ligne (y compris celle-ci), ouvre &lt;code&gt;docker inspect&lt;/code&gt; sur tes conteneurs en prod et compare. Tu seras surpris de ce qui tourne avec des défauts que personne n'a jamais remis en question.&lt;/p&gt;

&lt;p&gt;C'est par là qu'on commence.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>devops</category>
      <category>docker</category>
      <category>security</category>
    </item>
  </channel>
</rss>
