Private/Set/Set-LowPrivilegeCAAdministrator.ps1
|
function Set-LowPrivilegeCAAdministrator { <# .SYNOPSIS Adds LowPrivilegeCAAdministrator properties to CA objects based on CA Administrator assignments. .DESCRIPTION Examines the CA Administrator assignments on Certificate Authority objects to identify principals that are neither high-privilege administrators nor overly broad dangerous groups. This function identifies "middle ground" CA Administrators - specific users or groups that have CA Administrator permissions but aren't part of the standard administrative hierarchy or the dangerous principals that represent broad attack surfaces. The function excludes two categories of principals: 1. Safe/Administrative principals: Domain Admins, Enterprise Admins, SYSTEM, etc. 2. Dangerous principals: Everyone, Authenticated Users, Domain Users, etc. What remains are custom CA Administrators that may represent specific service accounts, security groups, or users that have been granted CA Administrator permissions outside the standard model. These should be reviewed to ensure they align with security policies and least privilege principles for ESC7 vulnerability assessment. The function adds two properties to each CA object: 1. LowPrivilegeCAAdministrator: Array of SIDs for custom CA Administrators 2. LowPrivilegeCAAdministratorNames: Array of human-readable names formatted as "DOMAIN\User (SID)" or "SID (could not resolve)" if the principal cannot be resolved. .PARAMETER AdcsObject One or more CA objects that have been processed by Set-CAAdministrator. These objects must contain the CAAdministrators property. .INPUTS PSCustomObject[] You can pipe CA objects to this function. .OUTPUTS PSCustomObject[] Returns the input objects with added properties: - LowPrivilegeCAAdministrator: Array of SIDs - LowPrivilegeCAAdministratorNames: Array of human-readable names .EXAMPLE $CAs | Set-LowPrivilegeCAAdministrator Processes all CA objects and adds the LowPrivilegeCAAdministrator property to each. .EXAMPLE $CAs | Set-CAAdministrator | Set-LowPrivilegeCAAdministrator | Where-Object { $_.LowPrivilegeCAAdministrator.Count -gt 0 } Retrieves CA Administrators, identifies low-privilege ones, and filters to only those CAs with custom CA Administrators. .EXAMPLE $ca = $CAs | Where-Object Name -eq 'MyRootCA' $ca | Set-LowPrivilegeCAAdministrator if ($ca.LowPrivilegeCAAdministrator) { Write-Host "CA has custom CA Administrators:" $ca.LowPrivilegeCAAdministratorNames | ForEach-Object { Write-Host " $_" } } Checks a specific CA for custom/low-privilege CA Administrators and displays human-readable names. .NOTES Safe/Administrative principals excluded by default: - Domain Admins (-512), Enterprise Admins (-519), Builtin Administrators (-544) - SYSTEM (-18), Builtin Administrator (-500) - Cert Publishers (-517) - Domain Controllers (-516), Read-Only Domain Controllers (-521) - Enterprise Domain Controllers (-498), Enterprise Read-Only Domain Controllers (-9) - Key Admins (-526), Enterprise Key Admins (-527) - SELF (S-1-5-10) Dangerous principals excluded by default: - NULL SID, Everyone, Anonymous Logon, BUILTIN\Users - Authenticated Users, Domain Users, Domain Computers This function requires the CAAdministrators property to be populated by Set-CAAdministrator. .LINK https://posts.specterops.io/certified-pre-owned-d95910965cd2 #> [CmdletBinding()] [OutputType([PSCustomObject[]])] param ( [Parameter(Mandatory, ValueFromPipeline)] [PSCustomObject[]]$AdcsObject ) #requires -Version 5.1 begin { Write-Verbose "Identifying CAs that have low-privilege CA Administrators..." } process { $AdcsObject | ForEach-Object { try { $objectName = if ($_.Name) { $_.Name } else { $_.DistinguishedName } Write-Verbose "Processing CA: $objectName" # Get the distinguished name - handle both DirectoryEntry and LS2AdcsObject $dn = if ($_.Properties.distinguishedName) { $_.Properties.distinguishedName[0] } else { $_.DistinguishedName } # Get the CAAdministrators property from AdcsObjectStore $caAdministrators = if ($script:AdcsObjectStore.ContainsKey($dn)) { $script:AdcsObjectStore[$dn].CAAdministrators } else { $_.CAAdministrators } [array]$lowPrivilegeIdentityReference = if ($caAdministrators) { foreach ($admin in $caAdministrators) { try { # Convert the CA Administrator name to an NTAccount $ntAccount = New-Object System.Security.Principal.NTAccount($admin.CAAdministrator) # Translate to SID $sid = $ntAccount | Convert-IdentityReferenceToSid # Check if this is a low-privilege principal $isLowPrivilege = $sid.Value | Test-IsLowPrivilegePrincipal if ($isLowPrivilege) { Write-Verbose " Low-privilege CA Administrator: $($admin.CAAdministrator) ($($sid.Value))" # Ensure principal is in PrincipalStore $null = $ntAccount | Resolve-Principal $sid.Value } } catch { Write-Verbose " Failed to process CA Administrator $($admin.CAAdministrator): $($_.Exception.Message)" } } } else { Write-Verbose " No CAAdministrators property found" @() } $lowPrivilegeIdentityReference = $lowPrivilegeIdentityReference | Sort-Object -Unique if ($lowPrivilegeIdentityReference) { Write-Verbose "CA has $($lowPrivilegeIdentityReference.Count) low privilege CA Administrator(s): $($lowPrivilegeIdentityReference -join ', ')" # Expand any groups to include their direct members Write-Verbose "Expanding group memberships for low privilege CA Administrators..." $lowPrivilegeIdentityReference = Expand-GroupMembership -SidList $lowPrivilegeIdentityReference Write-Verbose "After expansion: $($lowPrivilegeIdentityReference.Count) unique principal(s)" } else { Write-Verbose "No low privilege CA Administrators found" } # Build human-readable names array from PrincipalStore [array]$lowPrivilegeCAAdministratorNames = $lowPrivilegeIdentityReference | ForEach-Object { if ($script:PrincipalStore -and $script:PrincipalStore.ContainsKey($_)) { $name = $script:PrincipalStore[$_].ntAccountName if ($name) { "$name ($_)" } else { "$_ (could not resolve)" } } else { "$_ (could not resolve)" } } | Sort-Object -Unique # Update the AD CS Object Store with the LowPrivilegeCAAdministrator property if ($script:AdcsObjectStore.ContainsKey($dn)) { Write-Verbose " Setting LowPrivilegeCAAdministrator to: $($lowPrivilegeIdentityReference -join ', ')" Write-Verbose " Setting LowPrivilegeCAAdministratorNames to: $($lowPrivilegeCAAdministratorNames -join ', ')" $script:AdcsObjectStore[$dn].LowPrivilegeCAAdministrator = $lowPrivilegeIdentityReference $script:AdcsObjectStore[$dn].LowPrivilegeCAAdministratorNames = $lowPrivilegeCAAdministratorNames Write-Verbose " After assignment - LowPrivilegeCAAdministrator count: $($script:AdcsObjectStore[$dn].LowPrivilegeCAAdministrator.Count)" Write-Verbose "Updated AD CS Object Store for $dn with LowPrivilegeCAAdministrator" } # Also add to the pipeline object for backward compatibility $_ | Add-Member -NotePropertyName LowPrivilegeCAAdministrator -NotePropertyValue $lowPrivilegeIdentityReference -Force $_ | Add-Member -NotePropertyName LowPrivilegeCAAdministratorNames -NotePropertyValue $lowPrivilegeCAAdministratorNames -Force # Return the modified object $_ } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( $_.Exception, 'LowPrivilegeCAAdministratorProcessingFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $_ ) $PSCmdlet.WriteError($errorRecord) # Still return the object even if processing failed $_ } } } end { Write-Verbose "Done identifying CAs that have low-privilege CA Administrators." } } |