Private/Set/Set-DangerousEnrollee.ps1
|
function Set-DangerousEnrollee { <# .SYNOPSIS Adds a DangerousEnrollee property to AD CS certificate template objects. .DESCRIPTION Examines the access control lists (ACLs) of Active Directory Certificate Services certificate template objects to identify overly permissive enrollment permissions. The function checks for enrollment permissions granted to well-known dangerous principals that represent overly broad groups. These should typically not have enrollment permissions, as they can lead to privilege escalation vulnerabilities. This is a critical check for ESC1 and other AD CS vulnerability detection, as templates with dangerous enrollees combined with other risky settings can allow unauthorized users to obtain certificates for privilege escalation. The function adds two properties to each template object: 1. DangerousEnrollee: Array of SIDs for dangerous principals 2. DangerousEnrolleeNames: Array of human-readable names formatted as "DOMAIN\User (SID)" or "SID (could not resolve)" if the principal cannot be resolved. .PARAMETER AdcsObject One or more DirectoryEntry objects representing AD CS certificate templates. These objects must contain ObjectSecurity.Access information. .PARAMETER Credential PSCredential for authenticating to Active Directory. Required for LDAP queries when converting identity references to directory entries and SIDs. .PARAMETER RootDSE A DirectoryEntry object for the RootDSE. Used to determine the domain context for LDAP queries when resolving principals. .INPUTS System.DirectoryServices.DirectoryEntry[] You can pipe certificate template DirectoryEntry objects to this function. .OUTPUTS System.DirectoryServices.DirectoryEntry[] Returns the input objects with added properties: - DangerousEnrollee: Array of SIDs - DangerousEnrolleeNames: Array of human-readable names .EXAMPLE $templates = Get-AdcsObject | Where-Object { $_.objectClass -contains 'pKICertificateTemplate' } $templates | Set-DangerousEnrollee Processes all certificate templates and adds the DangerousEnrollee property to each. .EXAMPLE Get-AdcsObject | Set-DangerousEnrollee | Where-Object { $_.DangerousEnrollee.Count -gt 0 } Retrieves all AD CS objects, adds DangerousEnrollee property, and filters to only those with dangerous enrollees. .EXAMPLE $template = Get-AdcsObject | Where-Object Name -eq 'WebServer' $template | Set-DangerousEnrollee if ($template.DangerousEnrollee) { Write-Host "Template has dangerous enrollees:" $template.DangerousEnrolleeNames | ForEach-Object { Write-Host " $_" } } Checks a specific template for dangerous enrollees and displays human-readable names. .NOTES Well-known dangerous principals checked by default: - S-1-0-0: NULL SID - S-1-1-0: Everyone (all users possibly including anonymous) - S-1-5-7: Anonymous Logon - S-1-5-32-545: BUILTIN\Users - S-1-5-11: Authenticated Users - SIDs ending in -513: Domain Users groups - SIDs ending in -515: Domain Computers groups Templates with these principals having enrollment permissions are considered high-risk when combined with other misconfigurations like allowing SAN specification. .LINK https://posts.specterops.io/certified-pre-owned-d95910965cd2 #> [CmdletBinding()] [OutputType([System.DirectoryServices.DirectoryEntry[]])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.DirectoryServices.DirectoryEntry[]]$AdcsObject ) #requires -Version 5.1 begin { Write-Verbose "Identifying templates that allow dangerous principals to enroll..." } process { $AdcsObject | Where-Object SchemaClassName -eq pKICertificateTemplate | ForEach-Object { try { $objectName = if ($_.Properties.displayName.Count -gt 0) { $_.Properties.displayName[0] } elseif ($_.Properties.name.Count -gt 0) { $_.Properties.name[0] } else { $_.Properties.distinguishedName[0] } Write-Verbose "Processing template: $objectName" [array]$dangerousIdentityReference = foreach ($ace in $_.ObjectSecurity.Access) { $aceSid = $ace.IdentityReference | Convert-IdentityReferenceToSid $isDangerousEnrollee = $aceSid | Test-IsDangerousPrincipal if ($isDangerousEnrollee) { $isEnrollmentAce = Test-IsEnrollmentAce -Ace $ace if ($isEnrollmentAce) { Write-Verbose "Dangerous enrollee found: $($ace.IdentityReference)" # Ensure the principal is in the store (triggers cache population) $null = $ace.IdentityReference | Resolve-Principal # Convert to SID and return as the key to PrincipalStore $aceSid.Value } } } $dangerousIdentityReference = $dangerousIdentityReference | Sort-Object -Unique if ($dangerousIdentityReference) { Write-Verbose "Template has $($dangerousIdentityReference.Count) dangerous enrollee(s): $($dangerousIdentityReference -join ', ')" } else { Write-Verbose "No dangerous enrollees found in template" } # Build human-readable names array from PrincipalStore [array]$dangerousEnrolleeNames = $dangerousIdentityReference | ForEach-Object { if ($script:PrincipalStore -and $script:PrincipalStore.ContainsKey($_)) { $name = $script:PrincipalStore[$_].ntAccountName if ($name) { "$name ($_)" } else { "$_ (could not resolve)" } } else { "$_ (could not resolve)" } } | Sort-Object -Unique # Update the AD CS Object Store with the DangerousEnrollee property $dn = $_.Properties.distinguishedName[0] if ($script:AdcsObjectStore.ContainsKey($dn)) { $script:AdcsObjectStore[$dn] | Add-Member -NotePropertyName DangerousEnrollee -NotePropertyValue $dangerousIdentityReference -Force $script:AdcsObjectStore[$dn] | Add-Member -NotePropertyName DangerousEnrolleeNames -NotePropertyValue $dangerousEnrolleeNames -Force Write-Verbose "Updated AD CS Object Store for $dn with DangerousEnrollee" } # Also add to the pipeline object for backward compatibility $_ | Add-Member -NotePropertyName DangerousEnrollee -NotePropertyValue $dangerousIdentityReference -Force $_ | Add-Member -NotePropertyName DangerousEnrolleeNames -NotePropertyValue $dangerousEnrolleeNames -Force # Return the modified object $_ } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( $_.Exception, 'DangerousEnrolleeProcessingFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $_ ) $PSCmdlet.WriteError($errorRecord) # Still return the object even if processing failed $_ } } } end { Write-Verbose "Done identifying templates that allow dangerous principals to enroll." } } |