LaBouaBouate

Comment stocker des données confidentielles dans Active Directory ?

Cacher une information sensible à la vue de tous : cas pratique des codes PUK

Introduction

Le problème des données sensibles dans Active Directory

Active Directory est globalement un livre ouvert : n’importe quel utilisateur peut lire la plupart des informations de l’annuaire (qui est membre de quoi, quel est le numéro de téléphone de John, qui est le manager de Jane…). Quelques attributs font exception (comme unicodePwd qui contient le mot de passe de l’objet), mais ceux-ci sont gérés par les contrôleurs de domaine et ne peuvent pas être utilisés à nos propres fins.

Comment stocker alors dans l’AD des informations confidentielles qui ne soient pas visibles par tous ? Windows LAPS et les mots de passe des comptes gMSA montrent qu’il est possible de protéger et de contrôler l’accès à ce type de données, en utilisant des mécanismes de chiffrement et de visibilité des attributs. On va donc s’en inspirer pour concevoir des solutions adaptées à nos propres besoins.

Les smart cards pour l’authentification

Si vous avez un Active Directory avec un modèle de tiering déployé et que vous devez utiliser un mot de passe différent pour chaque compte, les smart cards sont une solution merveilleuse pour vous simplifier la vie. Grâce à elles, vous pouvez vous connecter avec l’ensemble de vos comptes sans avoir à taper de mot de passe : un code PIN suffit !

Pour résumer, une smart card c’est :

…et si le code PIN est personnel (c’est le “P” dans “PIN”), le code PUK est utile à votre administrateur pour éviter d’avoir à refaire tous vos certificats si jamais vous avez oublié votre PIN (ou que vous avez bloqué votre clé après plusieurs mauvais codes PIN).

Vient alors la réflexion du stockage de ces codes PUK, et pour ça, plusieurs options sont possibles :

Principal problème

Toutes ces options ont un inconvénient majeur : elles ne suivent pas le cycle de vie des utilisateurs Active Directory. Au fil des départs et des arrivées, des changements de nom ou d’équipe, vous allez finir par vous y perdre. Impossible d’identifier rapidement les utilisateurs qui n’ont pas de code PUK, ou ceux qui ont quitté l’entreprise depuis déjà plusieurs années.

There is another

L’idée de cet article est donc de proposer autre chose : une méthode purement expérimentale qui va lier directement l’utilisateur à sa smart card dans Active Directory ; sans pour autant que le code PUK soit accessible à tous.

Ma solution (expérimentale)

Le principe s’inspire très largement du travail que Microsoft a déjà effectué pour la gestion des clés BitLocker sur les comptes ordinateurs, ainsi que des mots de passe LAPS et de ceux des comptes gMSA. On va créer une nouvelle classe d’objet représentant nos smart cards dans notre schéma Active Directory (vous comprenez maintenant le côté expérimental). Ces nouveaux objets “smart cards” viendraient se greffer comme des objets enfants des comptes utilisateurs existants, pour se lier durablement à eux sans être impactés par un déplacement ou un renommage de l’objet parent.

Pour accompagner cette nouvelle classe, on vient également ajouter trois nouveaux attributs :

Les codes PUK ne doivent évidemment être accessibles qu’au personnel autorisé. Pour ça, on part sur une approche ceinture-bretelles avec :

Modification du schéma

Petit rappel, mais modifier son schéma sans savoir ce que l’on fait n’est clairement pas une bonne idée. Ne faites évidemment pas ça sur votre production. Chaque changement fait à votre schéma est indélébile et peut impacter la stabilité de votre domaine Active Directory.

Prérequis

Pour pouvoir modifier le schéma, créer notre classe et nos nouveaux attributs, il va falloir :

  1. Être membre du groupe “Schema Admins”
  2. Pouvoir générer un OID (avec la commande PowerShell ci-dessous par exemple)
  3. Pouvoir redémarrer le contrôleur de domaine qui porte le rôle SchemaMaster si besoin

New-Oid.ps1 New-Oid.ps1

Définition des attributs

On va avoir besoin de trois nouveaux attributs dans le schéma :

Les attributs sont préfixés par “CORP” pour éviter une éventuelle collision avec de nouveaux attributs qui pourraient être ajoutés dans le schéma par Microsoft ou un éditeur tiers.

Voici les spécifications techniques pour chacun :

corp-puk

corp-puk va être une chaîne d’octet confidentielle :

Attribut Valeur
name corp-puk
objectClass attributeSchema
adminDescription Contains the current personal unlocking key (PUK)
attributeSyntax 2.5.5.10 (Octet String)
isSingleValued True
omSyntax 4 (Octet String)
searchFlags 128 (Confidential)

corp-pukHistory

Tout comme corp-puk, corp-pukHistory sera une chaîne d’octets confidentielle, mais multivaluée :

Attribut Valeur
name corp-puk
objectClass attributeSchema
adminDescription Contains all the old PUK values
attributeSyntax 2.5.5.10 (Octet String)
isSingleValued False
omSyntax 4 (Octet String)
searchFlags 128 (Confidential)

corp-pukLastSet

Ici, pas besoin de déclarer l’attribut comme confidentiel, on définit simplement le bon format pour avoir une belle date bien propre (comme l’attribut accountExpirationDate par exemple) :

Attribut Valeur
name corp-puk
objectClass attributeSchema
adminDescription Contains the last time the PUK was set
attributeSyntax 2.5.5.11 (DateTime)
isSingleValued True
omSyntax 24 (DateTime)
searchFlags 0 (Not indexed)

Vous pouvez retrouver le tableau officiel de Microsoft pour obtenir les valeurs attendues pour attributeSyntax et omSyntax ici : LDAP Representations | Microsoft Learn.

Création des attributs

Voici la commande pour l’attribut corp-puk, que vous pouvez recycler en mettant à jour les différentes valeurs indiquées dans les spécifications :

$name = 'corp-puk'
$splat = @{
    Name = $name
    Type = 'attributeSchema'
    OtherAttributes = @{
        AdminDescription = 'Contains the current personal unlocking key (PUK)'
        AdminDisplayName = $name
        AttributeSyntax  = '2.5.5.10'
        IsSingleValued   = $true
        LDAPDisplayName  = $name
        OMSyntax         = 4
        SearchFlags      = 128
        AttributeID      = (New-Oid)
    }
    Path = (Get-ADRootDSE).schemaNamingContext
}
New-ADObject @splat

N’oubliez pas qu’une seule erreur sera visible à jamais dans votre schéma.

Définition de la classe

Il faut maintenant passer aux spécifications de la classe qui va héberger nos nouveaux attributs :

Attribut Valeur
name corp-smartCard
objectClass classSchema
adminDescription Device that can be used to authenticate
defaultHidingValue True
mayContain serialNumber, corp-pukHistory, corp-pukLastSet
mustContain corp-puk
objectClassCategory 1 (STRUCTURAL)
possSuperior user, inetOrgPerson
showInAdvancedViewOnly True
systemOnly False
subClassOf 2.5.6.0 (top)

Création de la classe

Voici la dernière étape pour la modification du schéma : la création de notre nouvelle classe d’objet corp-smartCard qui va utiliser les attributs créés précédemment.

$name = 'corp-smartCard'
$schemaPath = (Get-ADRootDSE).schemaNamingContext
$mustContain = Get-ADObject -Filter { Name -eq 'corp-puk' } -SearchBase $schemaPath -Properties AttributeID
$mayContain = 'serialNumber', 'corp-pukHistory', 'corp-pukLastSet' | ForEach-Object {
    Get-ADObject -Filter { Name -eq $_ } -SearchBase $schemaPath -Properties AttributeID
}
$splat = @{
    Name = $name
    Type = 'classSchema'
    OtherAttributes = @{
        AdminDisplayName       = $name
        AdminDescription       = 'Device that can be used to authenticate'
        DefaultHidingValue     = $true
        GovernsID              = (New-OID)
        LDAPDisplayName        = $name
        MayContain             = $mayContain.AttributeID
        MustContain            = $mustContain.AttributeID
        ObjectClassCategory    = 1
        PossSuperiors          = 'user', 'inetOrgPerson'
        ShowInAdvancedViewOnly = $true
        SystemOnly             = $false
        SubClassOf             = '2.5.6.0'
    }
    Path = (Get-ADRootDSE).schemaNamingContext
}
New-ADObject @splat

Ça n’aura pas été aussi facile que prévu puisqu’il n’y a que très peu de documentation sur le sujet, mais sachez que les attributs “MayContain” et “MustContain” ne reçoivent que les OID des attributs cibles (si jamais ça peut vous aider).

Exploitation de la nouvelle classe et des nouveaux attributs

Création du groupe d’accès au PUK

Je l’ai caché dans mon module, et celui-ci devrait être plus visible mais les codes PUK ne sont déchiffrables que par les membres du groupe “SmartCard PUK Administrators”. Il convient donc de créer ce groupe dans Active Directory :

New-ADGroup -Name 'SmartCard PUK Administrators' -GroupScope Global -GroupCategory Security

Si vous voulez pouvoir déchiffrer les codes PUK, vous aurez besoin d’être membre du groupe.

Délégation d’accès sur les attributs confidentiels

Vous vous souvenez de l’explication sur les attributs qui ne pouvaient être lus que par les comptes autorisés ? Eh bien, comme le groupe “Domain Admins” possède déjà ces permissions, je m’en accommode très bien. Si vous souhaitez mettre en place une délégation, voici ce que Claude m’a proposé (je n’ai pas testé) :

$path  = "OU=Utilisateurs,DC=corp,DC=contoso,DC="
$group = "CORP\SmartCard PUK Administrators"
$attr  = "corp-puk"

# 1. Lecture de l'attribut
dsacls $path /I:S /G "${group}:RP;$attr;corp-smartCard"

# 2. Control Access (requis pour les attributs confidentiels)
dsacls $path /I:S /G "${group}:CA;;corp-smartCard"

Avant de mettre en place cette délégation, souvenez-vous qu’une seule smart card peut permettre d’utiliser plusieurs comptes, donc n’allez pas faire une délégation qui pourrait ouvrir un nouveau chemin d’attaque vers les membres du groupe “Domain Admins”.

Utilisation du module

Une fois notre schéma cassé modifié, on peut passer à l’exploitation de nos nouveaux jouets ! Pour ça, j’ai créé un module PowerShell assez simple qui permet d’interagir avec nos smart cards :

PUKManagement.psm1 PUKManagement.psm1

Celui-ci est composé de quelques commandes utiles :

Création d’une nouvelle smart card

Création d’une nouvelle smart card pour l’utilisateur “John Smith”, sans jamais saisir le code PUK en clair dans la console :

New-ADSmartCard -Name '28293929' -User (Get-ADUser john.smith)

Comme le code PUK est obligatoire, il sera automatiquement récupéré via un prompt supplémentaire au format SecureString.

Ici j’ai indiqué le numéro de série (SerialNumber) de la smart card dans la valeur du paramètre “Name”. A mon humble avis, c’est le meilleur choix à faire.

Récupération des informations d’une ou plusieurs smart cards

Obtenir toutes les smart cards du domaine :

Get-ADSmartCard -Filter *

Obtenir toutes les smart cards d’un utilisateur :

Get-ADUser john.smith | Get-ADSmartCard

Obtenir le code PUK en clair de la smart card “28293929” :

Get-ADSmartCard 28293929 -AsPlainText

Mise à jour d’un code PUK

Pour mettre à jour le code PUK de la smart card “28293929” :

Get-ADSmartCard 28293929 |
Set-ADSmartCard -PUK (Read-Host -AsSecureString -Prompt 'New PUK')

Suppression d’une smart card

Et pour finir, la commande Remove-ADSmartCard :

Get-ADSmartCard 28293929 | Remove-ADSmartCard

Alternatives sans modifier le schéma

Bon le POC est rigolo, mais une modification de schéma ne doit évidemment pas être prise à la légère. Dans 99% des cas, ça vous sera probablement refusé purement et simplement. Du coup voici une alternative pour stocker des informations confidentielles sans pour autant faire péter le rôle “Schema Admins”.

Récupération d’un attribut inutilisé

On va essayer d’identifier un attribut inutilisé de votre domaine. Pour ça, on peut utiliser la commande PowerShell la plus sous-cotée (Group-Object). Voici comment vérifier l’utilisation de l’attribut msds-CloudExtensionAttribute1 sur l’ensemble de vos utilisateurs :

$attribute = 'msds-CloudExtensionAttribute1'
Get-ADUser -Filter * -Properties $attribute | Group-Object $attribute

Les attributs msds-CloudExtensionAttribute... sont des candidats idéaux, et ils existent depuis 2012. Seul problème : ils ne sont pas du type “Octet String” mais simplement “String”. On va donc devoir convertir notre chaîne de bits chiffrés en une chaîne de caractères simple.

Conversion en chaîne de caractères

Contrairement à notre plan “parfait” avec modification du schéma, ici on va devoir convertir notre Octet String en une chaîne de caractères. La bonne nouvelle, c’est que le Base64 permet de faire ça facilement :

[Convert]::ToBase64String($blob)

On va donc utiliser notre commande personnalisée issue du module pour obtenir notre chaîne de caractères au format Base64 :

$splat = @{
    InputObject = ('test' | ConvertTo-SecureString -AsPlainText -Force)
    ToBase64 = $true
    Owner = 'Domain Admins'
}
$base64 = ConvertTo-DpapiNg @splat

Inscription dans l’attribut de votre choix

Une fois l’information chiffrée, il ne reste plus qu’à l’ajouter sur l’attribut msds-CloudExtensionAttribute1 de notre utilisateur :

Set-ADUser john.smith -Replace @{ 'msds-CloudExtensionAttribute1' = $base64 }

Quid de la longueur maximale de la chaîne de caractères ?

D’après mes tests sur les attributs msds-CloudExtensionAttribute..., on peut y stocker jusqu’à environ un million de caractères. Attention quand même, je ne pense pas qu’ajouter 1 MB de données par utilisateur soit une bonne idée sur le long terme !

Lecture de l’attribut

Enfin, pour déchiffrer la donnée, il faut faire le chemin inverse en faisant la conversion de Base64 vers Octet String :

$user = Get-ADUser john.smith -Properties 'msds-CloudExtensionAttribute1'
$blob = [Convert]::FromBase64String($user.'msds-CloudExtensionAttribute1')
ConvertFrom-DpapiNg -InputObject $blob

Conclusion

Avec la combinaison de DPAPI-NG et du drapeau “CONFIDENTIEL” sur les attributs dans le schéma, il est possible de cacher des informations dans Active Directory et de bénéficier ainsi du cycle de vie naturel de vos objets. La création d’une classe dédiée offre une solution intégrée, mais reste expérimentale et implique une modification irréversible du schéma. Pour un besoin plus simple, réutiliser un attribut existant peut éviter cette étape, après avoir vérifié qu’il est libre et adapté, évidemment !

Photo de profil
Article écrit par Léo Bouard

Ingénieur systèmes spécialisé dans les infrastructures, les technologies et l'écosystème Microsoft. Je travaille depuis 2017 sur les questions de gestion d'identité, d'outils collaboratifs et d'automatisation.

Cet article pourrait-il vous intéresser ?