Private/Set/Set-CAEditFlags.ps1

function Set-CAEditFlags {
    <#
        .SYNOPSIS
        Adds EditFlags configuration properties to AD CS Certification Authority objects.
 
        .DESCRIPTION
        For each pKIEnrollmentService (CA) object, queries the CA's EditFlags registry
        configuration using PSCertutil's Get-PCEditFlag cmdlet and stores the results
        in the AdcsObjectStore.
         
        This function specifically tracks the EDITF_ATTRIBUTESUBJECTALTNAME2 flag, which
        when enabled allows requesters to specify arbitrary Subject Alternative Names,
        leading to the ESC6 vulnerability.
         
        The function adds these properties to each CA object:
        - EditFlags: Array of all EditFlag objects returned by Get-PCEditFlag
        - SANFlagEnabled: Boolean indicating if EDITF_ATTRIBUTESUBJECTALTNAME2 is enabled (ESC6)
 
        .PARAMETER AdcsObject
        One or more DirectoryEntry objects representing AD CS Certification Authorities.
        Must have the CAFullName property set (typically by Set-CAComputerPrincipal).
 
        .INPUTS
        System.DirectoryServices.DirectoryEntry[]
        You can pipe CA DirectoryEntry objects to this function.
 
        .OUTPUTS
        System.DirectoryServices.DirectoryEntry[]
        Returns the input objects with added EditFlags properties.
 
        .EXAMPLE
        $cas | Set-CAEditFlags
        Queries EditFlags for all CAs and stores the results.
 
        .EXAMPLE
        $cas | Set-CAComputerPrincipal | Set-CAEditFlags
        Sets up CA identification and then queries EditFlags.
 
        .NOTES
        Requires the PSCertutil module to be installed and loaded.
        This function must be called after Set-CAComputerPrincipal has set the CAFullName property.
         
        The function silently skips CAs that:
        - Don't have a CAFullName property
        - Are unreachable or don't respond to certutil queries
        - Return errors from Get-PCEditFlag
 
        .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 "Querying EditFlags for Certification Authorities..."
    }

    process {
        $AdcsObject | Where-Object SchemaClassName -eq pKIEnrollmentService | ForEach-Object {
            try {
                # Extract CA name for logging
                $caName = if ($_.Properties -and $_.Properties.Contains('cn')) {
                    $_.Properties['cn'][0]
                } elseif ($_.cn) {
                    $_.cn
                } else {
                    'Unknown CA'
                }
                
                Write-Verbose "Processing CA: $caName"
                
                # Get CAFullName from the AdcsObjectStore (where LS2AdcsObject has CAFullName ScriptProperty)
                $dn = $_.Properties.distinguishedName[0]
                $caFullName = if ($script:AdcsObjectStore.ContainsKey($dn)) {
                    $script:AdcsObjectStore[$dn].CAFullName
                } else {
                    $null
                }
                
                if (-not $caFullName) {
                    Write-Verbose " CA '$caName' has no CAFullName property - skipping EditFlags query"
                    $_
                    continue
                }
                
                Write-Verbose " Querying EditFlags for: $caFullName"
                
                try {
                    # Query EditFlags using PSCertutil
                    $editFlags = Get-PCEditFlag -CAFullName $caFullName -ErrorAction Stop
                    
                    if ($editFlags) {
                        Write-Verbose " Retrieved $(@($editFlags).Count) EditFlags"
                        
                        # Check specifically for EDITF_ATTRIBUTESUBJECTALTNAME2
                        $sANFlag = $editFlags | Where-Object { $_.EditFlag -eq 'EDITF_ATTRIBUTESUBJECTALTNAME2' }
                        $sANFlagEnabled = if ($sANFlag) { $sANFlag.Enabled } else { $false }
                        
                        Write-Verbose " EDITF_ATTRIBUTESUBJECTALTNAME2 is $(if ($sANFlagEnabled) { 'enabled' } else { 'disabled' })"
                        
                        # Update the AD CS Object Store
                        $dn = $_.Properties.distinguishedName[0]
                        if ($script:AdcsObjectStore.ContainsKey($dn)) {
                            $script:AdcsObjectStore[$dn].EditFlags = $editFlags
                            $script:AdcsObjectStore[$dn].SANFlagEnabled = $sANFlagEnabled
                            Write-Verbose " Updated AD CS Object Store for $dn with EditFlags data"
                        }
                        
                        # Also add to the pipeline object for backward compatibility
                        $_ | Add-Member -NotePropertyName EditFlags -NotePropertyValue $editFlags -Force
                        $_ | Add-Member -NotePropertyName SANFlagEnabled -NotePropertyValue $sANFlagEnabled -Force
                        
                    } else {
                        Write-Verbose " No EditFlags returned from Get-PCEditFlag"
                    }
                    
                } catch {
                    Write-Verbose " Failed to query EditFlags for '$caFullName': $($_.Exception.Message)"
                    # Continue processing other CAs
                }
                
                # Return the modified object
                $_
                
            } catch {
                $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $_.Exception,
                    'EditFlagQueryFailed',
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $_
                )
                $PSCmdlet.WriteError($errorRecord)
                
                # Still return the object even if processing failed
                $_
            }
        }
    }

    end {
        Write-Verbose "Done querying EditFlags for Certification Authorities."
    }
}