Analyzers/Test-Esc13.ps1

function Test-Esc13 {
    <#
    .SYNOPSIS
        ESC13 - OID Group Link (issuance policy mapped to a privileged group).
    .DESCRIPTION
        Read-only analyzer. A certificate template whose IssuancePolicies
        (msPKI-Certificate-Policy) reference an msPKI-Enterprise-Oid object that
        carries msDS-OIDToGroupLink pointing at a privileged group grants that group
        membership to anyone who enrolls. If such a template allows low-priv
        enrollment and client authentication, it is an ESC13 escalation.

        The OID->group link values come from $Context.OidGroupLinks when a dedicated
        collector populated it (entries with an OID and a linked group). $Context.PkiAcls
        OidContainer entries only carry ACLs, not the link value, so when no
        OidGroupLinks collection exists this analyzer emits ManualReview describing
        exactly what to enumerate.
    .PARAMETER Context
        AssessmentContext pscustomobject.
    .PARAMETER ExtraLowPrivSid
        Additional SID strings to treat as low-privileged.
    .OUTPUTS
        Finding[]
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject] $Context,

        [Parameter(Mandatory = $false)]
        [string[]] $ExtraLowPrivSid = @()
    )

    $reference = 'https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53'
    $findings = @()

    $extra = @()
    if ($ExtraLowPrivSid) { $extra += $ExtraLowPrivSid }
    if ($Context.ExtraLowPrivSid) { $extra += $Context.ExtraLowPrivSid }

    $hasLinks = ($null -ne $Context.OidGroupLinks -and @($Context.OidGroupLinks).Count -gt 0)

    if (-not $hasLinks) {
        return @([pscustomobject]@{
            Id = 'ESC13'
            Title = 'ESC13 - OID-to-group link data not collected (manual)'
            Severity = 'High'
            Status = 'ManualReview'
            AffectedObject = 'msPKI-Enterprise-Oid objects'
            Evidence = [pscustomobject]@{
                Reason = 'No $Context.OidGroupLinks collection present; PkiAcls OidContainer entries carry ACLs only, not msDS-OIDToGroupLink values.'
                ManualCheck = @(
                    'Enumerate CN=OID,CN=Public Key Services,CN=Services,CN=Configuration,<forest> for msPKI-Enterprise-Oid objects with msDS-OIDToGroupLink set.',
                    'Resolve each linked group DN and assess whether it is privileged.',
                    'Match the linked OID against each template IssuancePolicies (msPKI-Certificate-Policy).',
                    'A matching template with low-priv Enroll/AutoEnroll + client-auth EKU is ESC13 Vulnerable.'
                )
                TemplatesWithIssuancePolicies = @($Context.Templates | Where-Object { $_.IssuancePolicies -and @($_.IssuancePolicies).Count -gt 0 } | ForEach-Object { [pscustomobject]@{ Template = $_.Name; IssuancePolicies = @($_.IssuancePolicies) } })
            }
            Principals = @()
            Exploitability = 'Theoretical'
            RiskScore = 0
            Remediation = 'Remove msDS-OIDToGroupLink from OIDs mapped to privileged groups, or restrict enrollment on templates carrying those issuance policies.'
            Reference = $reference
        })
    }

    if ($null -eq $Context.Templates) {
        return @([pscustomobject]@{
            Id = 'ESC13'; Title = 'ESC13 template data unavailable'; Severity = 'High';
            Status = 'ManualReview'; AffectedObject = 'N/A';
            Evidence = [pscustomobject]@{ Reason = 'OidGroupLinks present but Context.Templates null; cannot cross-reference.' };
            Principals = @(); Exploitability = 'Theoretical'; RiskScore = 0;
            Remediation = 'Re-run template collection.'; Reference = $reference
        })
    }

    $linkByOid = @{}
    foreach ($link in $Context.OidGroupLinks) {
        $oid = $link.Oid
        if (-not $oid) { $oid = $link.OidValue }
        if (-not $oid) { continue }
        $linkByOid[[string]$oid] = $link
    }

    foreach ($tmpl in $Context.Templates) {
        $pols = @($tmpl.IssuancePolicies)
        if ($pols.Count -eq 0) { continue }

        $matched = @()
        foreach ($p in $pols) {
            if ($p -and $linkByOid.ContainsKey([string]$p)) { $matched += $linkByOid[[string]$p] }
        }
        if ($matched.Count -eq 0) { continue }

        if (-not (Test-EscEkuAllowsAuth -EkuList $tmpl.EkuList -ApplicationPolicies $tmpl.ApplicationPolicies)) { continue }

        $lowPrivEnrollers = @()
        foreach ($ace in @($tmpl.EnrollPrincipals)) {
            if ($null -eq $ace) { continue }
            if ($ace.AceType -ne 'Allow') { continue }
            $isEnroll = ($ace.Rights -contains 'Enroll') -or ($ace.Rights -contains 'GenericAll')
            if (-not $isEnroll) { continue }
            $isLow = $false
            if ($ace.IsLowPriv) { $isLow = $true } else { $isLow = Test-EscLowPrivPrincipal -Sid $ace.PrincipalSid -ExtraLowPrivSid $extra }
            if ($isLow) { $lowPrivEnrollers += $ace }
        }
        if ($lowPrivEnrollers.Count -eq 0) { continue }

        $principals = @($lowPrivEnrollers | ForEach-Object { if ($_.PrincipalName) { $_.PrincipalName } else { $_.PrincipalSid } } | Select-Object -Unique)
        $linkedGroups = @($matched | ForEach-Object { if ($_.GroupName) { $_.GroupName } elseif ($_.GroupDn) { $_.GroupDn } else { $_.GroupSid } })

        $priv = $false
        foreach ($m in $matched) { if ($m.IsPrivilegedGroup -or $m.Privileged) { $priv = $true } }
        if ($priv) { $exploit = 'High' } else { $exploit = 'Medium' }

        $findings += [pscustomobject]@{
            Id = 'ESC13'
            Title = "Template '$($tmpl.Name)' issuance policy links to group membership (ESC13)"
            Severity = 'High'
            Status = 'Vulnerable'
            AffectedObject = $tmpl.Name
            Evidence = [pscustomobject]@{
                Template = $tmpl.Name
                IssuancePolicies = $pols
                LinkedOids = @($matched | ForEach-Object { if ($_.Oid) { $_.Oid } else { $_.OidValue } })
                LinkedGroups = $linkedGroups
                LowPrivEnrollers = $principals
                Note = 'Enrolling this template grants membership in the linked group for the duration of the certificate; verify the group is privileged.'
            }
            Principals = $principals
            Exploitability = $exploit
            RiskScore = 0
            Remediation = 'Remove msDS-OIDToGroupLink from the OID, or restrict enrollment on this template to trusted principals.'
            Reference = $reference
        }
    }

    if ($findings.Count -eq 0) {
        return @([pscustomobject]@{
            Id = 'ESC13'; Title = 'No ESC13 OID-group-link escalation templates found'; Severity = 'Info';
            Status = 'NotVulnerable'; AffectedObject = 'All templates';
            Evidence = [pscustomobject]@{ TemplatesChecked = @($Context.Templates).Count; OidLinksChecked = @($Context.OidGroupLinks).Count };
            Principals = @(); Exploitability = 'Theoretical'; RiskScore = 0;
            Remediation = 'None required.'; Reference = $reference
        })
    }

    return $findings
}