.local/share/powershell/Modules/Locksmith2/Locksmith2.Classes.ps1
|
class LS2AdcsObject { # Common properties for all AD CS objects [string]$distinguishedName [string[]]$objectClass [string]$name [string]$displayName [string]$cn [System.DirectoryServices.ActiveDirectorySecurity]$ObjectSecurity [string]$Path [string]$Owner [Nullable[bool]]$HasNonStandardOwner # Certificate Template properties (pKICertificateTemplate) [Nullable[int]]$flags [Nullable[int]]$pKIDefaultKeySpec [Nullable[int]]$pKIMaxIssuingDepth [string[]]$pKICriticalExtensions [string[]]$pKIExtendedKeyUsage [Nullable[int]]$CertificateNameFlag # msPKI-Certificate-Name-Flag [Nullable[int]]$EnrollmentFlag # msPKI-Enrollment-Flag [Nullable[int]]$PrivateKeyFlag # msPKI-Private-Key-Flag [Nullable[int]]$RASignature # msPKI-RA-Signature [Nullable[int]]$TemplateSchemaVersion # msPKI-Template-Schema-Version [Nullable[int]]$TemplateMinorRevision # msPKI-Template-Minor-Revision # CA properties (pKIEnrollmentService) [string[]]$certificateTemplates [string]$dNSHostName [object[]]$CAAdministrators [object[]]$CertificateManagers [string[]]$DangerousCAAdministrator [string[]]$DangerousCAAdministratorNames [string[]]$LowPrivilegeCAAdministrator [string[]]$LowPrivilegeCAAdministratorNames [string[]]$DangerousCACertificateManager [string[]]$DangerousCACertificateManagerNames [string[]]$LowPrivilegeCACertificateManager [string[]]$LowPrivilegeCACertificateManagerNames # Computed properties (added by Set-* functions) [Nullable[bool]]$SANAllowed [Nullable[bool]]$AuthenticationEKUExist [Nullable[bool]]$AnyPurposeEKUExist [Nullable[bool]]$DangerousEnrollee [Nullable[bool]]$LowPrivilegeEnrollee [string[]]$DangerousEditor [string[]]$DangerousEditorNames [string[]]$LowPrivilegeEditor [string[]]$LowPrivilegeEditorNames [Nullable[bool]]$ManagerApprovalNotRequired [Nullable[bool]]$AuthorizedSignatureNotRequired [Nullable[bool]]$Enabled [string[]]$EnabledOn [string]$ComputerPrincipal [Nullable[bool]]$RPCEncryptionNotRequired [object[]]$EditFlags [Nullable[bool]]$SANFlagEnabled [object[]]$InterfaceFlags [Nullable[int]]$AuditFilter [object[]]$DisableExtensionList [Nullable[bool]]$SecurityExtensionDisabled # Schema class name for easy type checking [string]$SchemaClassName # Constructor from DirectoryEntry LS2AdcsObject([System.DirectoryServices.DirectoryEntry]$DirectoryEntry) { # Common properties - set these FIRST $this.distinguishedName = if ($DirectoryEntry.distinguishedName) { $DirectoryEntry.distinguishedName.Value } else { $null } $this.objectClass = if ($DirectoryEntry.objectClass) { @($DirectoryEntry.objectClass) } else { @() } $this.name = if ($DirectoryEntry.name) { $DirectoryEntry.name.Value } else { $null } $this.displayName = if ($DirectoryEntry.displayName) { $DirectoryEntry.displayName.Value } else { $null } $this.cn = if ($DirectoryEntry.cn) { $DirectoryEntry.cn.Value } else { $null } $this.Path = $DirectoryEntry.Path # CA properties - set dNSHostName early for CAFullName ScriptProperty $this.certificateTemplates = if ($DirectoryEntry.Properties.Contains('certificateTemplates')) { @($DirectoryEntry.certificateTemplates) } else { @() } $this.dNSHostName = if ($DirectoryEntry.Properties.Contains('dNSHostName')) { $DirectoryEntry.Properties['dNSHostName'][0] } else { $null } # Determine schema class name (most specific objectClass) if ($this.objectClass.Count -gt 0) { $this.SchemaClassName = $this.objectClass[$this.objectClass.Count - 1] } # Certificate Template properties $this.flags = if ($DirectoryEntry.Properties.Contains('flags')) { $DirectoryEntry.flags.Value } else { $null } $this.pKIDefaultKeySpec = if ($DirectoryEntry.Properties.Contains('pKIDefaultKeySpec')) { $DirectoryEntry.pKIDefaultKeySpec.Value } else { $null } $this.pKIMaxIssuingDepth = if ($DirectoryEntry.Properties.Contains('pKIMaxIssuingDepth')) { $DirectoryEntry.pKIMaxIssuingDepth.Value } else { $null } $this.pKICriticalExtensions = if ($DirectoryEntry.Properties.Contains('pKICriticalExtensions')) { @($DirectoryEntry.pKICriticalExtensions) } else { @() } $this.pKIExtendedKeyUsage = if ($DirectoryEntry.Properties.Contains('pKIExtendedKeyUsage')) { @($DirectoryEntry.pKIExtendedKeyUsage) } else { @() } # msPKI-* properties with proper type conversion $this.CertificateNameFlag = if ($DirectoryEntry.Properties.Contains('msPKI-Certificate-Name-Flag')) { [int]$DirectoryEntry.Properties['msPKI-Certificate-Name-Flag'][0] } else { $null } $this.EnrollmentFlag = if ($DirectoryEntry.Properties.Contains('msPKI-Enrollment-Flag')) { [int]$DirectoryEntry.Properties['msPKI-Enrollment-Flag'][0] } else { $null } $this.PrivateKeyFlag = if ($DirectoryEntry.Properties.Contains('msPKI-Private-Key-Flag')) { [int]$DirectoryEntry.Properties['msPKI-Private-Key-Flag'][0] } else { $null } $this.RASignature = if ($DirectoryEntry.Properties.Contains('msPKI-RA-Signature')) { [int]$DirectoryEntry.Properties['msPKI-RA-Signature'][0] } else { $null } $this.TemplateSchemaVersion = if ($DirectoryEntry.Properties.Contains('msPKI-Template-Schema-Version')) { [int]$DirectoryEntry.Properties['msPKI-Template-Schema-Version'][0] } else { $null } $this.TemplateMinorRevision = if ($DirectoryEntry.Properties.Contains('msPKI-Template-Minor-Revision')) { [int]$DirectoryEntry.Properties['msPKI-Template-Minor-Revision'][0] } else { $null } # Security descriptor and ownership try { $this.ObjectSecurity = $DirectoryEntry.ObjectSecurity $this.Owner = $this.ObjectSecurity.Owner } catch { Write-Verbose "Could not retrieve ObjectSecurity for '$($this.distinguishedName)': $_" $this.ObjectSecurity = $null $this.Owner = $null } # Initialize HasNonStandardOwner (preserve value if already set by Set-HasNonStandardOwner) if ($DirectoryEntry.PSObject.Properties['HasNonStandardOwner'] -and $null -ne $DirectoryEntry.HasNonStandardOwner) { $this.HasNonStandardOwner = $DirectoryEntry.HasNonStandardOwner } else { $this.HasNonStandardOwner = $null } # Initialize computed properties to defaults $this.SANAllowed = $null $this.AuthenticationEKUExist = $null $this.AnyPurposeEKUExist = $null $this.DangerousEnrollee = $null $this.LowPrivilegeEnrollee = $null $this.DangerousEditor = @() $this.DangerousEditorNames = @() $this.LowPrivilegeEditor = @() $this.LowPrivilegeEditorNames = @() $this.ManagerApprovalNotRequired = $null $this.AuthorizedSignatureNotRequired = $null $this.Enabled = $null $this.EnabledOn = @() $this.RPCEncryptionNotRequired = $null $this.SANFlagEnabled = $null $this.AuditFilter = $null $this.DisableExtensionList = @() # Initialize CA-specific properties $this.CAAdministrators = @() $this.CertificateManagers = @() $this.DangerousCAAdministrator = @() $this.DangerousCAAdministratorNames = @() $this.LowPrivilegeCAAdministrator = @() $this.LowPrivilegeCAAdministratorNames = @() $this.DangerousCACertificateManager = @() $this.DangerousCACertificateManagerNames = @() $this.LowPrivilegeCACertificateManager = @() $this.LowPrivilegeCACertificateManagerNames = @() # Add CAFullName as a ScriptProperty for CA objects if ($this.IsCertificationAuthority()) { $this | Add-Member -MemberType ScriptProperty -Name CAFullName -Value { if ($this.dNSHostName -and $this.cn) { return "$($this.dNSHostName)\$($this.cn)" } elseif ($this.cn) { return $this.cn } else { return $null } } } # Add nTSecurityDescriptor as an alias for ObjectSecurity $this | Add-Member -MemberType ScriptProperty -Name nTSecurityDescriptor -Value { return $this.ObjectSecurity } } # Method to check if this is a Certificate Template [bool] IsCertificateTemplate() { return $this.SchemaClassName -eq 'pKICertificateTemplate' } # Method to check if this is a CA [bool] IsCertificationAuthority() { return $this.objectClass -contains 'pKIEnrollmentService' } # Method to get a friendly name for logging [string] GetFriendlyName() { if ($this.displayName) { return $this.displayName } elseif ($this.name) { return $this.name } elseif ($this.cn) { return $this.cn } else { return $this.distinguishedName } } } class LS2Issue { # Core issue identification [string]$Technique # ESC1, ESC2, ESC6, etc. [string]$Forest # Forest where issue was found [string]$Name # Friendly name of vulnerable object [string]$DistinguishedName # DN of vulnerable object # Principal information (for permission-based issues) [string]$IdentityReference # DOMAIN\User or group name [string]$IdentityReferenceSID # SID of the principal [string]$ActiveDirectoryRights # GenericAll, ExtendedRight, etc. # Template-specific properties [Nullable[bool]]$Enabled # Whether template is enabled on any CA [string[]]$EnabledOn # List of CAs where template is enabled # CA-specific properties [string]$CAFullName # For CA issues: SERVER\CA # Ownership properties [string]$Owner # Owner of the vulnerable object [Nullable[bool]]$HasNonStandardOwner # Whether object has non-standard owner # Group expansion properties [Nullable[int]]$MemberCount # For group issues: number of members expanded # Issue details [string]$Issue # Description of the vulnerability [string]$Fix # PowerShell script to remediate [string]$Revert # PowerShell script to undo remediation # Constructor for creating issues from hashtable LS2Issue([hashtable]$Properties) { # Core properties $this.Technique = $Properties.Technique $this.Forest = $Properties.Forest $this.Name = $Properties.Name $this.DistinguishedName = $Properties.DistinguishedName # Principal properties (may be null for non-permission issues) $this.IdentityReference = $Properties.IdentityReference $this.IdentityReferenceSID = $Properties.IdentityReferenceSID $this.ActiveDirectoryRights = $Properties.ActiveDirectoryRights # Template properties (may be null for CA issues) $this.Enabled = $Properties.Enabled $this.EnabledOn = $Properties.EnabledOn # CA properties (may be null for template issues) $this.CAFullName = $Properties.CAFullName # Ownership properties $this.Owner = $Properties.Owner $this.HasNonStandardOwner = $Properties.HasNonStandardOwner # Issue details $this.Issue = $Properties.Issue $this.Fix = $Properties.Fix $this.Revert = $Properties.Revert } # Method to get a friendly identifier for the issue [string] GetIdentifier() { if ($this.IdentityReference) { return "$($this.Technique): $($this.Name) - $($this.IdentityReference)" } elseif ($this.Owner) { return "$($this.Technique): $($this.Name) - Owner: $($this.Owner)" } else { return "$($this.Technique): $($this.Name)" } } # Method to check if this is a permission-based issue [bool] HasPrincipal() { return -not [string]::IsNullOrEmpty($this.IdentityReference) } # Method to check if this is a template issue [bool] IsTemplateIssue() { return $null -ne $this.Enabled } # Method to check if this is a CA issue [bool] IsCAIssue() { return -not [string]::IsNullOrEmpty($this.CAFullName) } # Method to check if this issue matches another issue (for deduplication) [bool] Matches([LS2Issue]$Other) { if ($null -eq $Other) { return $false } # Core properties must match if ($this.Technique -ne $Other.Technique) { return $false } if ($this.DistinguishedName -ne $Other.DistinguishedName) { return $false } # Principal properties must match (null-safe comparison) if ($this.IdentityReferenceSID -ne $Other.IdentityReferenceSID) { return $false } if ($this.ActiveDirectoryRights -ne $Other.ActiveDirectoryRights) { return $false } # CA property must match if ($this.CAFullName -ne $Other.CAFullName) { return $false } # Owner must match if ($this.Owner -ne $Other.Owner) { return $false } return $true } } class LS2Principal { [string]$distinguishedName [string]$objectSid [string]$sAMAccountName [string]$objectClass [string]$displayName [string]$NTAccountName [string]$userPrincipalName [string[]]$memberOf [int]$MemberCount [System.DirectoryServices.ActiveDirectorySecurity]$ObjectSecurity # Constructor from SearchResult LS2Principal( [System.DirectoryServices.SearchResult]$SearchResult, [string]$Server, [System.Security.Principal.SecurityIdentifier]$SidKey, [string]$NTAccountName ) { $this.distinguishedName = $SearchResult.Properties['distinguishedName'][0] # Create DirectoryEntry to get ObjectSecurity if (-not $Server) { throw "Server parameter is null or empty" } $objectPath = "LDAP://$Server/$($this.distinguishedName)" Write-Verbose "LS2Principal: Creating DirectoryEntry for $objectPath" $tempEntry = New-AuthenticatedDirectoryEntry -Path $objectPath # Handle case where DirectoryEntry creation fails if (-not $tempEntry) { throw "Failed to create DirectoryEntry for path: $objectPath" } # Set objectSid if ($SearchResult.Properties['objectSid'].Count -gt 0) { $this.objectSid = (New-Object System.Security.Principal.SecurityIdentifier($SearchResult.Properties['objectSid'][0], 0)).Value } # Set sAMAccountName if ($SearchResult.Properties['sAMAccountName'].Count -gt 0) { $this.sAMAccountName = $SearchResult.Properties['sAMAccountName'][0] } # Set objectClass (get the most specific class) if ($SearchResult.Properties['objectClass'].Count -gt 0) { $classes = @($SearchResult.Properties['objectClass']) $this.objectClass = $classes[$classes.Count - 1] } # Set displayName if ($SearchResult.Properties['displayName'].Count -gt 0) { $this.displayName = $SearchResult.Properties['displayName'][0] } # Set NTAccountName - use provided or build from sAMAccountName + domain if ($NTAccountName) { $this.NTAccountName = $NTAccountName } elseif ($this.sAMAccountName) { # Build NTAccount name from sAMAccountName and domain NetBIOS name $domainDN = $this.distinguishedName -replace '^.*?,(?=DC=)', '' if ($script:DomainStore -and $script:DomainStore.ContainsKey($domainDN)) { $domainNetBiosName = $script:DomainStore[$domainDN].nETBIOSName.ToUpper() $this.NTAccountName = "$domainNetBiosName\$($this.sAMAccountName)" } else { # Fallback: extract first DC component from DN if ($domainDN -match 'DC=([^,]+)') { $domainNetBiosName = $Matches[1].ToUpper() $this.NTAccountName = "$domainNetBiosName\$($this.sAMAccountName)" } } } # Set userPrincipalName if ($SearchResult.Properties['userPrincipalName'].Count -gt 0) { $this.userPrincipalName = $SearchResult.Properties['userPrincipalName'][0] } # Set memberOf if ($SearchResult.Properties['memberOf'].Count -gt 0) { $this.memberOf = @($SearchResult.Properties['memberOf']) } else { $this.memberOf = @() } # Set ObjectSecurity (may fail for some objects in PS5.1) try { $this.ObjectSecurity = $tempEntry.ObjectSecurity } catch { Write-Verbose "Could not retrieve ObjectSecurity for '$($this.distinguishedName)': $_" $this.ObjectSecurity = $null } # Dispose only if not null if ($tempEntry) { $tempEntry.Dispose() } # Add nTSecurityDescriptor as an alias for ObjectSecurity $this | Add-Member -MemberType ScriptProperty -Name nTSecurityDescriptor -Value { return $this.ObjectSecurity } } # Constructor for well-known principals that don't exist in AD # Used for BUILTIN groups (S-1-5-32-*) and other machine-local SIDs # Note: Some well-known SIDs like S-1-5-11 exist as foreignSecurityPrincipal objects # and will use the main constructor instead LS2Principal( [string]$ObjectSid, [string]$NTAccountName ) { $this.distinguishedName = $null $this.objectSid = $ObjectSid $this.sAMAccountName = $null $this.objectClass = 'wellKnownPrincipal' $this.displayName = $null $this.NTAccountName = $NTAccountName $this.userPrincipalName = $null $this.memberOf = @() $this.ObjectSecurity = $null } } |