Private/Set/Set-LowPrivilegeCACertificateManager.ps1

function Set-LowPrivilegeCACertificateManager {
    <#
        .SYNOPSIS
        Adds LowPrivilegeCACertificateManager properties to CA objects based on Certificate Manager assignments.
 
        .DESCRIPTION
        Examines the Certificate Manager assignments on Certificate Authority objects to identify
        principals that are neither high-privilege administrators nor overly broad dangerous groups.
         
        This function identifies "middle ground" Certificate Managers - specific users or groups that have
        Certificate Manager 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 Certificate Managers that may represent specific service accounts, security
        groups, or users that have been granted Certificate Manager 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. LowPrivilegeCACertificateManager: Array of SIDs for custom Certificate Managers
        2. LowPrivilegeCACertificateManagerNames: 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-CACertificateManager.
        These objects must contain the CertificateManagers property.
 
        .INPUTS
        PSCustomObject[]
        You can pipe CA objects to this function.
 
        .OUTPUTS
        PSCustomObject[]
        Returns the input objects with added properties:
        - LowPrivilegeCACertificateManager: Array of SIDs
        - LowPrivilegeCACertificateManagerNames: Array of human-readable names
 
        .EXAMPLE
        $CAs | Set-LowPrivilegeCACertificateManager
        Processes all CA objects and adds the LowPrivilegeCACertificateManager property to each.
 
        .EXAMPLE
        $CAs |
            Set-CACertificateManager |
            Set-LowPrivilegeCACertificateManager |
            Where-Object { $_.LowPrivilegeCACertificateManager.Count -gt 0 }
        Retrieves Certificate Managers, identifies low-privilege ones, and filters to
        only those CAs with custom Certificate Managers.
 
        .EXAMPLE
        $ca = $CAs | Where-Object Name -eq 'MyRootCA'
        $ca | Set-LowPrivilegeCACertificateManager
        if ($ca.LowPrivilegeCACertificateManager) {
            Write-Host "CA has custom Certificate Managers:"
            $ca.LowPrivilegeCACertificateManagerNames | ForEach-Object { Write-Host " $_" }
        }
        Checks a specific CA for custom/low-privilege Certificate Managers 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 CertificateManagers property to be populated by Set-CACertificateManager.
 
        .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 Certificate Managers..."
    }

    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 CertificateManagers property from AdcsObjectStore
                $certificateManagers = if ($script:AdcsObjectStore.ContainsKey($dn)) {
                    $script:AdcsObjectStore[$dn].CertificateManagers
                } else {
                    $_.CertificateManagers
                }

                [array]$lowPrivilegeIdentityReference = if ($certificateManagers) {
                    foreach ($manager in $certificateManagers) {
                        try {
                            # Convert the Certificate Manager name to an NTAccount
                            $ntAccount = New-Object System.Security.Principal.NTAccount($manager.CertificateManager)
                            
                            # 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 Certificate Manager: $($manager.CertificateManager) ($($sid.Value))"
                                # Ensure principal is in PrincipalStore
                                $null = $ntAccount | Resolve-Principal
                                $sid.Value
                            }
                        } catch {
                            Write-Verbose " Failed to process Certificate Manager $($manager.CertificateManager): $($_.Exception.Message)"
                        }
                    }
                } else {
                    Write-Verbose " No CertificateManagers property found"
                    @()
                }

                $lowPrivilegeIdentityReference = $lowPrivilegeIdentityReference | Sort-Object -Unique
                
                if ($lowPrivilegeIdentityReference) {
                    Write-Verbose "CA has $($lowPrivilegeIdentityReference.Count) low privilege Certificate Manager(s): $($lowPrivilegeIdentityReference -join ', ')"
                    
                    # Expand any groups to include their direct members
                    Write-Verbose "Expanding group memberships for low privilege Certificate Managers..."
                    $lowPrivilegeIdentityReference = Expand-GroupMembership -SidList $lowPrivilegeIdentityReference
                    Write-Verbose "After expansion: $($lowPrivilegeIdentityReference.Count) unique principal(s)"
                } else {
                    Write-Verbose "No low privilege Certificate Managers found"
                }

                # Build human-readable names array from PrincipalStore
                [array]$lowPrivilegeCACertificateManagerNames = $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 LowPrivilegeCACertificateManager property
                if ($script:AdcsObjectStore.ContainsKey($dn)) {
                    Write-Verbose " Setting LowPrivilegeCACertificateManager to: $($lowPrivilegeIdentityReference -join ', ')"
                    Write-Verbose " Setting LowPrivilegeCACertificateManagerNames to: $($lowPrivilegeCACertificateManagerNames -join ', ')"
                    $script:AdcsObjectStore[$dn].LowPrivilegeCACertificateManager = $lowPrivilegeIdentityReference
                    $script:AdcsObjectStore[$dn].LowPrivilegeCACertificateManagerNames = $lowPrivilegeCACertificateManagerNames
                    Write-Verbose " After assignment - LowPrivilegeCACertificateManager count: $($script:AdcsObjectStore[$dn].LowPrivilegeCACertificateManager.Count)"
                    Write-Verbose "Updated AD CS Object Store for $dn with LowPrivilegeCACertificateManager"
                }

                # Also add to the pipeline object for backward compatibility
                $_ | Add-Member -NotePropertyName LowPrivilegeCACertificateManager -NotePropertyValue $lowPrivilegeIdentityReference -Force
                $_ | Add-Member -NotePropertyName LowPrivilegeCACertificateManagerNames -NotePropertyValue $lowPrivilegeCACertificateManagerNames -Force
                
                # Return the modified object
                $_
                
            } catch {
                $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $_.Exception,
                    'LowPrivilegeCACertificateManagerProcessingFailed',
                    [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 Certificate Managers."
    }
}