Private/Set/Set-DangerousEnrollee.ps1

function Set-DangerousEnrollee {
    <#
        .SYNOPSIS
        Adds a DangerousEnrollee property to AD CS certificate template objects.
 
        .DESCRIPTION
        Examines the access control lists (ACLs) of Active Directory Certificate Services
        certificate template objects to identify overly permissive enrollment permissions.
         
        The function checks for enrollment permissions granted to well-known dangerous principals
        that represent overly broad groups. These should typically not have enrollment permissions,
        as they can lead to privilege escalation vulnerabilities.
         
        This is a critical check for ESC1 and other AD CS vulnerability detection, as
        templates with dangerous enrollees combined with other risky settings can allow
        unauthorized users to obtain certificates for privilege escalation.
         
        The function adds two properties to each template object:
        1. DangerousEnrollee: Array of SIDs for dangerous principals
        2. DangerousEnrolleeNames: 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 DirectoryEntry objects representing AD CS certificate templates.
        These objects must contain ObjectSecurity.Access information.
 
        .PARAMETER Credential
        PSCredential for authenticating to Active Directory. Required for LDAP queries
        when converting identity references to directory entries and SIDs.
 
        .PARAMETER RootDSE
        A DirectoryEntry object for the RootDSE. Used to determine the domain context
        for LDAP queries when resolving principals.
 
        .INPUTS
        System.DirectoryServices.DirectoryEntry[]
        You can pipe certificate template DirectoryEntry objects to this function.
 
        .OUTPUTS
        System.DirectoryServices.DirectoryEntry[]
        Returns the input objects with added properties:
        - DangerousEnrollee: Array of SIDs
        - DangerousEnrolleeNames: Array of human-readable names
 
        .EXAMPLE
        $templates = Get-AdcsObject |
            Where-Object { $_.objectClass -contains 'pKICertificateTemplate' }
        $templates | Set-DangerousEnrollee
        Processes all certificate templates and adds the DangerousEnrollee property to each.
 
        .EXAMPLE
        Get-AdcsObject |
            Set-DangerousEnrollee |
            Where-Object { $_.DangerousEnrollee.Count -gt 0 }
        Retrieves all AD CS objects, adds DangerousEnrollee property, and filters to
        only those with dangerous enrollees.
 
        .EXAMPLE
        $template = Get-AdcsObject | Where-Object Name -eq 'WebServer'
        $template | Set-DangerousEnrollee
        if ($template.DangerousEnrollee) {
            Write-Host "Template has dangerous enrollees:"
            $template.DangerousEnrolleeNames | ForEach-Object { Write-Host " $_" }
        }
        Checks a specific template for dangerous enrollees and displays human-readable names.
 
        .NOTES
        Well-known dangerous principals checked by default:
        - S-1-0-0: NULL SID
        - S-1-1-0: Everyone (all users possibly including anonymous)
        - S-1-5-7: Anonymous Logon
        - S-1-5-32-545: BUILTIN\Users
        - S-1-5-11: Authenticated Users
        - SIDs ending in -513: Domain Users groups
        - SIDs ending in -515: Domain Computers groups
         
        Templates with these principals having enrollment permissions are considered
        high-risk when combined with other misconfigurations like allowing SAN specification.
 
        .LINK
        https://posts.specterops.io/certified-pre-owned-d95910965cd2
    #>

    [CmdletBinding()]
    [OutputType([System.DirectoryServices.DirectoryEntry[]])]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.DirectoryServices.DirectoryEntry[]]$AdcsObject
    )

    #requires -Version 5.1

    begin {
        Write-Verbose "Identifying templates that allow dangerous principals to enroll..."
    }

    process {
        $AdcsObject | Where-Object SchemaClassName -eq pKICertificateTemplate | ForEach-Object {
            try {
                $objectName = if ($_.Properties.displayName.Count -gt 0) {
                    $_.Properties.displayName[0] 
                } elseif ($_.Properties.name.Count -gt 0) {
                    $_.Properties.name[0]
                } else {
                    $_.Properties.distinguishedName[0]
                }
                Write-Verbose "Processing template: $objectName"
                
                [array]$dangerousIdentityReference = foreach ($ace in $_.ObjectSecurity.Access) {
                    $aceSid = $ace.IdentityReference | Convert-IdentityReferenceToSid
                    $isDangerousEnrollee = $aceSid | Test-IsDangerousPrincipal
                    if ($isDangerousEnrollee) {
                        $isEnrollmentAce = Test-IsEnrollmentAce -Ace $ace
                        if ($isEnrollmentAce) {
                            Write-Verbose "Dangerous enrollee found: $($ace.IdentityReference)"
                            # Ensure the principal is in the store (triggers cache population)
                            $null = $ace.IdentityReference | Resolve-Principal
                            # Convert to SID and return as the key to PrincipalStore
                            $aceSid.Value
                        }
                    }
                }

                $dangerousIdentityReference = $dangerousIdentityReference | Sort-Object -Unique
                
                if ($dangerousIdentityReference) {
                    Write-Verbose "Template has $($dangerousIdentityReference.Count) dangerous enrollee(s): $($dangerousIdentityReference -join ', ')"
                } else {
                    Write-Verbose "No dangerous enrollees found in template"
                }

                # Build human-readable names array from PrincipalStore
                [array]$dangerousEnrolleeNames = $dangerousIdentityReference | 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 DangerousEnrollee property
                $dn = $_.Properties.distinguishedName[0]
                if ($script:AdcsObjectStore.ContainsKey($dn)) {
                    $script:AdcsObjectStore[$dn] | Add-Member -NotePropertyName DangerousEnrollee -NotePropertyValue $dangerousIdentityReference -Force
                    $script:AdcsObjectStore[$dn] | Add-Member -NotePropertyName DangerousEnrolleeNames -NotePropertyValue $dangerousEnrolleeNames -Force
                    Write-Verbose "Updated AD CS Object Store for $dn with DangerousEnrollee"
                }

                # Also add to the pipeline object for backward compatibility
                $_ | Add-Member -NotePropertyName DangerousEnrollee -NotePropertyValue $dangerousIdentityReference -Force
                $_ | Add-Member -NotePropertyName DangerousEnrolleeNames -NotePropertyValue $dangerousEnrolleeNames -Force
                
                # Return the modified object
                $_
                
            } catch {
                $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $_.Exception,
                    'DangerousEnrolleeProcessingFailed',
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $_
                )
                $PSCmdlet.WriteError($errorRecord)
                
                # Still return the object even if processing failed
                $_
            }
        }
    }

    end {
        Write-Verbose "Done identifying templates that allow dangerous principals to enroll."
    }
}