Analyzers/Test-Esc7.ps1
|
function Test-Esc7 { <# .SYNOPSIS ESC7 - Low-privileged principal holds CA management rights (ManageCA / ManageCertificates). .DESCRIPTION Flags CAs whose security descriptor grants a low-privileged principal ManageCA (CA Administrator) or ManageCertificates (CA Officer). A CA Administrator can flip dangerous policy flags (e.g. enable EDITF_ATTRIBUTESUBJECTALTNAME2 -> ESC6) and manage the CA; a CA Officer can approve pending requests. Either enables privilege escalation. Vulnerable (per CA) = any SecurityAce with AceType == Allow AND IsLowPriv == $true AND Rights contains ManageCA or ManageCertificates. SecurityAces null / CA unreachable -> ManualReview. .PARAMETER Context AssessmentContext pscustomobject. Reads $Context.CaConfigs. .PARAMETER ExtraLowPrivSid Extra SID strings to treat as low-privileged (parity; ACE IsLowPriv is precomputed). .OUTPUTS Finding[] #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject] $Context, [Parameter(Mandatory = $false)] [string[]] $ExtraLowPrivSid = @() ) $reference = 'SpecterOps "Certified Pre-Owned" (ESC7) / Certipy' $findings = @() $newFinding = { param($Title, $Severity, $Status, $Affected, $Evidence, $Principals, $Exploit, $Remediation) [pscustomobject]@{ Id = 'ESC7' Title = $Title Severity = $Severity Status = $Status AffectedObject = $Affected Evidence = $Evidence Principals = @($Principals) Exploitability = $Exploit RiskScore = 0 Remediation = $Remediation Reference = $reference } } $caConfigs = @() if ($null -ne $Context -and $null -ne $Context.CaConfigs) { $caConfigs = @($Context.CaConfigs) } if ($caConfigs.Count -eq 0) { return @(& $newFinding 'ESC7 - no CA configuration available' 'Critical' 'ManualReview' 'N/A' ` ([pscustomobject]@{ Error = 'Context.CaConfigs is empty or unavailable; could not evaluate ESC7.' }) ` @() 'Theoretical' 'Collect CA security (certutil -getreg CA\Security) and re-run.') } foreach ($ca in $caConfigs) { if (-not $ca.Reachable -or $null -eq $ca.SecurityAces) { $findings += & $newFinding ` ("ESC7 - CA '{0}' security descriptor unavailable" -f $ca.Name) ` 'Critical' 'ManualReview' $ca.Name ` ([pscustomobject]@{ Error = 'SecurityAces null or CA unreachable; ManageCA/ManageCertificates grants could not be evaluated.'; Reachable = [bool]$ca.Reachable; DnsHostName = $ca.DnsHostName }) ` @() 'Theoretical' 'Verify CA security (certutil -config "<host>\<CA>" -getreg CA\Security) manually.' continue } $mgmtAces = @($ca.SecurityAces | Where-Object { $_.AceType -eq 'Allow' -and $_.IsLowPriv -and ( ($_.Rights -contains 'ManageCA') -or ($_.Rights -contains 'ManageCertificates') ) }) if ($mgmtAces.Count -eq 0) { continue } $hasManageCa = (@($mgmtAces | Where-Object { $_.Rights -contains 'ManageCA' }).Count -gt 0) if ($hasManageCa) { $exploit = 'High' } else { $exploit = 'Medium' } $principalNames = @($mgmtAces | ForEach-Object { if ($_.PrincipalName) { $_.PrincipalName } else { $_.PrincipalSid } } | Select-Object -Unique) $evidence = [pscustomobject]@{ DnsHostName = $ca.DnsHostName ManagementAces = @($mgmtAces | ForEach-Object { [pscustomobject]@{ Principal = $_.PrincipalName Sid = $_.PrincipalSid Rights = @($_.Rights | Where-Object { @('ManageCA','ManageCertificates') -contains $_ }) AccessMask = ('0x{0:X}' -f [int]$_.AccessMask) } }) Pivot = [pscustomobject]@{ EditFlagsAttributeSanSet = [bool]$ca.EditFlagsAttributeSanSet DisableExtensionList = @($ca.DisableExtensionList) } } $findings += & $newFinding ` ("ESC7 - CA '{0}' grants CA management rights to a low-priv principal" -f $ca.Name) ` 'Critical' 'Vulnerable' $ca.Name $evidence $principalNames $exploit ` 'Remove ManageCA/ManageCertificates from low-privileged principals; grant CA management only to trusted administrators.' } if ($findings.Count -eq 0) { return @(& $newFinding 'ESC7 - no CA grants management rights to low-priv principals' 'Critical' 'NotVulnerable' 'N/A' ` ([pscustomobject]@{ CasEvaluated = $caConfigs.Count }) @() 'Theoretical' 'No action required for ESC7.') } return $findings } |