Private/Add-IssueToObject.ps1
|
function Add-IssueToObject { <# .SYNOPSIS Adds Issue objects as properties to DirectoryEntry objects from AD CS scans. .DESCRIPTION This function takes DirectoryEntry objects from Get-AdcsObjects and attaches security issues found by Find-ESC4Issue, Find-ESC5Issue, and other vulnerability scanning functions as properties. .PARAMETER AdcsObjects Array of DirectoryEntry objects from Get-AdcsObjects to attach issues to. .PARAMETER Issues Array of ESCalatorIssue objects from Find-ESC4Issue, Find-ESC5Issue, or other vulnerability scanning functions. Supports multiple arrays that will be automatically flattened. .INPUTS System.DirectoryServices.DirectoryEntry[] DirectoryEntry objects from Get-AdcsObjects ESCalatorIssue[] ESCalatorIssue objects from Find-ESC4Issue, Find-ESC5Issue, or other vulnerability scanning functions. Supports multiple arrays that will be automatically flattened. .OUTPUTS Object[] Returns the original objects with additional issue-related properties added. .EXAMPLE $AdcsObjects = Get-AdcsObjects $ESC4Issues = Find-ESC4Issue -AdcsObjects $AdcsObjects $ESC5Issues = Find-ESC5Issue -AdcsObjects $AdcsObjects $AllIssues = @($ESC4Issues; $ESC5Issues) $ObjectsWithIssues = Add-Issue -AdcsObjects $AdcsObjects -Issues $AllIssues .EXAMPLE $AdcsObjects = Get-AdcsObjects $Issues = @(Find-ESC4Issue -AdcsObjects $AdcsObjects; Find-ESC5Issue -AdcsObjects $AdcsObjects) $ObjectsWithIssues = Add-Issue -AdcsObjects $AdcsObjects -Issues $Issues $VulnerableObjects = $ObjectsWithIssues | Where-Object { $_.HasIssues } .EXAMPLE # Pipeline usage Get-AdcsObjects | Add-Issue -Issues $AllIssues | Where-Object { $_.HasIssues } .LINK https://posts.specterops.io/certified-pre-owned-d95910965cd2 #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.DirectoryServices.DirectoryEntry[]]$AdcsObjects, [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]]$Issues ) begin { Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd hh:mm:ss')] Starting $($MyInvocation.MyCommand) on $env:COMPUTERNAME..." # Load ESCalatorIssue class if not already loaded if (-not ([System.Management.Automation.PSTypeName]'ESCalatorIssue').Type) { $escalatorIssuePath = Join-Path $PSScriptRoot "ESCalatorIssue.ps1" if (Test-Path $escalatorIssuePath) { . $escalatorIssuePath } else { throw "ESCalatorIssue class not found. Please ensure ESCalatorIssue.ps1 is available." } } # Flatten any nested arrays and validate all items are ESCalatorIssue objects $AllIssues = @() $NonESCalatorIssues = @() $Issues | ForEach-Object { if ($_.PSObject.TypeNames[0] -eq 'ESCalatorIssue') { $AllIssues += $_ } elseif ($_ -is [Array]) { # Recursively flatten nested arrays $_ | ForEach-Object { if ($_.PSObject.TypeNames[0] -eq 'ESCalatorIssue') { $AllIssues += $_ } else { $NonESCalatorIssues += $_ } } } else { $NonESCalatorIssues += $_ } } # Warn about non-ESCalatorIssue objects but continue processing if ($NonESCalatorIssues.Count -gt 0) { Write-Warning "Found $($NonESCalatorIssues.Count) non-ESCalatorIssue objects that will be ignored. Expected ESCalatorIssue objects." } Write-Verbose "Processing $($AllIssues.Count) ESCalatorIssue objects for attachment to AD CS objects" } process { foreach ($AdcsObject in $AdcsObjects) { # Handle different object types and property access patterns $objectName = if ($AdcsObject.Name.Value) { $AdcsObject.Name.Value } elseif ($AdcsObject.Name) { $AdcsObject.Name } else { "Unknown" } $objectDN = if ($AdcsObject.distinguishedName.Value) { $AdcsObject.distinguishedName.Value } elseif ($AdcsObject.distinguishedName) { $AdcsObject.distinguishedName } else { $null } Write-Verbose "Processing AD CS object: $objectName" try { # Find issues related to this object $relatedIssues = $AllIssues | Where-Object { $_.Name -eq $objectName -or $_.DistinguishedName -eq $objectDN } Write-Verbose "Found $($relatedIssues.Count) issues for object: $objectName" # Group issues by technique for easier analysis $issuesByTechnique = $relatedIssues | Group-Object Technique -AsHashTable -AsString # Get unique techniques and affected principals $techniques = $relatedIssues | Select-Object -ExpandProperty Technique -Unique $affectedPrincipals = $relatedIssues | Select-Object -ExpandProperty IdentityReference -Unique # Add comprehensive issue information as properties $AdcsObject | Add-Member -NotePropertyName "SecurityIssues" -NotePropertyValue $relatedIssues -Force $AdcsObject | Add-Member -NotePropertyName "IssueCount" -NotePropertyValue $relatedIssues.Count -Force $AdcsObject | Add-Member -NotePropertyName "HasIssues" -NotePropertyValue ($relatedIssues.Count -gt 0) -Force $AdcsObject | Add-Member -NotePropertyName "IssuesByTechnique" -NotePropertyValue $issuesByTechnique -Force $AdcsObject | Add-Member -NotePropertyName "VulnerableTechniques" -NotePropertyValue $techniques -Force $AdcsObject | Add-Member -NotePropertyName "AffectedPrincipals" -NotePropertyValue $affectedPrincipals -Force $AdcsObject | Add-Member -NotePropertyName "AffectedPrincipalCount" -NotePropertyValue $affectedPrincipals.Count -Force # Add convenience methods for common operations $AdcsObject | Add-Member -MemberType ScriptMethod -Name "GetIssuesByTechnique" -Value { param([string]$Technique) return $this.SecurityIssues | Where-Object { $_.Technique -eq $Technique } } -Force $AdcsObject | Add-Member -MemberType ScriptMethod -Name "GetIssueSummary" -Value { $objName = if ($this.Name.Value) { $this.Name.Value } elseif ($this.Name) { $this.Name } else { "Unknown" } $summary = [PSCustomObject]@{ ObjectName = $objName TotalIssues = $this.IssueCount Techniques = $this.VulnerableTechniques -join ', ' AffectedPrincipals = $this.AffectedPrincipalCount } return $summary } -Force Write-Output $AdcsObject } catch { Write-Warning "Failed to process AD CS object $objectName : $_" # Still output the object even if issue attachment failed Write-Output $AdcsObject } } } end { Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd hh:mm:ss')] Finishing $($MyInvocation.MyCommand) on $env:COMPUTERNAME..." } } |