Private/Set-RiskRating.ps1

function Set-RiskRating {
    <#
        .SYNOPSIS
        This function takes an Issue object as input and assigns a numerical risk score depending on issue conditions.
 
        .DESCRIPTION
        Risk of Issue is based on:
        - Issue type: Templates issues are more risky than CA/Object issues by default.
        - Template status: Enabled templates are more risky than disabled templates.
        - Principals: Single users are less risky than groups, and custom groups are less risky than default groups.
        - Principal type: AD Admins aren't risky. gMSAs have little risk (assuming proper controls). Non-admins are most risky
        - Modifiers: Some issues are present a higher risk when certain conditions are met.
 
        .PARAMETER Issue
        A PSCustomObject that includes all pertinent information about an AD CS issue.
 
        .INPUTS
        PSCustomObject
 
        .OUTPUTS
        None. This function sets a new attribute on each Issue object and returns nothing to the pipeline.
 
        .EXAMPLE
        $Targets = Get-Target
        $ADCSObjects = Get-ADCSObject -Targets $Targets
        $DangerousRights = @('GenericAll', 'WriteProperty', 'WriteOwner', 'WriteDacl')
        $SafeOwners = '-519$'
        $SafeUsers = '-512$|-519$|-544$|-18$|-517$|-500$|-516$|-521$|-498$|-9$|-526$|-527$|S-1-5-10'
        $SafeObjectTypes = '0e10c968-78fb-11d2-90d4-00c04f79dc55|a05b8cc2-17bc-4802-a710-e7c15ab866a2'
        $ESC4Issues = Find-ESC4 -ADCSObjects $ADCSObjects -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeUsers $SafeUsers -SafeObjectTypes $SafeObjectTypes -Mode 1
        foreach ($issue in $ESC4Issues) {
            if ($SkipRisk -eq $false) {
                Set-RiskRating -ADCSObjects $ADCSObjects -Issue $Issue -SafeUsers $SafeUsers -UnsafeUsers $UnsafeUsers
            }
        }
 
        .LINK
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [object]$Issue,
        [Parameter(Mandatory)]
        [Microsoft.ActiveDirectory.Management.ADEntity[]]$ADCSObjects,
        [Parameter(Mandatory)]
        [string]$SafeUsers,
        [Parameter(Mandatory)]
        [string]$UnsafeUsers
    )

    #requires -Version 5

    $RiskValue = 0
    $RiskName = ''
    $RiskScoring = @()

    # CA issues don't rely on a principal and have a base risk of Medium.
    if ($Issue.Technique -in @('DETECT', 'ESC6', 'ESC7', 'ESC8', 'ESC11','ESC17')) {
        $RiskValue += 3
        $RiskScoring += 'Base Score: 3'

        if ($Issue.CAEnrollmentEndpoint -like 'http:*') {
            $RiskValue += 2
            $RiskScoring += 'HTTP Enrollment: +2'
        }

        if ($Issue.Technique -eq 'ESC7') {
            # If an Issue can be tied to a principal, the principal's objectClass impacts the Issue's risk
            $SID = $Issue.IdentityReferenceSID.ToString()
            $IdentityReferenceObjectClass = Get-ADObject -Filter { objectSid -eq $SID } | Select-Object objectClass

            if ($Issue.IdentityReferenceSID -match $UnsafeUsers) {
                # Authenticated Users, Domain Users, Domain Computers etc. are very risky
                $RiskValue += 2
                $RiskScoring += 'Very Large Group: +2'
            } elseif ($IdentityReferenceObjectClass -eq 'group') {
                # Groups are riskier than individual principals
                $RiskValue += 1
                $RiskScoring += 'Group: +1'
            } elseif ($Issue.IdentityReferenceSID -notmatch $UnsafeUsers -and
                    $Issue.IdentityReferenceSID -notmatch $SafeUsers -and
                    $IdentityReferenceObjectClass -notlike '*ManagedServiceAccount') {
                $RiskValue += 1
                $RiskScoring += 'Unprivileged Principal: +1'
            }
        }

        # Modifiers that rely on the existence of other ESCs
        if ($Issue.Technique -eq 'ESC6') {
            [array]$ESC9 = Find-ESC9 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEkus -UnsafeUsers $UnsafeUsers
            [array]$ESC16 = Find-ESC16 -ADCSObjects $ADCSObjects -UnsafeUsers $UnsafeUsers
            $ESC9and16 = $ESC9 + $ESC16
            if ($ESC9and16) {
                $RiskValue += 2
                $RiskScoring += "Additional risky configurations exist which make this issue more severe: +2"
                foreach ($otherIssue in $ESC9and16) {
                    $RiskScoring += " - $($otherIssue.Technique) exists on $($otherIssue.Name)"
                }
            } # end if ($ESC9and16)
        }

        # TODO Check NtAuthCertificates for CA thumbprint. If found, +2, else -1
        # TODO Check if NTLMv1 is allowed.
    }

    # Template and object issues rely on a principal and have complex scoring.
    if ($Issue.Technique -notin @('DETECT', 'ESC6', 'ESC7', 'ESC8', 'ESC11','ESC17')) {
        $RiskScoring += 'Base Score: 0'

        # Templates are more dangerous when enabled, but objects cannot be enabled/disabled.
        if ($Issue.Technique -ne 'ESC5') {
            if ($Issue.Enabled) {
                $RiskValue += 1
                $RiskScoring += 'Enabled: +1'
            } else {
                $RiskValue -= 2
                $RiskScoring += 'Disabled: -2'
            }
        }

        # ESC1 and ESC4 templates are more dangerous than other templates because they can result in immediate compromise.
        if ($Issue.Technique -in @('ESC1', 'ESC4')) {
            $RiskValue += 1
            $RiskScoring += "$($Issue.Technique) +1"
        }

        # If an Issue can be tied to a principal, the principal's objectClass impacts the Issue's risk
        $SID = $Issue.IdentityReferenceSID.ToString()
        $IdentityReferenceObjectClass = Get-ADObject -Filter { objectSid -eq $SID } | Select-Object objectClass


        if ($Issue.IdentityReferenceSID -match $UnsafeUsers) {
            # Authenticated Users, Domain Users, Domain Computers etc. are very risky
            $RiskValue += 2
            $RiskScoring += 'Very Large Group: +2'
        } elseif ($IdentityReferenceObjectClass -eq 'group') {
            # Groups are riskier than individual principals
            $RiskValue += 1
            $RiskScoring += 'Group: +1'
        } elseif ($Issue.IdentityReferenceSID -notmatch $UnsafeUsers -and
                $Issue.IdentityReferenceSID -notmatch $SafeUsers -and
                $IdentityReferenceObjectClass -notlike '*ManagedServiceAccount') {
            $RiskValue += 1
            $RiskScoring += 'Unprivileged Principal: +1'
        }

        # Safe users and managed service accounts are inherently safer than other principals - except in ESC3 Condition 2 and ESC9!
        if (($Issue.Technique -eq 'ESC9') -or ($Issue.Technique -eq 'ESC3' -and $Issue.Condition -eq 2)) {
            if ($Issue.IdentityReferenceSID -match $SafeUsers) {
                # Safe Users are admins. Authenticating as an admin is bad.
                $RiskValue += 2
                $RiskScoring += 'Privileged Principal: +2'
            } elseif ($IdentityReferenceObjectClass -like '*ManagedServiceAccount') {
                # Managed Service Accounts are *probably* privileged in some way.
                $RiskValue += 1
                $RiskScoring += 'Managed Service Account: +1'
            }
        } elseif ($Issue.IdentityReferenceSID -notmatch $SafeUsers -and $IdentityReferenceObjectClass -notlike '*ManagedServiceAccount') {
            $RiskValue += 1
            $RiskScoring += 'Unprivileged Principal: +1'
        }

        # Modifiers that rely on the existence of other ESCs
        # ESC2 and ESC3C1 are more dangerous if ES3C2 templates exist or certain ESC15 templates are enabled
        if ($Issue.Technique -eq 'ESC2' -or ($Issue.Technique -eq 'ESC3' -and $Issue.Condition -eq 1)) {
            $ESC3C2 = Find-ESC3C2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -UnsafeUsers $UnsafeUsers  -SkipRisk |
            Where-Object { $_.Enabled -eq $true }
            $ESC3C2Names = @(($ESC3C2 | Select-Object -Property Name -Unique).Name)
            if ($ESC3C2Names) {
                $CheckedESC3C2Templates = @{}
                foreach ($name in $ESC3C2Names) {
                    $OtherTemplateRisk = 0
                    $Principals = @()
                    foreach ($esc in $($ESC3C2 | Where-Object Name -EQ $name) ) {
                        if ($CheckedESC3C2Templates.GetEnumerator().Name -contains $esc.Name) {
                            $Principals = $CheckedESC3C2Templates.$($esc.Name)
                        } else {
                            $CheckedESC3C2Templates = @{
                                $($esc.Name) = @()
                            }
                        }
                        $escSID = $esc.IdentityReferenceSID.ToString()
                        $escIdentityReferenceObjectClass = Get-ADObject -Filter { objectSid -eq $escSID } | Select-Object objectClass
                        if ($escSID -match $SafeUsers) {
                            # Safe Users are admins. Authenticating as an admin is bad.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 2
                        } elseif ($escSID -match $UnsafeUsers) {
                            # Unsafe Users are large groups that contain practically all principals and likely including admins.
                            # Authenticating as an admin is bad.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 2
                        } elseif ($escIdentityReferenceObjectClass -like '*ManagedServiceAccount') {
                            # Managed Service Accounts are *probably* privileged in some way.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 1
                        } elseif ($escIdentityReferenceObjectClass -eq 'group') {
                            # Groups are more dangerous than individual principals.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 1
                        }
                        $CheckedESC3C2Templates.$($esc.Name) = $Principals
                    }
                    $RiskScoring += "Principals ($($CheckedESC3C2Templates.$($esc.Name) -join ', ')) are able to enroll in an enabled ESC3 Condition 2 template ($name): +$OtherTemplateRisk"
                } # end foreach ($name)
                if ($OtherTemplateRisk -ge 2) {
                    $OtherTemplateRisk = 2
                }
            } # end if ($ESC3C2Names)

            # Default 'User' and 'Machine' templates are more dangerous
            $ESC15 = Find-ESC15 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -UnsafeUsers $UnsafeUsers  -SkipRisk |
            Where-Object { $_.Enabled -eq $true }
            $ESC15Names = @(($ESC15 | Where-Object Name -In @('Machine', 'User')).Name)
            if ($ESC15Names) {
                $CheckedESC15Templates = @{}
                foreach ($name in $ESC15Names) {
                    $OtherTemplateRisk = 0
                    $Principals = @()
                    foreach ($esc in $($ESC15 | Where-Object Name -EQ $name) ) {
                        if ($CheckedESC15Templates.GetEnumerator().Name -contains $esc.Name) {
                            $Principals = $CheckedESC15Templates.$($esc.Name)
                        } else {
                            $Principals = @()
                            $CheckedESC15Templates = @{
                                $($esc.Name) = @()
                            }
                        }
                        $escSID = $esc.IdentityReferenceSID.ToString()
                        $escIdentityReferenceObjectClass = Get-ADObject -Filter { objectSid -eq $escSID } | Select-Object objectClass
                        if ($escSID -match $SafeUsers) {
                            # Safe Users are admins. Authenticating as an admin is bad.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 2
                        } elseif ($escSID -match $UnsafeUsers) {
                            # Unsafe Users are large groups that contain practically all principals and likely including admins.
                            # Authenticating as an admin is bad.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 2
                        } elseif ($escIdentityReferenceObjectClass -like '*ManagedServiceAccount') {
                            # Managed Service Accounts are *probably* privileged in some way.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 1
                        } elseif ($escIdentityReferenceObjectClass -eq 'group') {
                            # Groups are more dangerous than individual principals.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 1
                        }
                        $CheckedESC15Templates.$($esc.Name) = $Principals
                    }
                    $RiskScoring += "Principals ($($CheckedESC15Templates.$($esc.Name) -join ', ')) are able to enroll in an enabled ESC15/EKUwu template ($name)): +$OtherTemplateRisk"
                } # end foreach ($name)
                if ($OtherTemplateRisk -ge 2) {
                    $OtherTemplateRisk = 2
                }
            } # end if ($ESC15Names)
            $RiskValue += $OtherTemplateRisk
        }

        # ESC3 Condition 2 and ESC15 User/Machine templates are only dangerous if ESC2 or ESC3 Condition 1 templates exist.
        if ( ($Issue.Technique -match 'ESC15' -and $Issue.Name -match 'User|Machine') -or
            ($Issue.Technique -eq 'ESC3' -and $Issue.Condition -eq 2)
        ) {
            $ESC2 = Find-ESC2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -UnsafeUsers $UnsafeUsers  -SkipRisk |
            Where-Object { $_.Enabled -eq $true }
            $ESC2Names = @(($ESC2 | Select-Object -Property Name -Unique).Name)
            if ($ESC2Names) {
                $CheckedESC2Templates = @{}
                foreach ($name in $ESC2Names) {
                    $OtherTemplateRisk = 0
                    $Principals = @()
                    foreach ($esc in $($ESC2 | Where-Object Name -EQ $name) ) {
                        if ($CheckedESC2Templates.GetEnumerator().Name -contains $esc.Name) {
                            $Principals = $CheckedESC2Templates.$($esc.Name)
                        } else {
                            $CheckedESC2Templates = @{
                                $($esc.Name) = @()
                            }
                        }
                        $escSID = $esc.IdentityReferenceSID.ToString()
                        $escIdentityReferenceObjectClass = Get-ADObject -Filter { objectSid -eq $escSID } | Select-Object objectClass
                        if ($escSID -match $UnsafeUsers) {
                            # Unsafe Users are large groups.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 2
                        } elseif ($escIdentityReferenceObjectClass -eq 'group') {
                            # Groups are more dangerous than individual principals.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 1
                        }
                        $CheckedESC2Templates.$($esc.Name) = $Principals
                    }
                    $RiskScoring += "Principals ($($CheckedESC2Templates.$($esc.Name) -join ', ')) are able to enroll in an enabled ESC2 template ($name): +$OtherTemplateRisk"
                } # end foreach ($name)
                if ($OtherTemplateRisk -ge 2) {
                    $OtherTemplateRisk = 2
                }
            } # end if ($ESC2Names)

            $ESC3C1 = Find-ESC3C1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -UnsafeUsers $UnsafeUsers  -SkipRisk |
            Where-Object { $_.Enabled -eq $true }
            $ESC3C1Names = @(($ESC3C1 | Select-Object -Property Name -Unique).Name)
            if ($ESC3C1Names) {
                $CheckedESC3C1Templates = @{}
                foreach ($name in $ESC3C1Names) {
                    $OtherTemplateRisk = 0
                    $Principals = @()
                    foreach ($esc in $($ESC3C1 | Where-Object Name -EQ $name) ) {
                        if ($CheckedESC3C1Templates.GetEnumerator().Name -contains $esc.Name) {
                            $Principals = $CheckedESC3C1Templates.$($esc.Name)
                        } else {
                            $CheckedESC3C1Templates = @{
                                $($esc.Name) = @()
                            }
                        }
                        $escSID = $esc.IdentityReferenceSID.ToString()
                        $escIdentityReferenceObjectClass = Get-ADObject -Filter { objectSid -eq $escSID } | Select-Object objectClass
                        if ($escSID -match $UnsafeUsers) {
                            # Unsafe Users are large groups.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 2
                        } elseif ($escIdentityReferenceObjectClass -eq 'group') {
                            # Groups are more dangerous than individual principals.
                            $Principals += $esc.IdentityReference.Value
                            $OtherTemplateRisk += 1
                        }
                        $CheckedESC3C1Templates.$($esc.Name) = $Principals
                    }
                    $RiskScoring += "Principals ($($CheckedESC3C1Templates.$($esc.Name) -join ', ')) are able to enroll in an enabled ESC3C1 template ($name): +$OtherTemplateRisk"
                } # end foreach ($name...
                if ($OtherTemplateRisk -ge 2) {
                    $OtherTemplateRisk = 2
                }
            } # end if ($ESC3C1Names)
            $RiskValue += $OtherTemplateRisk
        }

        # Disabled ESC1, ESC2, ESC3, ESC4, ESC9, and ESC15 templates are more dangerous if there's an ESC5 on one or more CA objects
        if ($Issue.Technique -match 'ESC1|ESC2|ESC3|ESC4|ESC9' -and $Issue.Enabled -eq $false ) {
            $ESC5 = Find-ESC5 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -UnsafeUsers $UnsafeUsers -DangerousRights $DangerousRights -SafeOwners '-519$' -SafeObjectTypes $SafeObjectTypes -SkipRisk |
            Where-Object { $_.objectClass -eq 'pKIEnrollmentService' }
            $ESC5Names = @(($ESC5 | Select-Object -Property Name -Unique).Name)
            if ($ESC5Names) {
                $CheckedESC5Templates = @{}
                foreach ($name in $ESC5Names) {
                    $OtherIssueRisk = 0
                    $Principals = @()
                    foreach ($OtherIssue in $($ESC5 | Where-Object Name -EQ $name) ) {
                        if ($CheckedESC5Templates.GetEnumerator().Name -contains $OtherIssue.Name) {
                            $Principals = $CheckedESC5Templates.$($OtherIssue.Name)
                        } else {
                            $CheckedESC5Templates = @{
                                $($OtherIssue.Name) = @()
                            }
                        }
                        $OtherIssueSID = $OtherIssue.IdentityReferenceSID.ToString()
                        $OtherIssueIdentityReferenceObjectClass = (Get-ADObject -Filter { objectSid -eq $OtherIssueSID } | Select-Object objectClass).objectClass
                        if ($OtherIssueSID -match $UnsafeUsers) {
                            # Unsafe Users are large groups.
                            $Principals += $OtherIssue.IdentityReference.Value
                            $OtherIssueRisk += 2
                        } elseif ($OtherIssueIdentityReferenceObjectClass -eq 'group') {
                            # Groups are more dangerous than individual principals.
                            $Principals += $OtherIssue.IdentityReference.Value
                            $OtherIssueRisk += 1
                        }
                        else {
                            $Principals += $OtherIssue.IdentityReference.Value
                            $OtherIssueRisk += 0.1
                        }
                        $CheckedESC5Templates.$($OtherIssue.Name) = $Principals
                    } # forech ($OtherIssue)
                    if ($OtherIssueRisk -ge 2) {
                        $OtherIssueRisk = 2
                    }
                    $RiskScoring += "Principals ($($CheckedESC5Templates.$($OtherIssue.Name) -join ', ')) are able to modify CA Host object ($name): +$OtherIssueRisk"
                } # end foreach ($name...
            } # end if ($ESC5Names)
            $RiskValue += $OtherIssueRisk
        }

        # ESC5 objectClass determines risk
        if ($Issue.Technique -eq 'ESC5') {
            if ($Issue.objectClass -eq 'certificationAuthority' -and $Issue.distinguishedName -like 'CN=NtAuthCertificates*') {
                # Being able to modify NtAuthCertificates is very bad.
                $RiskValue += 2
                $RiskScoring += 'NtAuthCertificates: +2'
            }
            switch ($Issue.objectClass) {
                # Being able to modify Root CA Objects is very bad.
                'certificationAuthority' { $RiskValue += 2; $RiskScoring += 'Root Certification Authority bject: +2' }
                # Being able to modify Issuing CA Objects is also very bad.
                'pKIEnrollmentService' { $RiskValue += 2; $RiskScoring += 'Issuing Certification Authority Object: +2' }
                # Being able to modify CA Hosts? Yeah... very bad.
                'computer' { $RiskValue += 2; $RiskScoring += 'Certification Authority Host Computer: +2' }
                # Being able to modify OIDs could result in ESC13 vulns.
                'msPKI-Enterprise-Oid' { $RiskValue += 1; $RiskScoring += 'OID: +1' }
                # Being able to modify PKS containers is bad.
                'container' { $RiskValue += 1; $RiskScoring += 'Container: +1' }
            }
        }
    }

    # ESC9/ESC16 are much more dangerous when ESC6 exists
    if ($Issue.Technique -match 'ESC9|ESC16') {
        $ESC6 = Find-ESC6 -ADCSObjects $ADCSObjects -UnsafeUsers $UnsafeUsers -SkipRisk
        if ($ESC6) {
            $RiskValue += 2
            $RiskScoring += "One or more CAs have an ESC6: +2"
            foreach ($ca in $ESC6.CAFullName) {
                $RiskScoring += " - ESC6 exists on $ca"
            }
        } # end if ($ESC6)
    }

    # Convert Value to Name
    $RiskName = switch ($RiskValue) {
        { $_ -le 1 } { 'Informational' }
        2 { 'Low' }
        3 { 'Medium' }
        4 { 'High' }
        { $_ -ge 5 } { 'Critical' }
    }

    # Write Risk attributes
    $Issue | Add-Member -NotePropertyName RiskValue -NotePropertyValue $RiskValue -Force
    $Issue | Add-Member -NotePropertyName RiskName -NotePropertyValue $RiskName -Force
    $Issue | Add-Member -NotePropertyName RiskScoring -NotePropertyValue $RiskScoring -Force
}