Analyzers/Test-Esc14.ps1

function Test-Esc14 {
    <#
    .SYNOPSIS
        ESC14 - Weak or writable altSecurityIdentities explicit certificate mappings.
    .DESCRIPTION
        Read-only analyzer. Two abuse variants:
          Variant B (weak value): a privileged account's altSecurityIdentities uses a
            weak mapping type - X509IssuerSubject (<I><S>), X509SubjectOnly (<S>), or
            X509RFC822 (email) - which an attacker can satisfy with a forged/spoofed
            certificate. Only exploitable when a DC allows weak binding
            (StrongCertificateBindingEnforcement 0/1).
          Variant A (writable ACL): a low-priv principal has write access
            (WriteProperty on altSecurityIdentities / GenericWrite / GenericAll /
            WriteDacl) over a privileged account and can add a strong-but-attacker-
            controlled mapping. Exploitable regardless of DC enforcement.

        Collectors do not enumerate account altSecurityIdentities / DACLs broadly, so
        unless $Context.AltSecurityIdentities is present this analyzer emits
        ManualReview with the exact check.
    .PARAMETER Context
        AssessmentContext pscustomobject.
    .PARAMETER ExtraLowPrivSid
        Additional SID strings to treat as low-privileged (used if mapping data present).
    .OUTPUTS
        Finding[]
    #>

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

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

    $reference = 'https://www.gradcatrix.com/ (ADCS ESC14 altSecurityIdentities)'

    $weakDcs = @()
    if ($Context.DcMappings) {
        foreach ($dc in $Context.DcMappings) {
            if ($dc.Reachable -eq $false) { continue }
            if ($null -ne $dc.StrongCertificateBindingEnforcement -and [int]$dc.StrongCertificateBindingEnforcement -lt 2) {
                $weakDcs += $dc.DomainController
            }
        }
    }

    $weakTypesNote = @(
        'X509IssuerSubject (X509:<I>...<S>...) - WEAK',
        'X509SubjectOnly (X509:<S>...) - WEAK',
        'X509RFC822 (X509:<RFC822>email) - WEAK',
        'Strong (safe): X509IssuerSerialNumber (<I><SR>), X509SKI (<SKI>), X509SHA1PublicKey (<SHA1-PUKEY>).'
    )

    $hasData = ($null -ne $Context.AltSecurityIdentities -and @($Context.AltSecurityIdentities).Count -gt 0)

    if (-not $hasData) {
        return @([pscustomobject]@{
            Id = 'ESC14'
            Title = 'ESC14 - altSecurityIdentities mappings not collected (manual)'
            Severity = 'High'
            Status = 'ManualReview'
            AffectedObject = 'Privileged accounts'
            Evidence = [pscustomobject]@{
                Reason = 'No $Context.AltSecurityIdentities collection present; account altSecurityIdentities values and DACLs are not gathered by current collectors.'
                ManualCheck = @(
                    'Enumerate user/computer objects with altSecurityIdentities populated.',
                    'Flag weak mapping types (see WeakMappingTypes).',
                    'Enumerate DACLs on privileged accounts for low-priv WriteProperty (altSecurityIdentities) / GenericWrite / GenericAll / WriteDacl.',
                    'Weak value + a DC with StrongCertificateBindingEnforcement < 2 = exploitable (Variant B); writable ACL = exploitable regardless (Variant A).'
                )
                WeakMappingTypes = $weakTypesNote
                WeakBindingDCs = $weakDcs
                DcContext = if ($weakDcs.Count -gt 0) { "Weak-binding DC(s) present: $($weakDcs -join ', ') - Variant B is live." } else { 'No weak-binding DC observed (or DcMappings absent); Variant A (writable ACL) still applies.' }
            }
            Principals = @()
            Exploitability = 'Theoretical'
            RiskScore = 0
            Remediation = 'Replace weak altSecurityIdentities mappings with strong types (SKI / SHA1PublicKey / IssuerSerialNumber), lock down write access to the attribute, and set StrongCertificateBindingEnforcement=2.'
            Reference = $reference
        })
    }

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

    $weakPrefixes = @('X509:<I>', 'X509:<S>', 'X509:<RFC822>')

    foreach ($acct in $Context.AltSecurityIdentities) {
        $weakMaps = @()
        foreach ($m in @($acct.Mappings)) {
            if (-not $m) { continue }
            $mv = [string]$m
            foreach ($pfx in $weakPrefixes) {
                if ($mv.Replace(' ', '').StartsWith($pfx)) {
                    if ($pfx -eq 'X509:<S>' -and ($mv -match '<SR>' -or $mv -match '<SKI>')) { continue }
                    $weakMaps += $mv
                    break
                }
            }
        }

        $writableAces = @()
        foreach ($ace in @($acct.WriteAces)) {
            if ($null -eq $ace) { continue }
            if ($ace.AceType -ne 'Allow') { continue }
            $canWrite = ($ace.Rights -contains 'WriteProperty') -or ($ace.Rights -contains 'GenericWrite') -or ($ace.Rights -contains 'GenericAll') -or ($ace.Rights -contains 'WriteDacl')
            if (-not $canWrite) { continue }
            $isLow = $false
            if ($ace.IsLowPriv) { $isLow = $true } else { $isLow = Test-EscLowPrivPrincipal -Sid $ace.PrincipalSid -ExtraLowPrivSid $extra }
            if ($isLow) { $writableAces += $ace }
        }

        $variantB = ($weakMaps.Count -gt 0 -and $weakDcs.Count -gt 0)
        $variantA = ($writableAces.Count -gt 0)
        if (-not $variantB -and -not $variantA) { continue }

        $principals = @($writableAces | ForEach-Object { if ($_.PrincipalName) { $_.PrincipalName } else { $_.PrincipalSid } } | Select-Object -Unique)
        if ($variantA) { $exploit = 'High' } else { $exploit = 'Medium' }

        $findings += [pscustomobject]@{
            Id = 'ESC14'
            Title = "Account '$($acct.Name)' has weak/writable altSecurityIdentities (ESC14)"
            Severity = 'High'
            Status = 'Vulnerable'
            AffectedObject = $acct.Name
            Evidence = [pscustomobject]@{
                Account = $acct.Name
                WeakMappings = $weakMaps
                VariantB_WeakValue = $variantB
                VariantA_WritableAcl = $variantA
                WritablePrincipals = $principals
                WeakBindingDCs = $weakDcs
            }
            Principals = $principals
            Exploitability = $exploit
            RiskScore = 0
            Remediation = 'Use strong mapping types, remove low-priv write access to altSecurityIdentities, and enforce StrongCertificateBindingEnforcement=2.'
            Reference = $reference
        }
    }

    if ($findings.Count -eq 0) {
        return @([pscustomobject]@{
            Id = 'ESC14'; Title = 'No ESC14 weak/writable explicit mappings found'; Severity = 'Info';
            Status = 'NotVulnerable'; AffectedObject = 'Accounts';
            Evidence = [pscustomobject]@{ AccountsChecked = @($Context.AltSecurityIdentities).Count };
            Principals = @(); Exploitability = 'Theoretical'; RiskScore = 0;
            Remediation = 'None required.'; Reference = $reference
        })
    }

    return $findings
}