ADACLScanner est un outil en PowerShell, créé par Robin Granberg (ex PFE Microsoft et aujourd'hui chez Semperis). L'outil est disponible sur https://github.com/canix1/ADACLScanner

Cet outil est utilisé pour auditer les autorisations Active Directory, les exporter au format CSV/HTML, les comparer avec une exportation précédente ou avec un autre domaine ou avec les autorisations par défaut.Il permet aussi de connaître les autorisations d'un utilisateur ou d'un groupe, le propriétaire d'un objet, la date de dernière modification des droits, les droits hérités, etc.

Il peut être exécuté avec une interface graphique (GUI) ou en ligne de commande.Le projet est toujours actif, et Robin prend le temps de corriger les erreurs signalées via Github.

Utilisation ligne de commandes

-RecursiveFind : This parameter will search any nested groups to show all security prinicpals that have access.

-RecursiveObjectType : Ce paramètre filtre les groupes imbriqués pour n'afficher que les utilisateurs qui y ont accès.

-FilterTrustee: Filtre ACL pour les chaînes de caractères correspondantes dans Trustee Exemple 1 -FilterTrustee "user1" (domaine\ est nécessaire)

-Owner: Obtenir les propriétaires

Exemples de recherche

💡
Pour rechercher uniquement dans certains emplacements, on utilise les CMDlets Get-AD*. Le module PowerShell doit donc être installé sur le PC ou serveur utilisé.

ACL personnalisés sur toutes les OU et tous les conteneurs

$res = .\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter "(|(objectClass=organizationalUnit)(objectClass=container))" -SkipDefaults -ShowCriticalityColor -Output HTML -Show
# To view the result and exlucde groupPolicyContainer
$res | Where-Object ObjectClass -ne 'groupPolicyContainer'

AdminSDHolder ACL et date de dernière modification

.\ADACLScan.ps1 -Base "CN=AdminSDHolder,CN=System,$((Get-ADRootDSE).defaultNamingContext)" -SDDate -ShowCriticalityColor -Show -Output HTML

Obtenir les droits d'un utilisateur/groupe spécifique

.\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter "(|(objectClass=organizationalUnit)(objectClass=container))" -SkipDefaults -SkipBuiltIn -ShowCriticalityColor -Output HTML -Show -EffectiveRightsPrincipal domain\user

Droits sur les GPOs

.\ADACLScan.ps1 -GPO -Scope subtree -ShowCriticalityColor -Output HTML -Show -RecursiveFind

Obtenir les propriétaires

.\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Owner -Scope subtree -Filter '(objectClass=*)' | Where-Object {$_.Access -eq 'Owner'}

Obtenir les permissions Deny

Rechercher les permissions autorisation Deny (peut être utile pour vérifier ManagedBy ou d'autres attributs utiles cachés).

.\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter '(objectClass=*)' | Where-Object {$_.Access -eq 'Deny'}

SID non résolus - tous les naming context et propriétaires

⚠️ Le temps d'exécution dépend du nombre d'objets⚠️

[System.Collections.Generic.List[PSObject]]$unresolvedSIDArray = @()
foreach($nc in (Get-ADRootDSE).namingcontexts){
    $unresolvedSID = .\ADACLScan.ps1 -Base $nc -Scope subtree -Filter '(objectClass=*)' -Owner -FilterTrustee 'S-1-5*'
    $unresolvedSID | ForEach-Object {
        $unresolvedSIDArray.add($_)
    }
}

SID non résolus - tous les objets de la partition domaine

⚠️ Le temps d'exécution dépend du nombre d'objets⚠️

$unresolvedSIDArray = .\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter '(objectClass=*)' -Owner -FilterTrustee 'S-1-5*'

SID non résolus sur OUs et conteneurs uniquement

⚠️ Le temps d'exécution dépend du nombre d'objets⚠️

$unresolvedSIDArray= .\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter '(|(objectClass=organizationalUnit)(objectClass=container))' -Owner -FilterTrustee 'S-1-5*'

SID non résolus sur GPO uniquement

⚠️ Le temps d'exécution dépend du nombre d'objets⚠️

[System.Collections.Generic.List[PSObject]]$unresolvedSIDArray = @()
$unresolvedSID = .\ADACLScan.ps1 -GPO -Scope subtree -Owner -FilterTrustee 'S-1-5*'
$unresolvedSID | ForEach-Object {
    $unresolvedSIDArray.add($_)
}

SID non résolus sur partitions DNS intégrées à AD

$adIntegratedDNSNC = (Get-ADRootDSE).namingContexts | Where-Object {$_ -like 'DC=DomainDNSZones*' -or $_ -like 'DC=ForestDNSZones*'}
[System.Collections.Generic.List[PSObject]]$unresolvedSIDArray = @()
foreach($nc in $adIntegratedDNSNC){
    $unresolvedSID = .\ADACLScan.ps1 -Base $nc -Scope subtree -Filter '(objectClass=*)' -Owner  -FilterTrustee 'S-1-5*'
     $unresolvedSID | ForEach-Object {
        $unresolvedSIDArray.add($_)
    }
}

Permissions DCSync

Le paramètre RecursiveFind recherche récursivement les membres du groupe.

.\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -RecursiveFind | Where-Object {$_.Permission -eq 'ExtendedRight Replicating Directory Changes' -or $_.Permission -eq 'ExtendedRight Replicating Directory Changes All' -or $_.Permission -eq 'ExtendedRight Replicating Directory Changes in Filtered Set'}

Utilisation avancées des résultats

Si vous souhaitez supprimer les SID inconnus ou modifier le propriétaire , vous pouvez utilisez les commandes suivantes.

Supprimer SID inconnus

Attention, n'utilisez les commandes suivantes que si vous savez ce que vous faites ! Ces commandes ne sont *pas* des commandes d'ADACLScanner!

Quelques informations utiles :

  • Import-Module ActiveDirectory est nécessaire pour accéder au lecteur AD:.
  • Il faut utiliser RemoveAccessRuleSpecific() à la place de RemoveAccessRule().
    La raison est qu’avec la méthode initiale, l’algorithme ne parvient pas toujours à identifier avec précision l’ACE à supprimer.
    RemoveAccessRuleSpecific() parcourt l’ACL pour trouver l’ACE exact et supprime uniquement celui-ci.
    Explications détaillées ici :
    https://forums.powershell.org/t/weird-issue-when-removing-permissions/5066
Import-Module ActiveDirectory

$DNWithUnresolvedSID = ($unresolvedSIDArray | Where-Object {$_.Access -ne 'Owner'}).Object | Select-Object -Unique

foreach ($dn in $DNWithUnresolvedSID){
    $acl = Get-Acl "AD:\$dn"
    
    $aclsToDelete = $acl.Access | Where-Object {$_.IdentityReference -like 'S-1-5*'}

    foreach($aclToDelete in $aclsToDelete){
        $acl.RemoveAccessRuleSpecific($aclToDelete)
    }

    Set-Acl -Path "AD:\$dn" -AclObject $acl
}

Pour l’historique, voici la méthode avec RemoveAccessRule, mais comme indiqué précédemment, elle ne fonctionne pas toujours. Je la laisse néanmoins à titre de référence.

Import-Module ActiveDirectory
$DNWithUnresolvedSID = ($unresolvedSIDArray | Where-Object {$_.Access -ne 'Owner'}  | Select-Object -Unique).Object

foreach ($dn in $DNWithUnresolvedSID){
    $acl = Get-Acl "AD:\$dn"
    
    $aclsToDelete = $acl.Access | Where-Object {$_.IdentityReference -like 'S-1-5*'}

    foreach($aclToDelete in $aclsToDelete){
        $acl.RemoveAccessRule($aclToDelete)
    }

    Set-Acl -Path "AD:\$dn" -AclObject $acl
}

Modifier propriétaire

Attention, n'utilisez les commandes suivantes que si vous savez ce que vous faites ! Ces commandes ne sont *pas* des commandes d'ADACLScanner!
$DNWithUnresolvedSIDOwner = ($unresolvedSIDArray | Where-Object {$_.Access -eq 'Owner'}).Object
$newOwner = New-Object System.Security.Principal.Ntaccount("Domain Admins")

foreach ($dn in $DNWithUnresolvedSIDOwner){
    $acl = Get-Acl "AD:\$dn"
    $acl.SetOwner($newOwner)
    Set-Acl -Path "AD:\$dn" -AclObject $acl
}

Supprimer les ACL correspondant à une classe d'objet ou un attribut

Parfois, il est nécessaire de supprimer tous les droits associés à une classe d’objet ou un attribut, par exemple après avoir supprimé cette classe/attribut. Dans le script ci-dessous, remplacez xxx par le nom de votre classe. À la fin, deux nombres s’affichent : le nombre d’ACE au début et le nombre d’ACE restants.

Par mesure de sécurité, le script n’écrase pas l’ACL. Si vous êtes absolument sûr de ce que vous faites, vous pourrez ensuite exécuter la commande Set-ACL … Utilisez ce script avec une extrême prudence : une erreur peut supprimer tous les droits de votre AD. Je ne pourrai pas être tenu responsable d’une mauvaise utilisation.
Import-Module ActiveDirectory

$rootdse = Get-ADRootDSE

$global:guidmap = @{}
Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter '(schemaidguid=*)' -Properties lDAPDisplayName, schemaIDGUID | ForEach-Object {
    $guidmap[$_.lDAPDisplayName] = [System.GUID]$_.schemaIDGUID
}

$acl = Get-Acl 'AD:\xxx'
$oldacl = Get-Acl 'AD:\xxx'

foreach ($access in $acl.Access) {
    $found = $false

    # search in guidmap by value is name
    $found = $guidmap.GetEnumerator() | Where-Object { $_.Value -eq $access.ObjectType } | Where-Object { $_.Name -like 'xxx*' }

    if ($found) {
        Write-Host -ForegroundColor Cyan "$($found.Name) for $($access.IdentityReference) on $($access.ObjectType) with inherited object type $($access.InheritedObjectType)"
        # remove the access rule
        $acl.RemoveAccessRuleSpecific($access)
        continue
    }
    elseif ($access.InheritedObjectType.Guid -eq '00000000-0000-0000-0000-000000000000') {
        continue
    }
    else {
        $found = $guidmap.GetEnumerator() | Where-Object { $_.Value -eq $access.InheritedObjectType } | Where-Object { $_.Name -like 'xxx*' }
        if ($found) {

            $found = $guidmap.GetEnumerator() | Where-Object { $_.Value -eq $access.ObjectType } | Where-Object { $_.Name -like '*xxx*' }

            Write-Host -ForegroundColor Cyan "$($found.Name) for $($access.IdentityReference) on $($access.ObjectType) with inherited object type $($access.InheritedObjectType)"
            # remove the access rule
            $acl.RemoveAccessRuleSpecific($access)
        }
    }


}

$oldacl.Access.count
$acl.Access.count

#Set-Acl -Path "AD:\xxx" -AclObject $acl

Comments

banner-Bastien Perez
Bastien Perez's avatar

Freelance Microsoft 365 - Active Directory - Modern Workplace

France