Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions Invoke-ADeleginator.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@ function Invoke-ADeleginator {
([ADSISearcher]"samaccountname=$($env:USERNAME)").Findone().Properties.memberof -replace '^CN=([^,]+).+$','$1'
}

# Resolve the directory CN of a well-known principal by its SID. On non-English
# DCs these objects carry a localized CN (e.g. "Domaenen-Admins"); reading the CN
# straight from the directory -- the same source ADeleg reads -- returns the exact
# name ADeleg writes to its report, regardless of the OS or domain UI language.
function Get-LocalizedWellKnownName {
[CmdletBinding()]
Param(
[System.Security.Principal.WellKnownSidType]$WellKnownSid,
[System.Security.Principal.SecurityIdentifier]$DomainSid
)

try {
$Sid = [System.Security.Principal.SecurityIdentifier]::new($WellKnownSid, $DomainSid)
$Searcher = [ADSISearcher]"(objectSid=$($Sid.Value))"
$Searcher.PropertiesToLoad.Add('cn') > $null
$Result = $Searcher.FindOne()
if ($Result) { $Result.Properties['cn'][0] }
} catch {
Write-Verbose "Could not resolve well-known SID '$WellKnownSid': $_"
}
}

# Create ADeleg csv or json report in the current directory
function Create-ADelegReport{
[CmdletBinding()]
Expand Down Expand Up @@ -101,6 +123,43 @@ function Invoke-ADeleginator {
$Tier0Resources = 'Account Operators|Administrator|Administrators|AdminSDHolder|Backup Operators|Cryptographic Operators|Distributed COM Users|Domain Admins|Domain Controllers|Domain Controllers (OU)|Domain root object|DnsAdmins|Enterprise Admins|GPO linked to Tier Zero container|krbtgt|Print Operators|RODC computer object|Schema Admins|Server Operators|Users (container)'
$UnsafeDelegations = 'owns|write all properties|create child objects|delete child objects|Change the owner|add/delete delegations|delete'

# The patterns above are English. On a localized DC, ADeleg writes these
# principals with their localized CN (e.g. "Domaenen-Admins" for Domain Admins),
# so the English patterns never match and insecure delegations are silently
# missed ("Eureka!"). Resolve each well-known principal's CN by its SID and add
# it to the patterns. On English domains this re-adds the English names (a
# harmless no-op). Universal SIDs (Everyone, Authenticated Users) are always
# written in English by ADeleg, so they stay as the static patterns above.
try {
$DomainSid = [System.Security.Principal.SecurityIdentifier]::new([byte[]]([ADSI]'').objectSid.Value, 0).AccountDomainSid
$WKS = [System.Security.Principal.WellKnownSidType]

$DomainUsersName = Get-LocalizedWellKnownName -WellKnownSid $WKS::AccountDomainUsersSid -DomainSid $DomainSid
if ($DomainUsersName) { $UnsafeTrustees += '|' + [regex]::Escape($DomainUsersName) }

$Tier0WellKnownSids = @(
$WKS::BuiltinAdministratorsSid
$WKS::BuiltinAccountOperatorsSid
$WKS::BuiltinSystemOperatorsSid
$WKS::BuiltinPrintOperatorsSid
$WKS::BuiltinBackupOperatorsSid
$WKS::AccountDomainAdminsSid
$WKS::AccountEnterpriseAdminsSid
$WKS::AccountSchemaAdminsSid
$WKS::AccountControllersSid
$WKS::WinAccountReadonlyControllersSid
)
foreach ($WellKnownSid in $Tier0WellKnownSids) {
$Name = Get-LocalizedWellKnownName -WellKnownSid $WellKnownSid -DomainSid $DomainSid
if ($Name) { $Tier0Resources += '|' + [regex]::Escape($Name) }
}
} catch {
Write-Warning "Could not resolve localized well-known names ($_). Detection falls back to English names only."
}

Write-Verbose "UnsafeTrustees pattern: $UnsafeTrustees"
Write-Verbose "Tier0Resources pattern: $Tier0Resources"

$CurrentUserGroups = Get-CurrentUserGroups
if ($CurrentUserGroups -notmatch $Tier0Resources) {
$UnsafeTrustees += "|" + $CurrentUserGroups
Expand Down