Gestion centralisée des mots de passe d’administrateurs locaux

I. Présentation

 

Si vous n’avez pas encore choisi une méthode de gestion centralisée de vos comptes d’administrateurs locaux pour votre parc de machines Windows, alors cet article est susceptible de vous intéresser. Normalement, à l’issue d’une jonction d’ordinateur à un domaine Active Directory, la politique par défaut consiste à désactiver le compte d’administrateur intégré (Alias « builtin » dont le SID se termine par 500) à l’issue de l’installation. Les « bonnes pratiques » nous encouragent à limiter (voir à supprimer) tous les comptes d’administration locaux afin de n’utiliser que des comptes de domaine, eux-mêmes membres du groupe local « Administrateurs ».

Ça c’est pour la théorie, car dans la pratique, on conserve très souvent un compte d’administration local, ne serait-ce que pour les dépannages en mode déconnecté ou lorsque le domaine est inaccessible.

Nous avons donc un compte local « actif » particulièrement sensible et exposé aux attaques, dont le principal rempart réside dans un mot de passe, généralement défini lors de l’installation et communiqué aux techniciens chargés des interventions. (N’oubliez-pas que la sécurité est un tout : cf http://cnf1g.com/?p=441 )

 

II. Choisir une protection

 

Voilà maintenant plusieurs mois que les machines sont déployées, mais le mot de passe est toujours le même (sig 🙁 ) et ce petit secret commence à se rependre comme une traînée de poudre. (Tout le monde ne sait pas tenir sa langue – Social engineering vous connaissez ?)

Et comble de malchance, vous n’avez pas encore implémenté la gestion à distance sur ces machines… sinon un bon coup de Powershell à distance, et c’était plié. Franchement la guigne quoi !…

C’est à ce moment précis que vous allez prendre une bonne ou une mauvaise décision !

 

A. LAPS – La solution Microsoft

 

Un palliatif acceptable consisterait à adopter la solution pour laquelle je vous laisse consulter l’excellent article de Florian. http://www.it-connect.fr/securite-proteger-les-comptes-administrateur-local-avec-laps/

Cette solution utilise Active Directory pour le stockage des mots de passe des comptes locaux.

L’utilisation de LAPS dans votre infrastructure peut toutefois présenter quelques contraintes, telles que l’extension du schéma Active Directory, l’installation d’un MSI à distribuer sur les machines cibles et la  limitation aux machines membres du domaine. De plus le mot de passe étant aléatoire et propre à chaque machine, il faudra communiquer ce fameux code « complexe » à l’intervenant (Pas toujours aisé par téléphone 🙁 )

Mais si ces contraintes vous semblent acceptables dans votre environnement d’entreprise, alors foncez, c’est gratuit…

 

B. La technique de Jason Fossen – Une solution blindée

 

Manifestement, la solution proposée par Jason Fossen (cyber-defense.sans.org) sur le lien suivant est très aboutie en termes de sécurité.

https://cyber-defense.sans.org/blog/2013/08/01/reset-local-administrator-password-automatically-with-a-different-password-across-the-enterprise

Dans cette approche, le changement de mot de passe s’effectue avec un mécanisme aléatoire et au travers du chiffrement asymétrique des certificats à clé publique (PKI). Les mots de passe sont archivés dans un dossier partagé dont le contenu est protégé et la solution ne nécessite aucune appartenance de domaine Active Directory.

Le recours aux certificats constitue certainement l’une des meilleures pistes pour la protection de ces informations sensibles.

 

C. Usage d’un script encodé sous Powershell

 

L’interpréteur Powershell accepte de traiter des commandes encodées, et/ou de manipuler des chaines sécurisées. C’est donc l’approche que propose Felipe Binotto sur son blog et vous trouverez l’explication de sa solution ici . http://fbinotto.blogspot.fr/2014/03/securely-change-local-administrator.html

Afin de parfaire cette solution, je vous invite à consulter préalablement mon article sur la sécurité des scripts « Compilez vos scripts »

 

D. Les préférences de GPO – La plus mauvaise solution

 

Bien que ce raisonnement soit légitime, face à  ce cas de figure et notre connaissance de Windows, la logique la plus simple consisterait à utiliser ce formidable outillage que constituent les préférences de GPO. En effet, quoi de plus facile que d’utiliser la fonction native suivante au sein d’un GPO :

« Configuration ordinateur … Préférences … Paramètres du panneau de configuration … Utilisateurs et groupes locaux » puis « Nouveau … Utilisateur local »

Laps-Alt-Img1

Pour plus de détails, cf http://www.grouppolicy.biz/2013/11/why-passwords-in-group-policy-preference-are-very-bad/

Note : Depuis Windows 8, Microsoft affiche un  message d’avertissement plus qu’explicite, lors de la saisie de mot de passe dans l’éditeur de stratégie.

Laps-Alt-Img2

Dans cette capture, les composants RSAT de Windows 10 n’étant pas encore disponibles en français. L’affichage du message est en anglais, mais le problème reste entier – et Microsoft vous prévient du risque…

Si vous cliquez sur le bouton d’aide comme mentionné, vous serez redirigé vers le lien suivant : https://support.microsoft.com/fr-fr/kb/2962486 « MS14-025 : Une vulnérabilité des préférences de stratégie de groupe peut permettre l’élévation de privilèges : 13 mai 2014 »

La faille réside dans le fait que les réglages associés aux préférences sont stockés dans des fichiers .xml, eux-mêmes disponibles dans un sous-dossier du partage « \\domaine.tld\SYSVOL\domaine.tld\Policies », accessible à tout utilisateur du domaine.

Les fichiers « sensibles » sont essentiellement :

  • Mappages de lecteurs : drives.xml (*1) restriction d’usage depuis MS15-015
  • Utilisateurs et groupes locaux : groups.xml
  • Tâches planifiées : scheduledtasks.xml
  • Services : services.xml
  • Sources de données datasources.xml

(*1) – Sur les préférences de connexion à un lecteur réseau, la fonctionnalité « Connecter en tant que » a été dépréciée via le correctif MS15 015 et est dorénavant grisée lors de l’édition du GPO sous Windows 8 et ultérieurs. Pour plus d’information : http://www.grouppolicy.biz/2014/05/group-policy-preferences-password-behaviour-change-ms14-025/ .

 

1. La preuve par l’exemple

 

Créez un GPO de test en y déclarant un mot de passe pour le compte administrateur intégré via les préférences, comme dans l’écran précédent.

A partir d’un poste du domaine, ouvrez une session ou une invite de commande Powershell avec un compte d’utilisateur standard, puis rentrez la commande suivante

Get-ChildItem -Path \\$env:USERDNSDOMAIN\SYSVOL\$env:USERDNSDOMAIN\Policies -Include groups.xml -Recurse

Vous devriez trouver le (ou les) fichiers « groups.xml » existants, que vous pourriez éditer avec un simple bloc-notes, par exemple en ajoutant la suite de la commande précédente :

| % { notepad $_.FullName }

Avant de poursuivre, créez un nouveau script que vous nommerez « Get-DecryptedCpassword.ps1 » dans lequel vous ajouterez le contenu suivant :

# Script Get-DecryptedCpassword.ps1
# inspiré de  https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Get-GPPPassword.ps1
        Param (
            [string] $Cpassword 
        )

        try {
            #Ajout du complement approprié en fonction de la longueur de chaîne  
            $Mod = ($Cpassword.length % 4)
            
            switch ($Mod) {
            '1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
            '2' {$Cpassword += ('=' * (4 - $Mod))}
            '3' {$Cpassword += ('=' * (4 - $Mod))}
            }

            $Base64Decoded = [Convert]::FromBase64String($Cpassword)
            
            #Creation d'un nouvel objet .NET pour le service de cryptographie AES 
            $AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
            [Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,
                                 0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
            
            # Positionnement de valeurs nulles sur les vecteurs initiaux (IV)
            # afin d'empêcher la génération dynamique d'une autre valeur
            $AesIV = New-Object Byte[]($AesObject.IV.Length) 
            $AesObject.IV = $AesIV
            $AesObject.Key = $AesKey
            $DecryptorObject = $AesObject.CreateDecryptor() 
            [Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
            
            return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
        } 
        
        catch {Write-Error $Error[0]}
     

Dans le(s) fichier(s) précédemment récupérés, il vous suffit de repérer la balise (ou attribut xml) « cpassword= » et de copier la valeur correspondante dans le presse-papier.

Chargez éventuellement ce contenu dans une variable Powershell, comme suit :

$AESpass = "v9NWtCCOKEUHkZBxakMd6HLzo4+DzuizXP83EaImqF8"

Bien que cette donnée soit protégée avec une clé AES 256 bits, cela n’en reste pas moins un algorithme de « chiffrement symétrique ». C’est-à-dire que la clé utilisée pour chiffrer est la même que pour déchiffrer.

Utilisez le script suivant pour afficher le mot de passe

.\Get-DecryptedCpassword.ps1 -Cpassword $AESpass

Et si la stratégie d’exécution bloque le script :

Powershell.exe -ExecutionPolicy bypass -file .\Get-DecryptedCpassword.ps1 -Cpassword "v9NWtCCOKEUHkZBxakMd6HLzo4+DzuizXP83EaImqF8"

Et le mot passe apparaît en clair !…

Laps-Alt-Img3

 

 

Alors convaincu ou pas ?….

 

E. Une autre technique – Ma (modeste) proposition alternative

 

Fort de toutes ces informations, vous disposez d’un annuaire Active Directory, mais vous n’êtes pas encore disposé à utiliser LAPS. Je vous propose une solution hybride qui consiste à utiliser des attributs standards d’un objet de l’annuaire afin d’y stocker les clés nécessaires au changement du mot passe des administrateurs locaux (celui-ci sera le même pour tous) mais demeure modifiable régulièrement.

1. L’objet de stockage

 

La logique voudrait que l’on choisisse un objet spécialisé, mais je voulais limiter au maximum les interactions avec l’annuaire et ne pas toucher au schéma. Pour cet exemple, j’ai donc choisi arbitrairement de créer un nouvel objet « Groupe de sécurité – Global« , nommé « LocalAdmins« . Nous utiliserons les attributs « info » et « mail » pour y stocker respectivement, la chaine sécurisée du mot de passe encodé et la clé partagée.

Au besoin, pour consulter ou renseigner ces attributs, utilisez l’onglet « Editeur d’attribut » (Affichage … Propriétés avancées) de la console « Utilisateurs et ordinateurs Active Directory »  (DSA.msc) – Je n’aborderais pas ce sujet dans cet article, mais si vous retenez cette technique, je vous encourage à limiter les droits de lecture sur l’objet et/ou ses attributs. (ou en choisir d’autres, sur lesquels vous n’octroierez que les ayant-droits nécessaires, typiquement le groupe dynamique « Ordinateurs du domaine« .

 

2. Script de génération des codes

 

Le script « Infra-SecurePass.ps1 » suivant peut être exécuté sur une machine quelconque, mais du fait qu’il génère des « données très sensibles », j’ai ajouté une action de chiffrement EFS afin d’assurer une protection minimale de ces fichiers. Dans le cas  où le volume de travail n’est pas en NTFS, les informations sont affichées uniquement à l’écran.

###########################################################################
# Script : Infra-SecurePass.ps1 
# Permet de stocker la clé et mot de passe encodé dans un objet AD
# pour cet exemple j'ai choisi arbitrairement un objet "Groupe", 
# nommé "LocalAdmins'
# et l'attribut 'info' (Notes) pour y stocker la chaine sécurisée
# ainsi que l'attribut 'mail' pour y stocker la clé partagée
###########################################################################

Clear-Host

$CurrentPath = split-path -parent $MyInvocation.MyCommand.Definition
$Password = Read-Host "Entrez le mot de passe (en clair !)"

# Essaye de créer un sous-dossier EFS pour le stockage des fichiers sensibles
$CheckNTFS = gwmi -Query "select name,FileSystem from Win32_Volume" |
       where { $_.name -eq $($CurrentPath.substring(0,3)) }
if ($CheckNTFS.FileSystem -eq 'NTFS') {
   $Protected = "EFS"
   Write-Host -fore 'green' "Création d'un sous-dossier EFS pour le stockage des fichiers sensibles."
   $StoreKeys = $true
   if (-not (Test-Path "$CurrentPath\$Protected")) {  
      New-Item -Name $Protected -ItemType Directory -Path "$CurrentPath" |Out-Null
      cipher.exe /e $Protected |Out-Null
     }  
     else { # le sous-dossier existe
       if ( (Get-ItemProperty $Protected).attributes -notmatch "Encrypted" )  {  
          cipher.exe /e $Protected 
         }
    }
 } 
 else { # le volume n'est pas en NTFS
   $StoreKeys = $false
   Write-Host -fore 'yellow' "Les données sensibles seront uniquement affichées à l'écran."
}  

# Génération d'une clé aléatoire 
$SharedKey = 1..16 | % { (Get-Random -Minimum 0 -Maximum 255) }
$keytab = $SharedKey -join "," 
if ($StoreKeys) {
  $File1 = "$CurrentPath\$Protected\grpLAdm-mail.txt"
  $keytab | Out-File -FilePath $File1
  Write-Host "La chaine chiffrée a été stockée dans le fichier $File1."
}
Write-Host "Veuillez recopier ce contenu dans l'attribut 'mail' de l'objet groupe 'LocalAdmins'."
Write-Host -fore 'yellow' $keytab

# Génération de la chaine sécurisée
$Secure  = $Password | ConvertTo-SecureString -AsPlainText -Force
$SecureString = $Secure | ConvertFrom-SecureString -key $SharedKey
if ($StoreKeys) {
  $File2 = "$CurrentPath\$Protected\grpLAdm-info.txt"
  $SecureString | Out-File -FilePath $File2
  Write-Host "La chaine chiffrée a été stockée dans le fichier $File2."
}
Write-Host "Veuillez recopier ce contenu dans l'attribut 'info' de l'objet groupe 'LocalAdmins'."
Write-Host -fore 'yellow' $SecureString

 

 

3. Script client à compiler

 

Enregistrez le script suivant puis procédez à la compilation via « PS2EXE » ou « Batch Compiler » comme indiqué dans mon article « Compilez vos scripts« .

###########################################################################
# Script client à compiler en exe via un outil tel que PS2EXE
# "SetPass-v2.exe" 
# cf https://gallery.technet.microsoft.com/PS2EXE-Convert-PowerShell-9e4e07f1
# !! A exécuter avec un compte ayant accès à l'annuaire AD
# et de niveau administrateur local - idéalement le compte de la machine
# 
###########################################################################

try {
  # lecture de la  clé partagée dans l'attribut 'mail' du groupe 'LocalAdmins' 
  $keytab = ([adsisearcher]"samAccountName=LocalAdmins").findone().getdirectoryentry().mail.value
  $SharedKey = ($keytab).Split(",")
  # lecture de la clé dans l'attribut "info" du groupe 'LocalAdmins'
  $Raw = ([adsisearcher]"samAccountName=LocalAdmins").findone().getdirectoryentry().info.value
  # génération de la chaine sécurisée 
  $SecureString = ConvertTo-SecureString $Raw -key $SharedKey
  # Décodage du mot de passe !!
  $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)            
  $PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)            
  # affectation du mot de passe récupéré à partir de la chaine sécurisée
  $LocalAdmin = (Get-WmiObject -Query "Select * From Win32_UserAccount Where LocalAccount = TRUE and SID like '%-500'").Name
  $objUser = [ADSI]"WinNT://$env:COMPUTERNAME/$LocalAdmin,user"
  $objUser.SetPassword("$PlainPassword")
  $objUser.SetInfo()
  }
  catch { 
   Write-Error "Erreur - Veuillez contacter votre administrateur." -ErrorAction Stop
   # attention à ne pas afficher d'info sensibles via le canal d'erreur
   # $($_.exception.innerexception.message) 
} 

Une fois compilé, vous n’aurez plus qu’à ajouter cet exécutable dans un script de démarrage de l’ordinateur, par exemple via un objet de stratégie de groupe (et/ou éventuellement le copier en local). Vous pouvez également limiter l’application de ce script par ciblage en ajoutant les ordinateurs souhaités en tant que membre du groupe « LocalAdmins » et en remplaçant « utilisateurs authentifiés » du filtrage de sécurité du GPO, par le groupe « LocalAdmins ».

 

4. Script de vérification

Afin de vérifier le bon fonctionnement du mécanisme, voici un script à exécuter sur une machine de test (Vous comprendrez par vous-même qu’il ne vaut mieux pas laisser trainer ce script n’importe où 😀 ) et effacer toute trace d’utilisation. A des fins de test uniquement, donc…

###########################################################################
# Script : Check-SecurePass.ps1
# Controle le bon fonctionnement de la solution en effectuant
# une lecture des attributs et affichage potentiel du mot de passe !
#   
# NE PAS DIFFUSER !...
###########################################################################

 try {
  # lecture de la clé dans l'attribut "mail" du groupe 'LocalAdmins'
  $keytab = ([adsisearcher]"samAccountName=LocalAdmins").findone().getdirectoryentry().mail.value
  $SharedKey = @($keytab.Split(","))
    
  # lecture de la chaine sécurisée dans l'attribut "info" du groupe 'LocalAdmins'
  $Raw = ([adsisearcher]"samAccountName=LocalAdmins").findone().getdirectoryentry().info.value
  
  # construction de l'objet de chaine sécurisée
  $SecureString = ConvertTo-SecureString $Raw -key $SharedKey
  
  # Décodage du mot de passe
  $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)            
  $PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)            
  
  $Confirm = Read-Host "Le mot de passe a été décodé, voulez-vous l'afficher (O/N) ? "
  if ($Confirm.ToUpper() -like "O*") {
      Write-Host "!! Debug Only : Le mot de passe est : $PlainPassword"
    }
  }
  catch {
    Write "Erreur : $($Error[0].Exception.Message)"
    
  } 

 

III. Conclusion

 

Une fois encore, je vous rappelle que mes articles n’ont qu’une vocation pédagogique de sensibilisation aux règles élémentaires de sécurité.  Quoi que nous fassions, ces informations sont (et seront) présentes sur la toile et ce n’est pas en se voilant la face que l’on pourra protéger nos informations sensibles.

Les protections qui utilisent des clés symétriques ne sont pas adaptées au stockage des informations sensibles, et ces dernières peuvent être rapidement compromises par une personne ou du code malveillant. A défaut de mieux, il faut impérativement conserver ces éléments en lieu sûr et redoubler de vigilance pour les stockages isolés et physiquement accessibles. Combien disques durs amovibles et autres clés USB égarés se retrouvent, comment dirais-je,…  entre de mauvaises mains 🙁 …)

Cela étant dit, c’est à vous de décider et d’évaluer les moyens que vous accordez à la protection de vos chères données. Si cet article vous a interpellé ou sensibilisé au sujet, il aura atteint son objectif premier. N’hésitez pas à déposer vos commentaires si vous avez des questions (ou des remarques constructives, des suggestions…) sur cet article et/ou échanger sur cette approche du sujet.

Bien à vous

Christophe.

3 Commentaires

  1. Scorpe

    La procédure à un défaut majeur car le GPO s’applique durant une phase de déploiement MDT et que le mot de passe ne correspond plus. Il faudrait que le script soit déclenché après 🙂

    Répondre
    1. Christophe

      Effectivement, je n’avais pas anticipé le fait que les GPO s’appliquent dès l’adhésion au domaine, mais l’idée du GPO reste valable pour peu qu’on dépose l’exécutable par un autre moyen, du genre en post-install ou via une technique de télédistribution. Il est également possible d’envisager un test sur un fichier ou dossier, tel que « %windir%\MININT » uniquement présent durant les phases de déploiement au sein d’un ciblage de préférences, et/ou de conditionner le lancement en fonction de ce contexte « déploiement en cours/achevé ».
      A l’occasion je développerais ce sujet dans un complément…
      Bien à vous et merci pour cette remarque très pertinente 😉

      Répondre
  2. Christophe M. (Auteur de l'article)

    Voilà, c’est corrigé.
    Je viens de publier un palliatif « Eviter l’application d’un GPO critique lorsqu’un déploiement MDT est en cours » http://cnf1g.com/?p=1195
    Mieux vaux tard que jamais 🙂
    N’hésitez-pas à poster votre retour d’expérience sur le sujet.
    Bien à vous

    Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *