Private/Find-ESC1Issue.ps1

function Find-ESC1Issue {
    <#
        .SYNOPSIS
        Identifies AD CS certificate templates vulnerable to ESC1 (SAN spoofing) attacks.
 
        .DESCRIPTION
        This function analyzes certificate templates for the conditions that enable ESC1:
        1. CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT (0x1) set in msPKI-Certificate-Name-Flag
        2. Client Authentication EKU (1.3.6.1.5.5.7.3.2) in pKIExtendedKeyUsage
        3. A non-administrative principal holds the Enroll extended right
        4. Template is enabled on at least one CA
        5. Manager approval is not required (msPKI-Enrollment-Flag bit 0x2 not set)
        6. No authorized signatures required (msPKI-RA-Signature = 0)
 
        .PARAMETER AdcsObjects
        Array of AD CS objects from Get-AdcsObjects. The function filters for certificate templates.
 
        .PARAMETER SafeOwners
        Regex pattern of SIDs for principals that are safe to own certificate templates.
 
        .PARAMETER SafeUsers
        Regex pattern of SIDs for principals considered safe (excluded from findings).
 
        .PARAMETER EnrollGUID
        GUID of the Enroll extended right. Defaults to the well-known value.
 
        .INPUTS
        System.DirectoryServices.DirectoryEntry[]
 
        .OUTPUTS
        ESCalatorIssue[] with Technique='ESC1', Subtype='Template-EnrolleeSuppliesSubject'
 
        .EXAMPLE
        $AdcsObjects = Get-AdcsObjects
        $ESC1Issues = Find-ESC1Issue -AdcsObjects $AdcsObjects
 
        .LINK
        https://posts.specterops.io/certified-pre-owned-d95910965cd2
    #>

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

        [Parameter()]
        [string]$SafeOwners = '-519$',

        [Parameter()]
        [string]$SafeUsers = '-512$|-519$|-544$|-18$|-517$|-500$|-516$|-521$|-498$|-9$|-526$|-527$|S-1-5-10',

        [Parameter()]
        [string]$EnrollGUID = '0e10c968-78fb-11d2-90d4-00c04f79dc55',

        [Parameter()]
        [string]$ClientAuthOID = '1.3.6.1.5.5.7.3.2'
    )

    begin {
        . "$PSScriptRoot\ESCalatorIssue.ps1"
        Write-Verbose "Starting ESC1 template vulnerability scan"
    }

    process {
        $Templates = $AdcsObjects | Where-Object { $_.objectClass -contains 'pKICertificateTemplate' }
        Write-Verbose "Processing $($Templates.Count) certificate templates"

        foreach ($Template in $Templates) {
            $templateName = $Template.Properties['name'].Value
            $templateDN = $Template.Properties['distinguishedName'].Value

            # Check 1: SAN allowed (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT)
            $nameFlag = 0
            try { $nameFlag = [int]$Template.Properties['msPKI-Certificate-Name-Flag'].Value } catch { }
            $sanAllowed = ($nameFlag -band 0x1) -eq 0x1
            if (-not $sanAllowed) { continue }

            # Check 2: Client Authentication EKU
            $ekus = @($Template.Properties['pKIExtendedKeyUsage'].Value)
            $clientAuthEnabled = $ekus -contains $ClientAuthOID
            if (-not $clientAuthEnabled) { continue }

            # Check 3: Enabled on at least one CA
            $templateEnabled = $false
            if ($Template.PSObject.Properties['Enabled']) {
                $templateEnabled = $Template.Enabled
            }
            if (-not $templateEnabled) { continue }

            # Check 4: No manager approval required
            $enrollFlag = 0
            try { $enrollFlag = [int]$Template.Properties['msPKI-Enrollment-Flag'].Value } catch { }
            $managerApprovalRequired = ($enrollFlag -band 0x2) -eq 0x2
            if ($managerApprovalRequired) { continue }

            # Check 5: No authorized signatures required
            $raSignature = 0
            try { $raSignature = [int]$Template.Properties['msPKI-RA-Signature'].Value } catch { }
            if ($raSignature -gt 0) { continue }

            # All property conditions met. Now check enroll rights.
            $forestName = if ($templateDN) {
                $parts = $templateDN -split ',DC='
                if ($parts.Count -gt 1) { $parts[1..($parts.Count - 1)] -join '.' } else { 'Unknown' }
            } else { 'Unknown' }

            try {
                $security = $Template.ObjectSecurity
                if (-not $security.Access) { continue }

                foreach ($ace in $security.Access) {
                    try {
                        if ($ace.AccessControlType -ne 'Allow') { continue }

                        # Only care about the Enroll extended right
                        if ($ace.ObjectType -and $ace.ObjectType.Guid -ne $EnrollGUID) { continue }

                        # Resolve identity to SID
                        $aceSID = $null
                        if ($ace.IdentityReference -match '^S-1-') {
                            $aceSID = $ace.IdentityReference.Value
                        } else {
                            try {
                                $acePrincipal = New-Object System.Security.Principal.NTAccount($ace.IdentityReference)
                                $aceSID = $acePrincipal.Translate([System.Security.Principal.SecurityIdentifier]).Value
                            } catch { continue }
                        }

                        # Skip safe principals
                        if ($aceSID -match $SafeOwners -or $aceSID -match $SafeUsers) { continue }

                        Write-Verbose "Found ESC1 issue: $templateName - $($ace.IdentityReference) can enroll with SAN spoofing"

                        [ESCalatorIssue]::CreateOriginalIssue(
                            $forestName,
                            $templateName,
                            $templateDN,
                            $ace.IdentityReference.Value,
                            $aceSID,
                            $ace.ActiveDirectoryRights.ToString(),
                            'ESC1',
                            'Template-EnrolleeSuppliesSubject',
                            "$($ace.IdentityReference) can enroll in this template which allows SAN specification and has Client Authentication EKU, enabling certificate requests with arbitrary Subject Alternative Names.",
                            'Critical',
                            $EnrollGUID,
                            $Template
                        )
                    } catch {
                        Write-Warning "Failed to process ACE for identity $($ace.IdentityReference) on template $templateName : $_"
                    }
                }
            } catch {
                Write-Warning "Failed to analyze security for template $templateName : $_"
            }
        }
    }

    end {
        Write-Verbose "ESC1 template vulnerability scan completed"
    }
}