Private/Test/Test-IsDangerousAce.ps1

function Test-IsDangerousAce {
    <#
        .SYNOPSIS
        Tests if an Active Directory access control entry grants dangerous permissions on AD CS objects.
 
        .DESCRIPTION
        Examines Active Directory access control entries (ACEs) on AD CS objects (templates, CAs,
        containers, computer accounts) to determine if they grant dangerous permissions that enable
        privilege escalation attacks.
         
        Dangerous permissions include GenericAll, WriteDacl, WriteOwner, GenericWrite, and specific
        WriteProperty rights depending on object class. The function matches ACEs against a
        comprehensive list of dangerous permission combinations defined in AceDefinitions.psd1,
        filtering by object class to ensure property-specific permissions are only flagged on
        relevant object types.
         
        Only Allow ACEs are considered dangerous; Deny ACEs always return false.
         
        This is critical for ESC1/ESC4/ESC5/ESC7/ESC9/ESC10 vulnerability detection in AD CS auditing.
 
        .PARAMETER Ace
        One or more ActiveDirectoryAccessRule objects to test for dangerous permissions.
        Typically obtained from an object's ObjectSecurity.Access property.
 
        .PARAMETER ObjectClass
        The objectClass or SchemaClassName of the object being audited. Used to filter
        dangerous ACE definitions to only those applicable to this object type.
        Valid values: pKICertificateTemplate, pKIEnrollmentService, certificationAuthority, computer
 
        .INPUTS
        System.DirectoryServices.ActiveDirectoryAccessRule[]
        You can pipe access control entries to this function.
 
        .OUTPUTS
        System.Management.Automation.PSCustomObject
        Returns a custom object for each ACE containing:
        - IsDangerous: Boolean indicating if ACE is dangerous for this object class
        - MatchedPermission: Name of the matched dangerous permission (if any)
        - Description: What the permission allows
        - Ace: The original ACE object
 
        .EXAMPLE
        $template = Get-AdcsObject | Where-Object Name -eq 'WebServer'
        $template.ObjectSecurity.Access | Test-IsDangerousAce -ObjectClass 'pKICertificateTemplate'
        Tests all ACEs on the WebServer template for dangerous permissions.
 
        .EXAMPLE
        $ca = Get-AdcsObject | Where-Object { $_.objectClass -contains 'pKIEnrollmentService' } | Select-Object -First 1
        $ca.ObjectSecurity.Access | Test-IsDangerousAce -ObjectClass 'pKIEnrollmentService'
        Tests ACEs on a CA object for dangerous permissions (ESC7).
 
        .EXAMPLE
        $computer = Get-ADComputer 'CA-SERVER$'
        $computer.nTSecurityDescriptor.Access | Test-IsDangerousAce -ObjectClass 'computer'
        Tests ACEs on a CA host computer account for dangerous permissions (ESC9/ESC10).
 
        .EXAMPLE
        $dangerousAces = $template.ObjectSecurity.Access |
            Test-IsDangerousAce -ObjectClass 'pKICertificateTemplate' |
            Where-Object IsDangerous
        Get only the ACEs that grant dangerous permissions.
 
        .EXAMPLE
        $template.ObjectSecurity.Access |
            Test-IsDangerousAce -ObjectClass 'pKICertificateTemplate' |
            Where-Object IsDangerous |
            Select-Object @{N='Identity';E={$_.Ace.IdentityReference}}, MatchedPermission, Description
        Display dangerous ACEs with formatted output.
 
        .NOTES
        Automatically loads dangerous ACE definitions from DangerousAces.psd1.
        Definitions are cached in $script:DangerousAces for subsequent calls.
         
        ObjectClass parameter is mandatory to ensure property-specific permissions are
        only flagged on applicable object types (e.g., WriteProperty on msPKI-Certificate-Name-Flag
        is only dangerous on templates, not on CAs or computers).
 
        .LINK
        https://specterops.io/blog/2021/06/17/certified-pre-owned/
         
        .LINK
        https://posts.specterops.io/certified-pre-owned-d95910965cd2
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.DirectoryServices.ActiveDirectoryAccessRule[]]$Ace,
        
        [Parameter(Mandatory)]
        [string]$ObjectClass
    )

    #requires -Version 5.1

    begin {
        # Load dangerous ACE definitions from PSD1 data file
        if (-not $script:DangerousAces) {
            $dataFilePath = Join-Path $PSScriptRoot '..\Data\AceDefinitions.psd1'
            
            if (Test-Path $dataFilePath) {
                $data = Import-PowerShellDataFile -Path $dataFilePath
                $script:DangerousAces = $data.DangerousAces
                Write-Verbose "Loaded $($script:DangerousAces.Count) dangerous ACE definitions from $dataFilePath"
            } else {
                Write-Warning "AceDefinitions.psd1 not found at $dataFilePath. Unable to test for dangerous permissions."
                return
            }
        } else {
            Write-Verbose "Using cached $($script:DangerousAces.Count) dangerous ACE definitions"
        }
        
        # Filter to ACEs applicable to this object class
        $applicableAces = $script:DangerousAces | Where-Object { $_.ApplicableToClasses -contains $ObjectClass }
        Write-Verbose "Filtered to $($applicableAces.Count) ACE definitions applicable to objectClass '$ObjectClass'"
    }

    process {
        $Ace | ForEach-Object {
            $currentAce = $_
            $identityRef = $currentAce.IdentityReference
            
            Write-Verbose "Testing ACE for $identityRef - Rights: $($currentAce.ActiveDirectoryRights), Type: $($currentAce.AccessControlType)"
            
            # Only Allow ACEs are dangerous (Deny ACEs are protective)
            if ($currentAce.AccessControlType -ne 'Allow') {
                Write-Verbose " Deny ACE - not dangerous (protective)"
                [PSCustomObject]@{
                    IsDangerous = $false
                    MatchedPermission = $null
                    Description = $null
                    Ace = $currentAce
                }
                return
            }
            
            # Check against all applicable dangerous ACE definitions
            $matchedPermission = $null
            
            foreach ($dangerousAce in $applicableAces) {
                # Check if ActiveDirectoryRights matches
                $rightsMatch = $currentAce.ActiveDirectoryRights -match $dangerousAce.Rights
                
                if (-not $rightsMatch) {
                    continue
                }
                
                # For WriteProperty, also check ObjectType GUID
                if ($dangerousAce.Rights -eq 'WriteProperty' -and $dangerousAce.ObjectType) {
                    $objectTypeMatch = $currentAce.ObjectType.ToString() -eq $dangerousAce.ObjectType
                    
                    if (-not $objectTypeMatch) {
                        Write-Verbose " Rights match ($($dangerousAce.Rights)) but ObjectType mismatch: ACE=$($currentAce.ObjectType), Expected=$($dangerousAce.ObjectType)"
                        continue
                    }
                }
                
                # Match found
                $matchedPermission = $dangerousAce
                Write-Verbose " DANGEROUS: Matched '$($dangerousAce.Name)'"
                break
            }
            
            # Return result
            if ($matchedPermission) {
                [PSCustomObject]@{
                    IsDangerous = $true
                    MatchedPermission = $matchedPermission.Name
                    Description = $matchedPermission.Description
                    Ace = $currentAce
                }
            } else {
                Write-Verbose " Not dangerous - no matching dangerous permission found for objectClass '$ObjectClass'"
                [PSCustomObject]@{
                    IsDangerous = $false
                    MatchedPermission = $null
                    Description = $null
                    Ace = $currentAce
                }
            }
        }
    }

    end {
        Write-Verbose "Finished testing ACEs for dangerous permissions on objectClass '$ObjectClass'"
    }
}