Analyzers/Test-Esc8.ps1
|
function Test-Esc8 { <# .SYNOPSIS ESC8 - NTLM relay to AD CS Web Enrollment / CES-CEP. .DESCRIPTION Flags CA web-enrollment endpoints that accept NTLM authentication without adequate channel binding, allowing an attacker to relay coerced NTLM authentication (e.g. from a DC) to the endpoint and enrol a certificate on the victim's behalf. Vulnerable (per endpoint) = Reachable == $true AND NtlmSupported == $true AND (Scheme == 'http' OR EpaEnabled == $false). NtlmSupported == $true AND EpaEnabled == $null (EPA undetermined over HTTPS) -> ManualReview (per ESC8 doc). EpaEnabled == $true over HTTPS -> NotVulnerable. .PARAMETER Context AssessmentContext pscustomobject. Reads $Context.WebEndpoints. .PARAMETER ExtraLowPrivSid Extra SID strings (unused; kept for signature parity). .OUTPUTS Finding[] #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject] $Context, [Parameter(Mandatory = $false)] [string[]] $ExtraLowPrivSid = @() ) $reference = 'SpecterOps "Certified Pre-Owned" (ESC8) / Certipy / PetitPotam' $findings = @() $newFinding = { param($Title, $Severity, $Status, $Affected, $Evidence, $Principals, $Exploit, $Remediation) [pscustomobject]@{ Id = 'ESC8' Title = $Title Severity = $Severity Status = $Status AffectedObject = $Affected Evidence = $Evidence Principals = @($Principals) Exploitability = $Exploit RiskScore = 0 Remediation = $Remediation Reference = $reference } } $endpoints = @() if ($null -ne $Context -and $null -ne $Context.WebEndpoints) { $endpoints = @($Context.WebEndpoints) } if ($endpoints.Count -eq 0) { return @(& $newFinding 'ESC8 - no web-enrollment endpoints available' 'Critical' 'ManualReview' 'N/A' ` ([pscustomobject]@{ Error = 'Context.WebEndpoints is empty or unavailable; could not evaluate ESC8.' }) ` @() 'Theoretical' 'Enumerate CA web-enrollment endpoints (/certsrv, CES/CEP) and re-run.') } $sawSignal = $false foreach ($ep in $endpoints) { if (-not $ep.Reachable) { continue } if (-not $ep.NtlmSupported) { continue } $sawSignal = $true $httpScheme = ($ep.Scheme -eq 'http') $epaOff = ($ep.EpaEnabled -eq $false) if ($httpScheme -or $epaOff) { if ($httpScheme) { $reason = 'HTTP scheme (no channel binding possible; EPA moot)' } else { $reason = 'EPA disabled (EpaEnabled=$false)' } $evidence = [pscustomobject]@{ CaName = $ep.CaName Url = $ep.Url Scheme = $ep.Scheme NtlmSupported = $true EpaEnabled = $ep.EpaEnabled CesCepPresent = [bool]$ep.CesCepPresent Reason = $reason } $findings += & $newFinding ` ("ESC8 - web-enrollment endpoint '{0}' relays NTLM ({1})" -f $ep.Url, $reason) ` 'Critical' 'Vulnerable' $ep.Url $evidence @() 'High' ` 'Disable NTLM on enrollment endpoints, serve over HTTPS only, and require Extended Protection for Authentication (EPA=Require); enable RPC/LDAP signing and channel binding.' } elseif ($null -eq $ep.EpaEnabled) { $evidence = [pscustomobject]@{ CaName = $ep.CaName Url = $ep.Url Scheme = $ep.Scheme NtlmSupported = $true EpaEnabled = $null CesCepPresent = [bool]$ep.CesCepPresent Note = 'NTLM enabled but EPA state could not be determined over HTTPS; verify Extended Protection on the IIS site.' } $findings += & $newFinding ` ("ESC8 - endpoint '{0}' accepts NTLM; EPA state undetermined" -f $ep.Url) ` 'Critical' 'ManualReview' $ep.Url $evidence @() 'Medium' ` 'Confirm Extended Protection = Required on the IIS Windows Authentication settings for this endpoint.' } } if ($findings.Count -eq 0) { if ($sawSignal) { $detail = 'Reachable NTLM endpoints found, but all enforce HTTPS + EPA.' } else { $detail = 'No reachable NTLM-capable endpoints found.' } return @(& $newFinding 'ESC8 - no relayable web-enrollment endpoints' 'Critical' 'NotVulnerable' 'N/A' ` ([pscustomobject]@{ EndpointsEvaluated = $endpoints.Count; Detail = $detail }) @() 'Theoretical' 'No action required for ESC8.') } return $findings } |