Analyzers/Test-Esc1.ps1
|
function Test-Esc1 { <# .SYNOPSIS ESC1 - Enrollee-supplied Subject Alternative Name on an authentication template. .DESCRIPTION Flags certificate templates that let a low-privileged enrollee both supply an arbitrary subject (SAN) AND obtain a certificate usable for authentication, without manager approval or enrollment-agent signatures. Such a template lets any low-priv principal request a certificate for an arbitrary user (e.g. a Domain Admin UPN) and authenticate as them. Vulnerable (per template) = EnrolleeSuppliesSubject == $true AND EKU allows authentication (client-auth / smartcard / PKINIT / Any-Purpose, or empty EKU list) AND ManagerApprovalRequired == $false AND RaSignaturesRequired -lt 1 AND a low-priv principal holds Enroll/AutoEnroll AND the template is published on at least one CA. .PARAMETER Context AssessmentContext pscustomobject. Reads $Context.Templates. .PARAMETER ExtraLowPrivSid Extra SID strings to treat as low-privileged. .OUTPUTS Finding[] #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject] $Context, [Parameter(Mandatory = $false)] [string[]] $ExtraLowPrivSid = @() ) $reference = 'SpecterOps "Certified Pre-Owned" (ESC1) / Certipy' $findings = @() $newFinding = { param($Title, $Severity, $Status, $Affected, $Evidence, $Principals, $Exploit, $Remediation) [pscustomobject]@{ Id = 'ESC1' Title = $Title Severity = $Severity Status = $Status AffectedObject = $Affected Evidence = $Evidence Principals = @($Principals) Exploitability = $Exploit RiskScore = 0 Remediation = $Remediation Reference = $reference } } $templates = @() if ($null -ne $Context -and $null -ne $Context.Templates) { $templates = @($Context.Templates) } if ($templates.Count -eq 0) { return @(& $newFinding 'ESC1 - no certificate templates available' 'Critical' 'ManualReview' 'N/A' ` ([pscustomobject]@{ Error = 'Context.Templates is empty or unavailable; could not evaluate ESC1.' }) ` @() 'Theoretical' 'Collect certificate templates and re-run the assessment.') } $extra = @() if ($ExtraLowPrivSid) { $extra += $ExtraLowPrivSid } if ($null -ne $Context.ExtraLowPrivSid) { $extra += $Context.ExtraLowPrivSid } foreach ($tpl in $templates) { if (-not $tpl.EnrolleeSuppliesSubject) { continue } $combinedEku = @() if ($tpl.EkuList) { $combinedEku += $tpl.EkuList } if ($tpl.ApplicationPolicies) { $combinedEku += $tpl.ApplicationPolicies } if (-not (Test-EscEkuAllowsAuth -EkuList $tpl.EkuList -ApplicationPolicies $tpl.ApplicationPolicies)) { continue } if ($tpl.ManagerApprovalRequired) { continue } if ([int]$tpl.RaSignaturesRequired -ge 1) { continue } $lowPrivEnrollers = @($tpl.EnrollPrincipals | Where-Object { $_.AceType -eq 'Allow' -and $_.IsLowPriv -and ( ($_.Rights -contains 'Enroll') -or ($_.Rights -contains 'GenericAll') ) }) if ($lowPrivEnrollers.Count -eq 0) { continue } $published = @($tpl.PublishedOnCAs) if ($published.Count -eq 0) { continue } $principalNames = @($lowPrivEnrollers | ForEach-Object { if ($_.PrincipalName) { $_.PrincipalName } else { $_.PrincipalSid } } | Select-Object -Unique) $authEkus = @($combinedEku | Where-Object { @('1.3.6.1.5.5.7.3.2','1.3.6.1.4.1.311.20.2.2','1.3.6.1.5.2.3.4','2.5.29.37.0') -contains $_ } | Select-Object -Unique) $evidence = [pscustomobject]@{ EnrolleeSuppliesSubject = $true NameFlag = ('0x{0:X}' -f [int]$tpl.NameFlag) EkuList = @($tpl.EkuList) ApplicationPolicies = @($tpl.ApplicationPolicies) AuthEkusMatched = if ($authEkus.Count -gt 0) { $authEkus } else { @('<empty EKU list - any purpose>') } ManagerApprovalRequired = $false RaSignaturesRequired = [int]$tpl.RaSignaturesRequired PublishedOnCAs = $published LowPrivEnrollRights = @($lowPrivEnrollers | ForEach-Object { '{0}:{1}' -f $_.PrincipalName, ($_.Rights -join '|') }) } $findings += & $newFinding ` ("ESC1 - template '{0}' allows enrollee-supplied SAN on an auth certificate" -f $tpl.Name) ` 'Critical' 'Vulnerable' $tpl.Name $evidence $principalNames 'High' ` 'Disable CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT, require manager approval or enrollment-agent signatures, and restrict enrollment to trusted principals.' } if ($findings.Count -eq 0) { return @(& $newFinding 'ESC1 - no enrollee-supplied-SAN authentication templates found' 'Critical' 'NotVulnerable' 'N/A' ` ([pscustomobject]@{ TemplatesEvaluated = $templates.Count }) @() 'Theoretical' ` 'No action required for ESC1.') } return $findings } |