ADACLScanner is a PowerShell tool, created by Robin Granberg (formerly a PFE at Microsoft and now at Semperis). The tool can be accessed on https://github.com/canix1/ADACLScanner
It's designed to audit Active Directory permissions, export them in CSV/HTML formats, compare them to a previous export or another domain, or to default permissions. The tool also provides features to know the permissions of a user or group, identify the owner of an object, the date of the last rights modification, inherited rights, and much more.
It can be used via a graphical interface (GUI) or command line. The project is ongoing, and Robin is committed to fixing errors reported via Github.
Command Line Usage
-RecursiveFind
This parameter searches all nested groups to display all security principals having access.
-RecursiveObjectType
This parameter filters nested groups to only display users who have access.
-FilterTrustee
ACL filter for matching strings in Trustee.
-Owner
To obtain the owners.
Search examples
Custom ACLs on all OUs and containers
$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 and last modified date
.\ADACLScan.ps1 -Base "CN=AdminSDHolder,CN=System,$((Get-ADRootDSE).defaultNamingContext)" -SDDate -ShowCriticalityColor -Show -Output HTML
Obtain rights for a specific user/group
.\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter "(|(objectClass=organizationalUnit)(objectClass=container))" -SkipDefaults -SkipBuiltIn -ShowCriticalityColor -Output HTML -Show -EffectiveRightsPrincipal domain\user
GPO rights
.\ADACLScan.ps1 -GPO -Scope subtree -ShowCriticalityColor -Output HTML -Show -RecursiveFind
Get owners
.\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Owner -Scope subtree -Filter '(objectClass=*)' | Where-Object {$_.Access -eq 'Owner'}
Get Deny permissions
Search for permissions Deny
(can be useful for checking ManagedBy
or other useful hidden attributes).
.\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter '(objectClass=*)' | Where-Object {$_.Access -eq 'Deny'}
Unresolved SIDs - all naming context and proprietary naming
⚠️ Execution time depends on number of objects⚠️
[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($_)
}
}
Unresolved SIDs - all objects in the domain partition
⚠️ Execution time depends on number of objects⚠️
$unresolvedSIDArray = .\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter '(objectClass=*)' -Owner -FilterTrustee 'S-1-5*'
Unresolved SIDs on OUs and containers only
⚠️ Execution time depends on number of objects⚠️
$unresolvedSIDArray= .\ADACLScan.ps1 -Base (Get-ADRootDSE).defaultNamingContext -Scope subtree -Filter '(|(objectClass=organizationalUnit)(objectClass=container))' -Owner -FilterTrustee 'S-1-5*'
Unresolved SIDs on GPO only
⚠️ Execution time depends on number of objects⚠️
[System.Collections.Generic.List[PSObject]]$unresolvedSIDArray = @()
$unresolvedSID = .\ADACLScan.ps1 -GPO -Scope subtree -Owner -FilterTrustee 'S-1-5*'
$unresolvedSID | ForEach-Object {
$unresolvedSIDArray.add($_)
}
Unresolved SIDs on AD-integrated DNS partitions
$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($_)
}
}
DCsync permission
The RecursiveFind
parameter searches recursively for group members.
.\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'}
Advanced use of results
If you wish to delete unknown SIDs or modify the owner, you can use the following commands.
Delete unknown SIDs
Some useful information:
Import-Module ActiveDirectory
is required to access theAD:\
drive.- You need to use
RemoveAccessRuleSpecific()
instead ofRemoveAccessRule()
.
The reason is that with the initial method, the algorithm cannot always accurately identify the ACE to remove.
RemoveAccessRuleSpecific() scans the ACL to find the exact ACE and removes only that one.
Detailed explanation here:
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
}
For reference, here is the method using RemoveAccessRule
, but as mentioned earlier, it does not always work. I am keeping it for historical purposes.
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
}
$DNWithUnresolvedSID = ($unresolvedSIDArray | Where-Object {$_.Access -ne 'Owner'}).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
}
Change owner
$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
}
Comments