Private/Find-ESC5p5Combo.ps1

function Find-ESC5p5Combo {
    <#
        .SYNOPSIS
        Find Critical ESC5 (CertTemplatesContainer and EnrollmentService subtypes) vulnerability combinations.
 
        .DESCRIPTION
        This function analyzes ESCalatorIssue objects to identify dangerous combinations of Critical ESC5 vulnerabilities
        where a principal has both CertTemplatesContainer and EnrollmentService subtype vulnerabilities.
         
        CertTemplatesContainer subtypes allow modification of certificate template containers, while EnrollmentService
        subtypes allow control over enrollment services. Having both provides comprehensive control over the
        certificate infrastructure.
 
        .PARAMETER Issues
        An array of ESCalatorIssue objects to analyze for Critical ESC5 combinations.
 
        .PARAMETER Principal
        Optional. A DirectoryEntry object representing a specific principal to analyze. If not provided,
        analyzes combinations for the current user context.
 
        .INPUTS
        System.Object[] - Array of ESCalatorIssue objects
        System.DirectoryServices.DirectoryEntry - Optional principal object
 
        .OUTPUTS
        PSCustomObject[] - Array of ESC5_Combination objects containing detailed information about
        Critical ESC5 CertTemplatesContainer and EnrollmentService combinations.
 
        .EXAMPLE
        Find-ESC5p5Combo -Issues $AllIssues
         
        Finds Critical ESC5 combinations for the current user.
 
        .EXAMPLE
        Find-ESC5p5Combo -Issues $AllIssues -Principal $horsePrincipal -Verbose
         
        Finds Critical ESC5 combinations for a specific principal with verbose output.
 
        .NOTES
        This function specifically looks for:
        - Critical ESC5 issues with subtypes starting with "CertTemplatesContainer"
        - Critical ESC5 issues with subtypes starting with "EnrollmentService"
        - Creates combination objects when a principal has both types
         
        Requires PowerShell 7.4 or later.
 
        .LINK
        https://github.com/jakehildreth/ESCalator
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNull()]
        [object[]]$Issues,
        
        [Parameter()]
        [System.DirectoryServices.DirectoryEntry]$Principal
    )


    begin {
        Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd hh:mm:ss')] Starting $($MyInvocation.MyCommand) on $env:COMPUTERNAME..."
        
        # Initialize results array
        $dangerousCombinations = @()
        
        # Get principal display name for logging
        if ($Principal) {
            $principalDisplayName = $null
            if ($Principal.Properties['sAMAccountName'].Value) {
                $principalDisplayName = $Principal.Properties['sAMAccountName'].Value
            } elseif ($Principal.Properties['name'].Value) {
                $principalDisplayName = $Principal.Properties['name'].Value
            } elseif ($Principal.Properties['distinguishedName'].Value) {
                $principalDisplayName = $Principal.Properties['distinguishedName'].Value
            } else {
                $principalDisplayName = "Unknown"
            }
            Write-Verbose "Analyzing Critical ESC5 CertTemplatesContainer/EnrollmentService combinations for specific principal: $principalDisplayName"
        } else {
            $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
            $currentUserName = $currentUser.Name
            Write-Verbose "No principal specified - analyzing Critical ESC5 CertTemplatesContainer/EnrollmentService combinations for current user: $currentUserName"
        }
    }

    process {
        # Step 1: Find Critical ESC5 issues with CertTemplatesContainer subtypes
        Write-Verbose "Step 1: Finding Critical ESC5 issues with CertTemplatesContainer subtypes..."
        $esc5CertTemplatesIssues = @()
        
        # Step 2: Find Critical ESC5 issues with EnrollmentService subtypes
        Write-Verbose "Step 2: Finding Critical ESC5 issues with EnrollmentService subtypes..."
        $esc5EnrollmentIssues = @()
        
        # Get principal info for ESC5 filtering
        $targetPrincipalSid = $null
        $targetPrincipalName = $null
        
        if ($Principal) {
            if ($Principal.Properties['objectSid'].Value) {
                $targetPrincipalSid = (New-Object System.Security.Principal.SecurityIdentifier($Principal.Properties['objectSid'].Value, 0)).Value
            }
            if ($Principal.Properties['sAMAccountName'].Value) {
                $targetPrincipalName = $Principal.Properties['sAMAccountName'].Value
            } elseif ($Principal.Properties['name'].Value) {
                $targetPrincipalName = $Principal.Properties['name'].Value
            }
        } else {
            $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
            $targetPrincipalSid = $currentUser.User.Value
            $targetPrincipalName = $currentUser.Name
        }
        
        # Filter for Critical ESC5 issues with CertTemplatesContainer and EnrollmentService subtypes
        foreach ($issue in $Issues) {
            # Skip non-ESCalatorIssue objects
            if ($issue.PSObject.TypeNames[0] -ne 'ESCalatorIssue') {
                continue
            }
            
            # Filter for Critical ESC5 issues only
            if ($issue.Technique -ne 'ESC5' -or $issue.Severity -ne 'Critical') {
                continue
            }
            
            # Check if issue applies to target principal
            $appliesToPrincipal = $false
            
            # Check by SID
            if ($targetPrincipalSid -and $issue.IdentityReferenceSID -eq $targetPrincipalSid) {
                $appliesToPrincipal = $true
                Write-Verbose "ESC5 issue matches principal by SID: $targetPrincipalSid"
            }
            
            # Check by name if SID match fails
            if (-not $appliesToPrincipal -and $targetPrincipalName -and $issue.Principal) {
                $shortName = $targetPrincipalName -replace '^.*\\', ''
                if ($issue.Principal -like "*$targetPrincipalName*" -or $issue.Principal -like "*$shortName*") {
                    $appliesToPrincipal = $true
                    Write-Verbose "ESC5 issue matches principal by name: $targetPrincipalName"
                }
            }
            
            if ($appliesToPrincipal) {
                # Check for CertTemplatesContainer subtypes
                if ($issue.Subtype -like "CertTemplatesContainer*") {
                    $esc5CertTemplatesIssues += $issue
                    Write-Verbose "Found Critical ESC5 CertTemplatesContainer issue: $($issue.Name) - $($issue.Subtype)"
                }
                
                # Check for EnrollmentService subtypes
                if ($issue.Subtype -like "EnrollmentService*") {
                    $esc5EnrollmentIssues += $issue
                    Write-Verbose "Found Critical ESC5 EnrollmentService issue: $($issue.Name) - $($issue.Subtype)"
                }
            }
        }
        
        Write-Verbose "Found $($esc5CertTemplatesIssues.Count) Critical ESC5 CertTemplatesContainer issue(s)"
        Write-Verbose "Found $($esc5EnrollmentIssues.Count) Critical ESC5 EnrollmentService issue(s)"
        
        # Step 3: Create combination objects if both types exist
        if ($esc5CertTemplatesIssues.Count -gt 0 -and $esc5EnrollmentIssues.Count -gt 0) {
            Write-Verbose "Step 3: Creating dangerous combination objects..."
            
            # Group issues by principal for combination analysis
            $principalCombinations = @{}
            
            # Add ESC5 CertTemplatesContainer issues to combinations
            foreach ($certTemplatesIssue in $esc5CertTemplatesIssues) {
                $principalKey = $certTemplatesIssue.IdentityReferenceSID -or $certTemplatesIssue.Principal -or "Unknown"
                if (-not $principalCombinations[$principalKey]) {
                    $principalCombinations[$principalKey] = @{
                        PrincipalSID = $certTemplatesIssue.IdentityReferenceSID
                        PrincipalName = $certTemplatesIssue.IdentityReference
                        ESC5CertTemplatesIssues = @()
                        ESC5EnrollmentIssues = @()
                    }
                }
                $principalCombinations[$principalKey].ESC5CertTemplatesIssues += $certTemplatesIssue
            }
            
            # Add ESC5 enrollment issues to combinations
            foreach ($enrollmentIssue in $esc5EnrollmentIssues) {
                $principalKey = $enrollmentIssue.IdentityReferenceSID -or $enrollmentIssue.Principal -or "Unknown"
                if ($principalCombinations[$principalKey]) {
                    $principalCombinations[$principalKey].ESC5EnrollmentIssues += $enrollmentIssue
                }
            }
            
            # Create combination objects for principals with both types
            foreach ($principalKey in $principalCombinations.Keys) {
                $combo = $principalCombinations[$principalKey]
                if ($combo.ESC5CertTemplatesIssues.Count -gt 0 -and $combo.ESC5EnrollmentIssues.Count -gt 0) {
                    $combinationObject = [PSCustomObject]@{
                        PSTypeName = 'ESC5_Combination'
                        PrincipalSID = $combo.PrincipalSID
                        PrincipalName = $combo.PrincipalName
                        ESC5CertTemplatesCount = $combo.ESC5CertTemplatesIssues.Count
                        ESC5EnrollmentCount = $combo.ESC5EnrollmentIssues.Count
                        ESC5CertTemplatesIssues = $combo.ESC5CertTemplatesIssues
                        ESC5EnrollmentIssues = $combo.ESC5EnrollmentIssues
                        CertTemplatesContainers = ($combo.ESC5CertTemplatesIssues | ForEach-Object { $_.Name } | Sort-Object -Unique)
                        EnrollmentServices = ($combo.ESC5EnrollmentIssues | ForEach-Object { $_.Name } | Sort-Object -Unique)
                        CertTemplatesSubtypes = ($combo.ESC5CertTemplatesIssues | ForEach-Object { $_.Subtype } | Sort-Object -Unique)
                        EnrollmentSubtypes = ($combo.ESC5EnrollmentIssues | ForEach-Object { $_.Subtype } | Sort-Object -Unique)
                        RiskLevel = "Critical"
                        Attack = "Certificate Templates Container Control (ESC5) + Template Enablement (ESC5)"
                    }
                    
                    $dangerousCombinations += $combinationObject
                    Write-Verbose "Created dangerous combination for principal: $($combo.PrincipalName) (CertTemplates: $($combo.ESC5CertTemplatesIssues.Count), EnrollmentService: $($combo.ESC5EnrollmentIssues.Count))"
                }
            }
        } else {
            Write-Verbose "Step 3: No dangerous combinations found (CertTemplatesContainer: $($esc5CertTemplatesIssues.Count), EnrollmentService: $($esc5EnrollmentIssues.Count))"
        }
    }

    end {
        Write-Verbose "Found $($dangerousCombinations.Count) dangerous ESC5 combination(s)"
        
        if ($dangerousCombinations.Count -gt 0) {
            $totalCertTemplates = ($dangerousCombinations | Measure-Object -Property ESC5CertTemplatesCount -Sum).Sum
            $totalEnrollment = ($dangerousCombinations | Measure-Object -Property ESC5EnrollmentCount -Sum).Sum
            $uniquePrincipals = ($dangerousCombinations | Select-Object -ExpandProperty PrincipalName | Sort-Object -Unique).Count
            
            Write-Verbose " Total ESC5 CertTemplatesContainer issues: $totalCertTemplates"
            Write-Verbose " Total ESC5 EnrollmentService issues: $totalEnrollment"
            Write-Verbose " Affected principals: $uniquePrincipals"
            
            # Log containers and services
            $allContainers = $dangerousCombinations | ForEach-Object { $_.CertTemplatesContainers } | Sort-Object -Unique
            $allServices = $dangerousCombinations | ForEach-Object { $_.EnrollmentServices } | Sort-Object -Unique
            
            if ($allContainers) {
                Write-Verbose " Vulnerable certificate template containers: $($allContainers -join ', ')"
            }
            if ($allServices) {
                Write-Verbose " Compromised enrollment services: $($allServices -join ', ')"
            }
        }
        
        Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd hh:mm:ss')] Finishing $($MyInvocation.MyCommand) on $env:COMPUTERNAME..."
        
        # Return the combination objects
        return $dangerousCombinations
    }
}