Private/Set/Set-HasNonStandardOwner.ps1
|
function Set-HasNonStandardOwner { <# .SYNOPSIS Adds HasNonStandardOwner property to AD CS objects based on their owner. .DESCRIPTION Examines the ownership of Active Directory Certificate Services objects to identify whether they have non-standard owners. Standard owners are typically high-privilege administrative groups such as: - Enterprise Admins (SID ending in -519) - Domain Admins (SID ending in -512) - Administrators (SID ending in -544) - SYSTEM (S-1-5-18) - Enterprise Domain Controllers (SID ending in -516) - Schema Admins (SID ending in -518) Objects owned by other principals may represent misconfiguration or potential security risks, as they could allow unauthorized modification of critical PKI infrastructure. The function sets the HasNonStandardOwner property on each object: - $true if the owner is NOT in the standard owner list - $false if the owner IS in the standard owner list - $null if ownership cannot be determined .PARAMETER AdcsObject One or more LS2AdcsObject or DirectoryEntry objects representing AD CS objects. These objects must have Owner property populated. .INPUTS LS2AdcsObject[] System.DirectoryServices.DirectoryEntry[] You can pipe AD CS objects to this function. .OUTPUTS LS2AdcsObject System.DirectoryServices.DirectoryEntry Returns the input objects with HasNonStandardOwner property added. .EXAMPLE $templates = Get-AdcsObject | Where-Object { $_.IsCertificateTemplate() } $templates | Set-HasNonStandardOwner Processes all certificate templates and sets the HasNonStandardOwner property. .EXAMPLE Get-AdcsObject | Set-HasNonStandardOwner | Where-Object { $_.HasNonStandardOwner -eq $true } Retrieves all AD CS objects, sets HasNonStandardOwner, and filters to only those with non-standard owners. .EXAMPLE $template = Get-AdcsObject | Where-Object Name -eq 'WebServer' $template | Set-HasNonStandardOwner if ($template.HasNonStandardOwner) { Write-Host "Template has non-standard owner: $($template.Owner)" } Checks a specific template for non-standard ownership. .NOTES Standard owner SID patterns: - S-1-5-18: SYSTEM - S-1-5-32-544: BUILTIN\Administrators - SIDs ending in -512: Domain Admins - SIDs ending in -516: Domain Controllers - SIDs ending in -518: Schema Admins - SIDs ending in -519: Enterprise Admins - SIDs ending in -521: Read-Only Domain Controllers Templates and CAs with non-standard owners may be vulnerable to ESC4-style attacks where the owner can modify critical security settings. .LINK https://posts.specterops.io/certified-pre-owned-d95910965cd2 #> [CmdletBinding()] [OutputType([LS2AdcsObject[]])] param ( [Parameter(Mandatory, ValueFromPipeline)] [object[]]$AdcsObject ) #requires -Version 5.1 begin { Write-Verbose "Identifying objects with non-standard owners..." # Use script-level StandardOwners (populated by Initialize-PrincipalDefinitions) if (-not $script:StandardOwners -or $script:StandardOwners.Count -eq 0) { Write-Warning "StandardOwners not initialized. Cannot validate object ownership." return } Write-Verbose "Using $($script:StandardOwners.Count) standard owner patterns" } process { foreach ($object in $AdcsObject) { try { $objectName = if ($object.displayName) { $object.displayName } elseif ($object.name) { $object.name } elseif ($object.Properties -and $object.Properties.Contains('displayName')) { $object.Properties.displayName[0] } elseif ($object.Properties -and $object.Properties.Contains('name')) { $object.Properties.name[0] } elseif ($object.distinguishedName) { $object.distinguishedName } elseif ($object.Properties -and $object.Properties.Contains('distinguishedName')) { $object.Properties.distinguishedName[0] } else { 'Unknown' } Write-Verbose "Processing object: $objectName" # Get the owner $owner = $null if ($object.Owner) { $owner = $object.Owner } elseif ($object.ObjectSecurity -and $object.ObjectSecurity.Owner) { $owner = $object.ObjectSecurity.Owner } elseif ($object.nTSecurityDescriptor -and $object.nTSecurityDescriptor.Owner) { $owner = $object.nTSecurityDescriptor.Owner } if (-not $owner) { Write-Verbose "Could not determine owner for $objectName" $hasNonStandardOwner = $null } else { Write-Verbose "Owner: $owner" # Test if owner is a standard owner $isStandardOwner = Test-IsStandardOwner -OwnerIdentity $owner if ($isStandardOwner) { $hasNonStandardOwner = $false } else { $hasNonStandardOwner = $true } } # Set the HasNonStandardOwner property on the pipeline object if ($object.PSObject.Properties['HasNonStandardOwner']) { $object.HasNonStandardOwner = $hasNonStandardOwner } else { $object | Add-Member -NotePropertyName HasNonStandardOwner -NotePropertyValue $hasNonStandardOwner -Force } # Update the AdcsObjectStore with the HasNonStandardOwner property $dn = $null if ($object.distinguishedName) { # Handle PropertyValueCollection from DirectoryEntry if ($object.distinguishedName -is [System.DirectoryServices.PropertyValueCollection]) { $dn = $object.distinguishedName.Value } else { $dn = $object.distinguishedName } } elseif ($object.Properties -and $object.Properties.Contains('distinguishedName')) { $dn = $object.Properties.distinguishedName[0] } if ($dn -and $script:AdcsObjectStore.ContainsKey($dn)) { $script:AdcsObjectStore[$dn].HasNonStandardOwner = $hasNonStandardOwner Write-Verbose "Updated AD CS Object Store for $dn with HasNonStandardOwner = $hasNonStandardOwner" } # Return the modified object $object } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( $_.Exception, 'HasNonStandardOwnerProcessingFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $object ) $PSCmdlet.WriteError($errorRecord) # Still return the object even if processing failed $object } } } end { Write-Verbose "Done identifying objects with non-standard owners." } } |