🏢Active DirectoryAccountsPermissionsPrivileged Access

Usurpation de primaryGroupID : les Domain Admins Cachés que les Audits Active Directory Ratent

Positionner primaryGroupID à 512 fait d'un compte un Domain Admin à part entière sans toucher à l'attribut member du groupe — invisible pour la plupart des exports d'audit.

Younes AZABARPar Younes AZABAR11 min de lecture
Usurpation de primaryGroupID : les Domain Admins Cachés que les Audits Active Directory Ratent

Positionnez l'attribut primaryGroupID d'un compte utilisateur à 512, et il devient membre à part entière de Domain Admins à chaque ouverture de session. C'est l'usurpation de primaryGroupID : des Domain Admins cachés qu'Active Directory calcule lui-même directement dans le jeton Kerberos du compte, mais qu'ADSI Edit, un filtre LDAP brut (member=...), et la plupart des exports d'audit ne voient jamais — parce que seule la moitié de la façon dont AD calcule l'appartenance aux groupes est réellement stockée sur l'objet groupe.

Qu'est-ce que primaryGroupID

Chaque objet utilisateur et ordinateur Active Directory porte un attribut primaryGroupID : un entier unique qui stocke l'identifiant relatif (RID) du groupe primaire du compte. C'est un héritage des exigences de conformité POSIX de Windows NT, datant d'avant qu'AD ne prenne en charge les groupes imbriqués arbitraires, et aujourd'hui il reste le plus souvent inchangé à sa valeur par défaut — au point qu'un fil r/activedirectory récent demandait simplement à quoi sert vraiment le champ « Primary Group » d'un objet utilisateur.

Les valeurs par défaut, confirmées par la documentation d'audit d'événements de Microsoft elle-même et par les recherches de Semperis sur cet attribut :

Type d'objetprimaryGroupID par défautNom du groupe
Utilisateur513Domain Users
Ordinateur515Domain Computers
Contrôleur de domaine516Domain Controllers
DC en lecture seule521Read-only Domain Controllers

Celui qui compte pour cet article : le RID 512 est Domain Admins.

Contrairement à l'appartenance de groupe ordinaire — qui vit dans l'attribut multi-valué member de l'objet groupe, avec un lien retour memberOf correspondant sur l'utilisateur — l'appartenance au groupe primaire est stockée uniquement sur le compte, sous la forme d'un seul entier. L'objet groupe lui-même n'enregistre rien à ce sujet.

Comment Active Directory Calcule Réellement « Qui Est Dans Ce Groupe »

« Qui est dans Domain Admins » n'est pas une recherche unique. L'appartenance effective est l'union de deux éléments distincts :

  1. Chaque compte listé dans l'attribut member du groupe Domain Admins.
  2. Chaque compte dont l'attribut primaryGroupID est égal à 512 — le RID de Domain Admins — indépendamment de ce que dit l'attribut member.

Seule la première moitié est stockée sur l'objet groupe. La seconde moitié est stockée entièrement sur le compte, et rien sur le groupe n'enregistre que ce lien existe. Les outils qui résolvent l'appartenance en interrogeant directement l'objet groupe reconstruisent les deux moitiés et affichent l'image complète. Les outils qui se contentent de lire la valeur brute member du groupe ne voient que la première moitié.

Les recherches de TrustedSec sur ce comportement (Brandon Colley, publiées en janvier 2026) démontrent directement cette scission : interroger Get-ADGroupMember "Domain Admins" sur le groupe renvoie un compte dont l'accès à Domain Admins provient uniquement de primaryGroupID — le cmdlet résout l'union. Active Directory Users and Computers et Active Directory Administrative Center font de même quand vous ouvrez l'onglet Membres du groupe. Mais la même recherche montre que deux outils se trouvent de l'autre côté de la scission :

  • ADSI Edit, en parcourant directement l'attribut member, ne liste pas le compte.
  • Un filtre LDAP brut tel que (member=CN=...) — le type sur lequel reposent la plupart des scripts d'export maison, des tâches de reporting basées sur des CSV, et des pipelines d'ingestion SIEM — le rate pour la même raison : il interroge l'attribut qui n'a jamais été touché.

Il existe un second écart, plus étroit, dans la même recherche : Get-ADGroupMember -Recursive parcourt les groupes imbriqués pour étendre l'appartenance, mais il ne détecte pas un compte dont le groupe primaire est défini sur un groupe imbriqué dans Domain Admins plutôt que Domain Admins lui-même. La récursion étend la chaîne member ; elle ne réexécute pas l'union primaryGroupID à chaque niveau d'imbrication — le même angle mort transitif couvert dans l'imbrication dangereuse de groupes.

Usurpation de primaryGroupID : les Domain Admins Cachés qu'Active Directory Ne Liste Jamais

Un attaquant (ou un script) disposant d'un accès en écriture sur l'attribut primaryGroupID d'un utilisateur cible — un droit GenericWrite, GenericAll, ou un WriteProperty restreint délégué quelque part au fond d'une OU, la même classe d'arête d'abus d'ACL que BloodHound cartographie déjà sur les objets utilisateur — le positionne à 512. Active Directory n'accepte cette écriture que sur un compte déjà membre explicite du groupe cible — les recommandations de Microsoft sur la définition programmatique du groupe primaire et les recherches de Tenable sur cet attribut confirment toutes deux qu'un simple Set-ADUser -Replace @{primaryGroupID=512} échoue contre un compte qui n'est pas déjà membre, et que seul un contournement du service d'annuaire tel que DCShadow peut le positionner sur un compte qui n'a jamais été membre du tout. Le chemin ordinaire tient en trois commandes, pas une seule :

Add-ADGroupMember -Identity "Domain Admins" -Members jdoe
Set-ADUser -Identity jdoe -Replace @{primaryGroupID=512}
Remove-ADGroupMember -Identity "Domain Admins" -Members jdoe -Confirm:$false

À la prochaine demande de TGT Kerberos du compte, le RID du groupe primaire est intégré dans le jeton exactement comme n'importe quel autre SID de groupe. Le compte s'authentifie désormais avec un accès équivalent à Domain Admins. Une fois la troisième commande exécutée, l'attribut member de Domain Admins retrouve sa liste d'origine, si bien qu'un diff planifié des exports de groupes ou une lecture statique de l'attribut member n'a plus rien à détecter après coup — bien que l'ajout et le retrait brefs déclenchent chacun un événement standard de changement d'appartenance à un groupe au passage, si bien qu'une alerte en temps réel sur ces événements spécifiques (contrairement à un diff d'export périodique) a bien une fenêtre pour l'attraper. C'est pourquoi cette technique se lit comme une usurpation de primaryGroupID : des Domain Admins cachés qu'Active Directory intègre lui-même dans chaque jeton, mais que le contrôle conçu pour repérer les nouveaux admins ne voit jamais.

Le chercheur en sécurité Yuval Gordon documente, dans un billet du blog de Semperis, une extension supplémentaire de cette idée : associer le changement de primaryGroupID à une Access Control Entry de refus de lecture sur l'attribut primaryGroupID de l'utilisateur lui-même, de sorte que même les outils conscients de l'union (ADUC, Get-ADGroupMember) ne puissent plus lire la valeur et cessent totalement de signaler l'appartenance. C'est une technique réelle — mais Gordon est explicite sur ses limites :

« Cette astuce ne peut pas fonctionner sur les membres de groupes protégés comme Domain Admins... cette technique ne fonctionnera pas sur les membres d'un groupe protégé par le processus SDPROP et AdminSDHolder, car nous nous appuyons sur la DACL, qui dans ce cas sera simplement réinitialisée à la DACL d'AdminSDHolder environ toutes les heures. »

Le cycle SDProp d'AdminSDHolder (environ toutes les 60 minutes) réapplique une ACL modèle à tout compte qu'il considère membre d'un groupe protégé, ce qui efface l'ACE de refus. Comme un simple changement primaryGroupID=512 ne touche absolument pas à la DACL du compte, SDProp ne l'annule pas — il ne déjoue que l'astuce de dissimulation par ACE de refus, pas l'usurpation sous-jacente. L'exemple fonctionnel de Gordon lui-même cible d'ailleurs un groupe de test non protégé, pas DnsAdmins — le même billet note par ailleurs que la technique fonctionne aussi contre des groupes ordinaires comme DnsAdmins, qui dispose de son propre chemin bien connu vers Domain Admins. La version simple — positionner primaryGroupID directement à 512, sans l'ACE — n'est de toute façon pas affectée par AdminSDHolder, ce qui explique précisément pourquoi c'est elle qu'il vaut la peine d'auditer directement.

Détection

C'est un cas où la surveillance des Event ID Windows joue réellement en votre faveur. La référence d'audit de sécurité de Microsoft pour l'Event ID 4738 (« Un compte utilisateur a été modifié », sous la sous-catégorie Audit User Account Management) documente un champ Primary Group ID dans les données d'attributs modifiés de l'événement, et son propre tableau de recommandations de surveillance le dit clairement :

Champ à surveillerRaison de la surveillance
Primary Group ID différent de 513En principe, la valeur de Primary Group est 513 pour les utilisateurs de domaine et locaux. Les autres valeurs doivent être surveillées.

Deux vérifications indépendantes, correspondant aux deux choses qu'il faut réellement détecter :

Temps Réel — Auditer le Changement Lui-Même

Avec Audit User Account Management activé, chaque écriture de primaryGroupID déclenche l'Event ID 4738 sur le DC qui l'a traitée, avec le nouveau RID dans le champ Primary Group ID. Alertez sur tout 4738 où ce champ est renseigné et où la nouvelle valeur n'est pas 513 (ou 515/516/521 pour les ordinateurs et les DC).

Instantané — Balayer les Comptes Déjà Usurpés

Les recherches de TrustedSec fournissent une commande directe pour cela :

Get-ADUser -property primaryGroup,primaryGroupID -Filter {-not(primaryGroupID -like "513")}

Exécutée contre un domaine avec une base saine, les seuls résultats attendus sont les contrôleurs de domaine (516), les RODC (521), et — si votre environnement en provisionne encore — des comptes de service mappés POSIX avec une raison documentée. Tout compte utilisateur ordinaire affichant 512, ou tout RID privilégié qu'il ne devrait pas avoir, constitue le constat.

Aucune des deux vérifications ne dépend de la lecture de l'objet groupe Domain Admins — c'est précisément le but. Une revue construite sur Get-ADGroupMember "Domain Admins" exécutée directement contre le groupe le détecterait également, selon la même recherche ; une revue construite sur un export LDAP de l'attribut member, ou un outil qui traite cet export comme la vérité de référence, ne le détectera pas.

Remédiation

  1. Exécutez le balayage ci-dessus sur chaque domaine de la forêt, pas seulement celui hébergeant Domain Admins — l'abus de primaryGroupID contre un groupe imbriqué/local dans un domaine enfant est tout aussi invisible pour les exports de l'attribut member.
  2. Réinitialisez tout compte non conforme à sa valeur par défaut correcte (513 pour les utilisateurs standards) sauf raison métier documentée et actuelle justifiant une valeur différente.
  3. Activez Audit User Account Management sur tous les contrôleurs de domaine si ce n'est pas déjà fait, afin que l'Event ID 4738 soit effectivement généré et transmis à votre SIEM.
  4. Reconstruisez toute revue d'accès privilégié automatisée qui interroge member directement — via ADSI Edit, un filtre LDAP brut (member=...), ou un script d'export construit de la même façon — pour appeler à la place Get-ADGroupMember sur le groupe, ou pour unir explicitement un balayage primaryGroupID. La revue censée être le filet de sécurité est précisément le contrôle que cette technique déjoue.
  5. Auditez qui détient WriteProperty/GenericWrite/GenericAll sur les objets utilisateur, et qui peut ajouter des membres à Domain Admins lui-même — le chemin ordinaire vers cette technique nécessite les deux : le droit d'ajouter brièvement le compte cible à Domain Admins, et le droit d'écrire son primaryGroupID. L'un ou l'autre délégué en dessous des OU d'administration intégrées constitue à lui seul un constat.

Comment EtcSec Détecte Cela

Le contrôle PRIMARYGROUPID_SPOOFING d'EtcSec exécute la même classe de balayage en tant que contrôle continu : chaque audit compare le primaryGroupID de chaque compte à sa valeur par défaut attendue et signale les comptes dont le groupe primaire diffère sans raison documentée, comblant exactement l'écart qu'un export limité à l'attribut member laisse ouvert. Les constats apparaissent aux côtés de la revue des permissions d'écriture (ACL_WRITE_PROPERTY_EXTENDED) qui montre qui pouvait effectuer le changement en premier lieu, et aux côtés de la surveillance des groupes privilégiés (PRIVILEGED_GROUP_MEMBER_CHANGES) afin que vous puissiez voir, côte à côte, quels chemins d'escalade de privilèges votre alerting sur l'attribut member couvre réellement.

Explorez les pages de sécurité des identités liées à ce sujet