From 03aa80ea3f8229317b693d38df60c16eea695b2a Mon Sep 17 00:00:00 2001 From: TrimarcJake Date: Sat, 14 Sep 2024 08:14:48 -0500 Subject: [PATCH 01/14] Separate methods of skipping certificates checks depending on PS version. --- Invoke-Locksmith.ps1 | 101 ++++++++++++++++++--------- Locksmith.psd1 | 2 +- Private/Set-AdditionalCAProperty.ps1 | 39 +++++++---- 3 files changed, 96 insertions(+), 46 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 1618ea0..0e9ba7d 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -421,10 +421,9 @@ function Find-ESC3Condition2 { $ADCSObjects | Where-Object { ($_.objectClass -eq 'pKICertificateTemplate') -and ($_.pkiExtendedKeyUsage -match $ClientAuthEKU) -and - ($_.'msPKI-Certificate-Name-Flag' -band 1) -and !($_.'msPKI-Enrollment-Flag' -band 2) -and - ($_.'msPKI-RA-Application-Policies' -eq '1.3.6.1.4.1.311.20.2.1') -and - ( ($_.'msPKI-RA-Signature' -eq 1) ) + ($_.'msPKI-RA-Application-Policies' -match '1.3.6.1.4.1.311.20.2.1') -and + ($_.'msPKI-RA-Signature' -eq 1) } | ForEach-Object { foreach ($entry in $_.nTSecurityDescriptor.Access) { $Principal = New-Object System.Security.Principal.NTAccount($entry.IdentityReference) @@ -1629,7 +1628,7 @@ function Invoke-Scans { .EXAMPLE # Perform all scans - Invoke-Scans + Invoke-Scans .EXAMPLE # Perform only the 'Auditing' and 'ESC1' scans @@ -1641,6 +1640,8 @@ function Invoke-Scans { #> [CmdletBinding()] + [OutputType([hashtable])] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Invoke-Scans', Justification = 'Performing multiple scans.')] param ( # Could split Scans and PromptMe into separate parameter sets. [Parameter()] @@ -1974,17 +1975,33 @@ function Set-AdditionalCAProperty { begin { $CAEnrollmentEndpoint = @() - $code = @" -using System.Net; -using System.Security.Cryptography.X509Certificates; -public class TrustAllCertsPolicy : ICertificatePolicy { - public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { - return true; - } -} + if ($PSVersionTable.PSEdition -eq 'Desktop') { + $code = @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + public class TrustAllCertsPolicy : ICertificatePolicy { + public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { + return true; + } + } "@ - Add-Type -TypeDefinition $code -Language CSharp - [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy + Add-Type -TypeDefinition $code -Language CSharp + [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy + } + else { + Add-Type @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + using System.Net.Security; + public class TrustAllCertsPolicy { + public static bool TrustAllCerts(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { + return true; + } + } +"@ + # Set the ServerCertificateValidationCallback + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [TrustAllCertsPolicy]::TrustAllCerts + } } process { @@ -2010,7 +2027,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { try { $FullURL = "https$URL" $Request = [System.Net.WebRequest]::Create($FullURL) - + $Request.GetResponse() | Out-Null $CAEnrollmentEndpoint += @{ 'URL' = $FullURL @@ -2047,7 +2064,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname } $ping = Test-Connection -ComputerName $CAHostFQDN -Quiet -Count 1 - if ($ping) { + if ($ping) { try { if ($Credential) { $CertutilAudit = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg CA\AuditFilter } -ArgumentList $CAFullName @@ -2110,6 +2127,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { } function Set-Severity { + [OutputType([string])] [CmdletBinding()] param( [Parameter(Mandatory = $true)] @@ -2237,9 +2255,11 @@ function Test-IsMemberOfProtectedUsers { Active Directory user object, user SID, SamAccountName, etc .OUTPUTS - True, False + Boolean #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Test-IsMemberOfProtectedUsers', Justification = 'The name of the group we are checking is plural.')] + [OutputType([Boolean])] [CmdletBinding()] param ( # User parameter accepts any input that is valid for Get-ADUser @@ -2318,12 +2338,9 @@ function Test-IsRecentVersion { Published at: 01/28/2024 12:47:18 Install Module: Install-Module -Name Locksmith Standalone Script: https://github.com/trimarcjake/locksmith/releases/download/v2.6/Invoke-Locksmith.zip - - .NOTES - Author: Sam Erde - Date: 02/10/2024 #> [CmdletBinding()] + [OutputType([boolean])] param ( # Check a specific version number from the script [Parameter(Mandatory)] @@ -2568,7 +2585,7 @@ function Invoke-Locksmith { Finds the most common malconfigurations of Active Directory Certificate Services (AD CS). .DESCRIPTION - Locksmith uses the Active Directory (AD) Powershell (PS) module to identify 6 misconfigurations + Locksmith uses the Active Directory (AD) Powershell (PS) module to identify 7 misconfigurations commonly found in Enterprise mode AD CS installations. .COMPONENT @@ -2630,17 +2647,30 @@ function Invoke-Locksmith { [CmdletBinding()] param ( - [string]$Forest, - [string]$InputPath, + #[string]$Forest, # Not used yet + #[string]$InputPath, # Not used yet + + # The mode to run Locksmith in. Defaults to 0. + [Parameter()] + [ValidateSet(0, 1, 2, 3, 4)] [int]$Mode = 0, + + # The scans to run. Defaults to 'All'. [Parameter()] [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'All', 'PromptMe')] [array]$Scans = 'All', - [string]$OutputPath = (Get-Location).Path, + + # The directory to save the output in (defaults to the current working directory). + [Parameter()] + [ValidateScript({ Test-Path -Path $_ -PathType Container })] + [string]$OutputPath = $PWD, + + # The credential to use for working with ADCS. + [Parameter()] [System.Management.Automation.PSCredential]$Credential ) - $Version = '2024.8' + $Version = '2024.9' $LogoPart1 = @" _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| @@ -2670,10 +2700,13 @@ function Invoke-Locksmith { # Exit if running in restricted admin mode without explicit credentials if (!$Credential -and (Get-RestrictedAdminModeSetting)) { Write-Warning "Restricted Admin Mode appears to be in place, re-run with the '-Credential domain\user' option" - break; + break } ### Initial variables + # For output filenames + [string]$FilePrefix = "Locksmith $(Get-Date -Format 'yyyy-MM-dd hh-mm-ss')" + # Extended Key Usages for client authentication. A requirement for ESC1 $ClientAuthEKUs = '1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.5\.2\.3\.4|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|2\.5\.29\.37\.0' @@ -2723,18 +2756,20 @@ function Invoke-Locksmith { ### Generated variables # $Dictionary = New-Dictionary + + $Forest = Get-ADForest $ForestGC = $(Get-ADDomainController -Discover -Service GlobalCatalog -ForceDiscover | Select-Object -ExpandProperty Hostname) + ":3268" - # $DNSRoot = [string]((Get-ADForest).RootDomain | Get-ADDomain).DNSRoot - $EnterpriseAdminsSID = ([string]((Get-ADForest).RootDomain | Get-ADDomain).DomainSID) + '-519' + # $DNSRoot = [string]($Forest.RootDomain | Get-ADDomain).DNSRoot + $EnterpriseAdminsSID = ([string]($Forest.RootDomain | Get-ADDomain).DomainSID) + '-519' $PreferredOwner = [System.Security.Principal.SecurityIdentifier]::New($EnterpriseAdminsSID) - # $DomainSIDs = (Get-ADForest).Domains | ForEach-Object { (Get-ADDomain $_).DomainSID.Value } + # $DomainSIDs = $Forest.Domains | ForEach-Object { (Get-ADDomain $_).DomainSID.Value } # Add SIDs of (probably) Safe Users to $SafeUsers Get-ADGroupMember $EnterpriseAdminsSID | ForEach-Object { $SafeUsers += '|' + $_.SID.Value } - (Get-ADForest).Domains | ForEach-Object { + $Forest.Domains | ForEach-Object { $DomainSID = (Get-ADDomain $_).DomainSID.Value <# -517 = Cert Publishers @@ -2837,7 +2872,7 @@ function Invoke-Locksmith { Format-Result $ESC8 '1' } 2 { - $Output = 'ADCSIssues.CSV' + $Output = Join-Path -Path $OutputPath -ChildPath "$FilePrefix ADCSIssues.CSV" Write-Host "Writing AD CS issues to $Output..." try { $AllIssues | Select-Object Forest, Technique, Name, Issue | Export-Csv -NoTypeInformation $Output @@ -2848,7 +2883,7 @@ function Invoke-Locksmith { } } 3 { - $Output = 'ADCSRemediation.CSV' + $Output = Join-Path -Path $OutputPath -ChildPath "$FilePrefix ADCSRemediation.CSV" Write-Host "Writing AD CS issues to $Output..." try { $AllIssues | Select-Object Forest, Technique, Name, DistinguishedName, Issue, Fix | Export-Csv -NoTypeInformation $Output diff --git a/Locksmith.psd1 b/Locksmith.psd1 index 13596d7..d63fadd 100644 --- a/Locksmith.psd1 +++ b/Locksmith.psd1 @@ -7,7 +7,7 @@ Description = 'A small tool to find and fix common misconfigurations in Active Directory Certificate Services.' FunctionsToExport = @('*') GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' - ModuleVersion = '2024.8' + ModuleVersion = '2024.9' PowerShellVersion = '5.1' PrivateData = @{ PSData = @{ diff --git a/Private/Set-AdditionalCAProperty.ps1 b/Private/Set-AdditionalCAProperty.ps1 index a397a47..461ba51 100644 --- a/Private/Set-AdditionalCAProperty.ps1 +++ b/Private/Set-AdditionalCAProperty.ps1 @@ -37,17 +37,32 @@ begin { $CAEnrollmentEndpoint = @() - $code= @" -using System.Net; -using System.Security.Cryptography.X509Certificates; -public class TrustAllCertsPolicy : ICertificatePolicy { - public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { - return true; - } -} + if ($PSVersionTable.PSEdition -eq 'Desktop') { + $code= @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + public class TrustAllCertsPolicy : ICertificatePolicy { + public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { + return true; + } + } "@ - Add-Type -TypeDefinition $code -Language CSharp - [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy + Add-Type -TypeDefinition $code -Language CSharp + [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy + } else { + Add-Type @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + using System.Net.Security; + public class TrustAllCertsPolicy { + public static bool TrustAllCerts(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { + return true; + } + } +"@ + # Set the ServerCertificateValidationCallback + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [TrustAllCertsPolicy]::TrustAllCerts + } } process { @@ -72,7 +87,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { try { $FullURL = "https$URL" $Request = [System.Net.WebRequest]::Create($FullURL) - + $Request.GetResponse() | Out-Null $CAEnrollmentEndpoint += @{ 'URL' = $FullURL @@ -106,7 +121,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname } $ping = Test-Connection -ComputerName $CAHostFQDN -Quiet -Count 1 - if ($ping) { + if ($ping) { try { if ($Credential) { $CertutilAudit = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg CA\AuditFilter } -ArgumentList $CAFullName From 977e181fc74baf2fb6363a982b70deb585ce3962 Mon Sep 17 00:00:00 2001 From: Sam Erde Date: Fri, 20 Sep 2024 17:16:48 -0400 Subject: [PATCH 02/14] Just for fun --- Private/Show-LocksmithLogo.ps1 | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Private/Show-LocksmithLogo.ps1 diff --git a/Private/Show-LocksmithLogo.ps1 b/Private/Show-LocksmithLogo.ps1 new file mode 100644 index 0000000..ae93dbc --- /dev/null +++ b/Private/Show-LocksmithLogo.ps1 @@ -0,0 +1,37 @@ +function Show-LocksmithLogo { + Write-Host '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%%%%%%%#+==============#%%%%%%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%%%%#=====================#%%%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%%#=========================#%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%=============================%%%%%%%%%%%' + Write-Host '%%%%%%%%%#==============+++==============#%%%%%%%%%' + Write-Host '%%%%%%%%#===========#%%%%%%%%%#===========#%%%%%%%%' + Write-Host '%%%%%%%%==========%%%%%%%%%%%%%%%==========%%%%%%%%' + Write-Host '%%%%%%%*=========%%%%%%%%%%%%%%%%%=========*%%%%%%%' + Write-Host '%%%%%%%+========*%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' + Write-Host '#=================================================#' + Write-Host '#=================================================#' + Write-Host '#=================+%%%============================#' + Write-Host '#==================%%%%*==========================#' + Write-Host '#===================*%%%%+========================#' + Write-Host '#=====================#%%%%=======================#' + Write-Host '#======================+%%%%#=====================#' + Write-Host '#========================*%%%%*===================#' + Write-Host '#========================+%%%%%===================#' + Write-Host '#======================#%%%%%+====================#' + Write-Host '#===================+%%%%%%=======================#' + Write-Host '#=================#%%%%%+=========================#' + Write-Host '#==============+%%%%%#============================#' + Write-Host '#============*%%%%%+====+%%%%%%%%%%===============#' + Write-Host '#=============%%*========+********+===============#' + Write-Host '#=================================================#' + Write-Host '#=================================================#' + Write-Host '#=================================================#' +} From 5e20602c720292f406cee68a324b78c8f1828d49 Mon Sep 17 00:00:00 2001 From: TrimarcJake Date: Wed, 23 Oct 2024 20:26:48 -0500 Subject: [PATCH 03/14] Issue #170 --- Invoke-Locksmith.ps1 | 93 ++++++++++++++++++++++++++-- Private/Find-ESC11.ps1 | 61 ++++++++++++++++++ Private/Set-AdditionalCAProperty.ps1 | 25 +++++++- 3 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 Private/Find-ESC11.ps1 diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index f293862..fd42035 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -245,6 +245,68 @@ Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = } } +function Find-ESC11 { + <# + .SYNOPSIS + This script finds AD CS (Active Directory Certificate Services) objects that have the ESC6 vulnerability. + + .DESCRIPTION + The script takes an array of ADCS objects as input and filters them based on objects that have the objectClass + 'pKIEnrollmentService' and the SANFlag set to 'Yes'. For each matching object, it creates a custom object with + properties representing various information about the object, such as Forest, Name, DistinguishedName, Technique, + Issue, Fix, and Revert. + + .PARAMETER ADCSObjects + Specifies the array of ADCS objects to be processed. This parameter is mandatory. + + .OUTPUTS + The script outputs an array of custom objects representing the matching ADCS objects and their associated information. + + .EXAMPLE + $ADCSObjects = Get-ADCSObjects + $Results = $ADCSObjects | Find-ESC6 + $Results + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + $ADCSObjects + ) + process { + $ADCSObjects | Where-Object { + ($_.objectClass -eq 'pKIEnrollmentService') -and + ($_.InterfaceFlag -ne 'Yes') + } | ForEach-Object { + [string]$CAFullName = "$($_.dNSHostName)\$($_.Name)" + $Issue = [pscustomobject]@{ + Forest = $_.CanonicalName.split('/')[0] + Name = $_.Name + DistinguishedName = $_.DistinguishedName + Technique = 'ESC11' + Issue = $_.AuditFilter + Fix = 'N/A' + Revert = 'N/A' + } + if ($_.InterfaceFlag -eq 'No') { + $Issue.Issue = 'IF_ENFORCEENCRYPTICERTREQUEST is disabled.' + $Issue.Fix = @" +certutil -config $CAFullname -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST +Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { + Get-Service -Name `'certsvc`' | Restart-Service -Force +} +"@ + $Issue.Revert = @" +certutil -config $CAFullname -setreg CA\InterfaceFlags -IF_ENFORCEENCRYPTICERTREQUEST +Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { + Get-Service -Name `'certsvc`' | Restart-Service -Force +} +"@ + } + $Issue + } + } +} + function Find-ESC2 { <# .SYNOPSIS @@ -1037,7 +1099,7 @@ function Format-Result { ESC1 = 'ESC1 - Vulnerable Certificate Template - Authentication' ESC2 = 'ESC2 - Vulnerable Certificate Template - Subordinate CA' ESC3 = 'ESC3 - Vulnerable Certificate Template - Enrollment Agent' - ESC4 = 'ESC4 - Vulnerable Access Control - Certifcate Template' + ESC4 = 'ESC4 - Vulnerable Access Control - Certificate Template' ESC5 = 'ESC5 - Vulnerable Access Control - PKI Object' ESC6 = 'ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2 Flag Enabled' ESC8 = 'ESC8 - HTTP/S Enrollment Enabled' @@ -2011,7 +2073,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { try { $FullURL = "https$URL" $Request = [System.Net.WebRequest]::Create($FullURL) - + $Request.GetResponse() | Out-Null $CAEnrollmentEndpoint += @{ 'URL' = $FullURL @@ -2048,7 +2110,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname } $ping = Test-Connection -ComputerName $CAHostFQDN -Quiet -Count 1 - if ($ping) { + if ($ping) { try { if ($Credential) { $CertutilAudit = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg CA\AuditFilter } -ArgumentList $CAFullName @@ -2069,12 +2131,24 @@ public class TrustAllCertsPolicy : ICertificatePolicy { } } catch { - $AuditFilter = 'Failure' + $SANFlag = 'Failure' + } + try { + if ($Credential) { + $CertutilInterfaceFlag = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg policy\EditFlags } -ArgumentList $CAFullName + } + else { + $CertutilInterfaceFlag = certutil -config $CAFullName -getreg CA\InterfaceFlags + } + } + catch { + $InterfaceFlag = 'Failure' } } else { $AuditFilter = 'CA Unavailable' $SANFlag = 'CA Unavailable' + $InterfaceFlag = 'CA Unavailable' } if ($CertutilAudit) { try { @@ -2100,12 +2174,22 @@ public class TrustAllCertsPolicy : ICertificatePolicy { $SANFlag = 'No' } } + if ($CertutilInterfaceFlag) { + [string]$InterfaceFlag = $CertutilInterfaceFlag | Select-String ' IF_ENFORCEENCRYPTICERTREQUEST -- 200 \(' + if ($InterfaceFlag) { + $InterfaceFlag = 'Yes' + } + else { + $InterfaceFlag = 'No' + } + } Add-Member -InputObject $_ -MemberType NoteProperty -Name AuditFilter -Value $AuditFilter -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name CAEnrollmentEndpoint -Value $CAEnrollmentEndpoint -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name CAFullName -Value $CAFullName -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name CAHostname -Value $CAHostname -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name CAHostDistinguishedName -Value $CAHostDistinguishedName -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name SANFlag -Value $SANFlag -Force + Add-Member -InputObject $_ -MemberType NoteProperty -Name InterfaceFlag -Value $InterfaceFlag -Force } } } @@ -2886,5 +2970,4 @@ function Invoke-Locksmith { } -# Export functions and aliases as required Invoke-Locksmith -Mode $Mode -Scans $Scans diff --git a/Private/Find-ESC11.ps1 b/Private/Find-ESC11.ps1 new file mode 100644 index 0000000..81a3196 --- /dev/null +++ b/Private/Find-ESC11.ps1 @@ -0,0 +1,61 @@ +function Find-ESC11 { + <# + .SYNOPSIS + This script finds AD CS (Active Directory Certificate Services) objects that have the ESC6 vulnerability. + + .DESCRIPTION + The script takes an array of ADCS objects as input and filters them based on objects that have the objectClass + 'pKIEnrollmentService' and the SANFlag set to 'Yes'. For each matching object, it creates a custom object with + properties representing various information about the object, such as Forest, Name, DistinguishedName, Technique, + Issue, Fix, and Revert. + + .PARAMETER ADCSObjects + Specifies the array of ADCS objects to be processed. This parameter is mandatory. + + .OUTPUTS + The script outputs an array of custom objects representing the matching ADCS objects and their associated information. + + .EXAMPLE + $ADCSObjects = Get-ADCSObjects + $Results = $ADCSObjects | Find-ESC6 + $Results + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + $ADCSObjects + ) + process { + $ADCSObjects | Where-Object { + ($_.objectClass -eq 'pKIEnrollmentService') -and + ($_.InterfaceFlag -ne 'Yes') + } | ForEach-Object { + [string]$CAFullName = "$($_.dNSHostName)\$($_.Name)" + $Issue = [pscustomobject]@{ + Forest = $_.CanonicalName.split('/')[0] + Name = $_.Name + DistinguishedName = $_.DistinguishedName + Technique = 'ESC11' + Issue = $_.AuditFilter + Fix = 'N/A' + Revert = 'N/A' + } + if ($_.InterfaceFlag -eq 'No') { + $Issue.Issue = 'IF_ENFORCEENCRYPTICERTREQUEST is disabled.' + $Issue.Fix = @" +certutil -config $CAFullname -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST +Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { + Get-Service -Name `'certsvc`' | Restart-Service -Force +} +"@ + $Issue.Revert = @" +certutil -config $CAFullname -setreg CA\InterfaceFlags -IF_ENFORCEENCRYPTICERTREQUEST +Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { + Get-Service -Name `'certsvc`' | Restart-Service -Force +} +"@ + } + $Issue + } + } +} diff --git a/Private/Set-AdditionalCAProperty.ps1 b/Private/Set-AdditionalCAProperty.ps1 index a397a47..e4dcb8a 100644 --- a/Private/Set-AdditionalCAProperty.ps1 +++ b/Private/Set-AdditionalCAProperty.ps1 @@ -72,7 +72,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { try { $FullURL = "https$URL" $Request = [System.Net.WebRequest]::Create($FullURL) - + $Request.GetResponse() | Out-Null $CAEnrollmentEndpoint += @{ 'URL' = $FullURL @@ -106,7 +106,7 @@ public class TrustAllCertsPolicy : ICertificatePolicy { $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname } $ping = Test-Connection -ComputerName $CAHostFQDN -Quiet -Count 1 - if ($ping) { + if ($ping) { try { if ($Credential) { $CertutilAudit = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg CA\AuditFilter } -ArgumentList $CAFullName @@ -123,11 +123,21 @@ public class TrustAllCertsPolicy : ICertificatePolicy { $CertutilFlag = certutil -config $CAFullName -getreg policy\EditFlags } } catch { - $AuditFilter = 'Failure' + $SANFlag = 'Failure' + } + try { + if ($Credential) { + $CertutilInterfaceFlag = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg policy\EditFlags } -ArgumentList $CAFullName + } else { + $CertutilInterfaceFlag = certutil -config $CAFullName -getreg CA\InterfaceFlags + } + } catch { + $InterfaceFlag = 'Failure' } } else { $AuditFilter = 'CA Unavailable' $SANFlag = 'CA Unavailable' + $InterfaceFlag = 'CA Unavailable' } if ($CertutilAudit) { try { @@ -150,12 +160,21 @@ public class TrustAllCertsPolicy : ICertificatePolicy { $SANFlag = 'No' } } + if ($CertutilInterfaceFlag) { + [string]$InterfaceFlag = $CertutilInterfaceFlag | Select-String ' IF_ENFORCEENCRYPTICERTREQUEST -- 200 \(' + if ($InterfaceFlag) { + $InterfaceFlag = 'Yes' + } else { + $InterfaceFlag = 'No' + } + } Add-Member -InputObject $_ -MemberType NoteProperty -Name AuditFilter -Value $AuditFilter -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name CAEnrollmentEndpoint -Value $CAEnrollmentEndpoint -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name CAFullName -Value $CAFullName -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name CAHostname -Value $CAHostname -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name CAHostDistinguishedName -Value $CAHostDistinguishedName -Force Add-Member -InputObject $_ -MemberType NoteProperty -Name SANFlag -Value $SANFlag -Force + Add-Member -InputObject $_ -MemberType NoteProperty -Name InterfaceFlag -Value $InterfaceFlag -Force } } } From 7f48fd5bcca9d445378ae484c1e54b02e4da7530 Mon Sep 17 00:00:00 2001 From: TrimarcJake Date: Wed, 23 Oct 2024 20:48:15 -0500 Subject: [PATCH 04/14] #170 possibly complete --- Invoke-Locksmith.ps1 | 90 +++++++++++++++++++++++++-------- Private/Export-RevertScript.ps1 | 9 ++-- Private/Invoke-Remediation.ps1 | 41 ++++++++++++++- Private/Invoke-Scans.ps1 | 62 +++++++++++++---------- Public/Invoke-Locksmith.ps1 | 72 +++++++++++++------------- 5 files changed, 185 insertions(+), 89 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index fd42035..714819e 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -75,7 +75,7 @@ function Export-RevertScript { An array of ESC6 changes to be reverted. .EXAMPLE - Export-RevertScript -AuditingIssues $auditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 + Export-RevertScript -AuditingIssues $auditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 Reverts the changes performed by Locksmith using the specified arrays of objects. #> @@ -87,18 +87,19 @@ function Export-RevertScript { [array]$ESC3, [array]$ESC4, [array]$ESC5, - [array]$ESC6 + [array]$ESC6, + [array]$ESC11 ) begin { $Output = 'Invoke-RevertLocksmith.ps1' Set-Content -Path $Output -Value "<#`nScript to revert changes performed by Locksmith`nCreated $(Get-Date)`n#>" -Force - $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC11 } process { if ($Objects) { $Objects | ForEach-Object { Add-Content -Path $Output -Value $_.Revert - Start-Sleep -Seconds 5 + Start-Sleep -Seconds 1 } } } @@ -1409,6 +1410,9 @@ function Invoke-Remediation { .PARAMETER ESC6 A PS Object containing all necessary information about ESC6 issues. + .PARAMETER ESC6 + A PS Object containing all necessary information about ESC11 issues. + .INPUTS PS Objects @@ -1424,7 +1428,8 @@ function Invoke-Remediation { $ESC3, $ESC4, $ESC5, - $ESC6 + $ESC6, + $ESC11 ) Write-Host "`nExecuting Mode 4 - Attempting to fix identified issues!`n" -ForegroundColor Green @@ -1432,7 +1437,7 @@ function Invoke-Remediation { Write-Host 'Invoke-RevertLocksmith.ps1' -ForegroundColor White -NoNewline Write-Host ") which can be used to revert all changes made by Locksmith...`n" try { - Export-RevertScript -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 + Export-RevertScript -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 } catch { Write-Warning 'Creation of Invoke-RevertLocksmith.ps1 failed.' @@ -1648,6 +1653,41 @@ function Invoke-Remediation { } } + if ($ESC11) { + $ESC11 | ForEach-Object { + $FixBlock = [scriptblock]::Create($_.Fix) + Write-Host 'ISSUE:' -ForegroundColor White + Write-Host "The Certification Authority `"$($_.Name)`" has the IF_ENFORCEENCRYPTICERTREQUEST flag disabled.`n" + Write-Host 'TECHNIQUE:' -ForegroundColor White + Write-Host "$($_.Technique)`n" + Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White + Write-Host "Locksmith will attempt to enable the IF_ENFORCEENCRYPTICERTREQUEST flag on Certifiction Authority `"$($_.Name)`".`n" + Write-Host 'COMMAND(S) TO BE RUN' -ForegroundColor White + Write-Host 'PS> ' -NoNewline + Write-Host "$($_.Fix)`n" -ForegroundColor Cyan + $WarningError = 'n' + Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White + Write-Host "WARNING: This change could cause some services to stop working.`n" -ForegroundColor Yellow + Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow + Write-Host "Continue with this operation? [Y] Yes " -NoNewline + Write-Host "[N] " -ForegroundColor Yellow -NoNewline + Write-Host "No: " -NoNewline + $WarningError = '' + $WarningError = Read-Host + if ($WarningError -like 'y') { + try { + Invoke-Command -ScriptBlock $FixBlock + } + catch { + Write-Error 'Could not enable the IF_ENFORCEENCRYPTICERTREQUEST flag. Are you an Active Directory or AD CS admin?' + } + } + else { + Write-Host "SKIPPED!`n" -ForegroundColor Yellow + } + } + } + Write-Host "Mode 4 Complete! There are no more issues that Locksmith can automatically resolve.`n" -ForegroundColor Green Write-Host 'If you experience any operational impact from using Locksmith Mode 4, use ' -NoNewline Write-Host 'Invoke-RevertLocksmith.ps1 ' -ForegroundColor White @@ -1675,16 +1715,17 @@ function Invoke-Scans { - ESC5 - ESC6 - ESC8 + - ESC11 - All - PromptMe .PARAMETER Scans Specifies the type of scans to perform. Multiple scan options can be provided as an array. The default value is 'All'. - The available scan options are: 'Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'All', 'PromptMe'. + The available scan options are: 'Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'All', 'PromptMe'. .NOTES - The script requires the following functions to be defined: Find-AuditingIssue, Find-ESC1, Find-ESC2, Find-ESC3Condition1, - Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8. + Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8, Find-ESC8. - The script uses Out-GridView or Out-ConsoleGridView for interactive selection when the 'PromptMe' scan option is chosen. - The script returns a hash table containing the results of the scans. @@ -1713,7 +1754,7 @@ function Invoke-Scans { [int]$Mode, $SafeObjectTypes, $SafeOwners, - [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'All', 'PromptMe')] + [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'All', 'PromptMe')] [array]$Scans = 'All', $UnsafeOwners, $UnsafeUsers, @@ -1761,21 +1802,25 @@ function Invoke-Scans { [array]$ESC3 += Find-ESC3Condition2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers } ESC4 { - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC4)...' + Write-Host 'Identifying AD CS templates with poor access control (ESC4)...' [array]$ESC4 = Find-ESC4 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeObjectTypes $SafeObjectTypes } ESC5 { - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC5)...' + Write-Host 'Identifying AD CS objects with poor access control (ESC5)...' [array]$ESC5 = Find-ESC5 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeObjectTypes $SafeObjectTypes } ESC6 { - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC6)...' + Write-Host 'Identifying Issuing CAs with EDITF_ATTRIBUTESUBJECTALTNAME2 enabled (ESC6)...' [array]$ESC6 = Find-ESC6 -ADCSObjects $ADCSObjects } ESC8 { Write-Host 'Identifying HTTP-based certificate enrollment interfaces (ESC8)...' [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects } + ESC6 { + Write-Host 'Identifying Issuing CAs with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' + [array]$ESC6 = Find-ESC6 -ADCSObjects $ADCSObjects + } All { Write-Host 'Identifying auditing issues...' [array]$AuditingIssues = Find-AuditingIssue -ADCSObjects $ADCSObjects @@ -1786,21 +1831,23 @@ function Invoke-Scans { Write-Host 'Identifying AD CS templates with dangerous ESC3 configurations...' [array]$ESC3 = Find-ESC3Condition1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers [array]$ESC3 += Find-ESC3Condition2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC4)...' + Write-Host 'Identifying AD CS templates with poor access control (ESC4)...' [array]$ESC4 = Find-ESC4 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeObjectTypes $SafeObjectTypes -Mode $Mode - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC5)...' + Write-Host 'Identifying AD CS objects with poor access control (ESC5)...' [array]$ESC5 = Find-ESC5 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeObjectTypes $SafeObjectTypes - Write-Host 'Identifying Certificate Authorities configured with dangerous flags (ESC6)...' + Write-Host 'Identifying Certificate Authorities with EDITF_ATTRIBUTESUBJECTALTNAME2 enabled v (ESC6)...' [array]$ESC6 = Find-ESC6 -ADCSObjects $ADCSObjects Write-Host 'Identifying HTTP-based certificate enrollment interfaces (ESC8)...' [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects + Write-Host 'Identifying Certificate Authorities with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' + [array]$ESC11 = Find-ESC11 -ADCSObjects $ADCSObjects } } - [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + $ESC11 # If these are all empty = no issues found, exit - if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) ) { + if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) -and ($ESC11) ) { Write-Host "`n$(Get-Date) : No ADCS issues were found." -ForegroundColor Green break } @@ -1816,6 +1863,7 @@ function Invoke-Scans { ESC5 = $ESC5 ESC6 = $ESC6 ESC8 = $ESC8 + ESC11 = $ESC11 } } @@ -2727,7 +2775,7 @@ function Invoke-Locksmith { [Parameter()] [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'All', 'PromptMe')] [array]$Scans = 'All', - + # The directory to save the output in (defaults to the current working directory). [Parameter()] [ValidateScript({ Test-Path -Path $_ -PathType Container })] @@ -2908,7 +2956,7 @@ function Invoke-Locksmith { $ESC5 = $Results['ESC5'] $ESC6 = $Results['ESC6'] $ESC8 = $Results['ESC8'] - #} + $ESC11 = $Results['ESC11'] # If these are all empty = no issues found, exit if ($null -eq $Results) { @@ -2928,6 +2976,7 @@ function Invoke-Locksmith { Format-Result $ESC5 '0' Format-Result $ESC6 '0' Format-Result $ESC8 '0' + Format-Result $ESC11 '0' } 1 { Format-Result $AuditingIssues '1' @@ -2938,6 +2987,7 @@ function Invoke-Locksmith { Format-Result $ESC5 '1' Format-Result $ESC6 '1' Format-Result $ESC8 '1' + Format-Result $ESC11 '1' } 2 { $Output = Join-Path -Path $OutputPath -ChildPath "$FilePrefix ADCSIssues.CSV" @@ -2962,7 +3012,7 @@ function Invoke-Locksmith { } } 4 { - Invoke-Remediation -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 + Invoke-Remediation -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 } } Write-Host 'Thank you for using ' -NoNewline diff --git a/Private/Export-RevertScript.ps1 b/Private/Export-RevertScript.ps1 index afffed9..6dc6e69 100644 --- a/Private/Export-RevertScript.ps1 +++ b/Private/Export-RevertScript.ps1 @@ -31,7 +31,7 @@ An array of ESC6 changes to be reverted. .EXAMPLE - Export-RevertScript -AuditingIssues $auditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 + Export-RevertScript -AuditingIssues $auditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 Reverts the changes performed by Locksmith using the specified arrays of objects. #> @@ -43,18 +43,19 @@ [array]$ESC3, [array]$ESC4, [array]$ESC5, - [array]$ESC6 + [array]$ESC6, + [array]$ESC11 ) begin { $Output = 'Invoke-RevertLocksmith.ps1' Set-Content -Path $Output -Value "<#`nScript to revert changes performed by Locksmith`nCreated $(Get-Date)`n#>" -Force - $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC11 } process { if ($Objects) { $Objects | ForEach-Object { Add-Content -Path $Output -Value $_.Revert - Start-Sleep -Seconds 5 + Start-Sleep -Seconds 1 } } } diff --git a/Private/Invoke-Remediation.ps1 b/Private/Invoke-Remediation.ps1 index b098ed9..6ce2f6b 100644 --- a/Private/Invoke-Remediation.ps1 +++ b/Private/Invoke-Remediation.ps1 @@ -27,6 +27,9 @@ function Invoke-Remediation { .PARAMETER ESC6 A PS Object containing all necessary information about ESC6 issues. + .PARAMETER ESC6 + A PS Object containing all necessary information about ESC11 issues. + .INPUTS PS Objects @@ -42,7 +45,8 @@ function Invoke-Remediation { $ESC3, $ESC4, $ESC5, - $ESC6 + $ESC6, + $ESC11 ) Write-Host "`nExecuting Mode 4 - Attempting to fix identified issues!`n" -ForegroundColor Green @@ -50,7 +54,7 @@ function Invoke-Remediation { Write-Host 'Invoke-RevertLocksmith.ps1' -ForegroundColor White -NoNewline Write-Host ") which can be used to revert all changes made by Locksmith...`n" try { - Export-RevertScript -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 + Export-RevertScript -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 } catch { Write-Warning 'Creation of Invoke-RevertLocksmith.ps1 failed.' Write-Host "Continue with this operation? [Y] Yes " -NoNewline @@ -253,6 +257,39 @@ function Invoke-Remediation { } } + if ($ESC11) { + $ESC11 | ForEach-Object { + $FixBlock = [scriptblock]::Create($_.Fix) + Write-Host 'ISSUE:' -ForegroundColor White + Write-Host "The Certification Authority `"$($_.Name)`" has the IF_ENFORCEENCRYPTICERTREQUEST flag disabled.`n" + Write-Host 'TECHNIQUE:' -ForegroundColor White + Write-Host "$($_.Technique)`n" + Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White + Write-Host "Locksmith will attempt to enable the IF_ENFORCEENCRYPTICERTREQUEST flag on Certifiction Authority `"$($_.Name)`".`n" + Write-Host 'COMMAND(S) TO BE RUN' -ForegroundColor White + Write-Host 'PS> ' -NoNewline + Write-Host "$($_.Fix)`n" -ForegroundColor Cyan + $WarningError = 'n' + Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White + Write-Host "WARNING: This change could cause some services to stop working.`n" -ForegroundColor Yellow + Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow + Write-Host "Continue with this operation? [Y] Yes " -NoNewline + Write-Host "[N] " -ForegroundColor Yellow -NoNewline + Write-Host "No: " -NoNewLine + $WarningError = '' + $WarningError = Read-Host + if ($WarningError -like 'y') { + try { + Invoke-Command -ScriptBlock $FixBlock + } catch { + Write-Error 'Could not enable the IF_ENFORCEENCRYPTICERTREQUEST flag. Are you an Active Directory or AD CS admin?' + } + } else { + Write-Host "SKIPPED!`n" -ForegroundColor Yellow + } + } + } + Write-Host "Mode 4 Complete! There are no more issues that Locksmith can automatically resolve.`n" -ForegroundColor Green Write-Host 'If you experience any operational impact from using Locksmith Mode 4, use ' -NoNewline Write-Host 'Invoke-RevertLocksmith.ps1 ' -ForegroundColor White diff --git a/Private/Invoke-Scans.ps1 b/Private/Invoke-Scans.ps1 index 75ed69e..e6063f2 100644 --- a/Private/Invoke-Scans.ps1 +++ b/Private/Invoke-Scans.ps1 @@ -13,16 +13,17 @@ function Invoke-Scans { - ESC5 - ESC6 - ESC8 + - ESC11 - All - PromptMe .PARAMETER Scans Specifies the type of scans to perform. Multiple scan options can be provided as an array. The default value is 'All'. - The available scan options are: 'Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'All', 'PromptMe'. + The available scan options are: 'Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'All', 'PromptMe'. .NOTES - The script requires the following functions to be defined: Find-AuditingIssue, Find-ESC1, Find-ESC2, Find-ESC3Condition1, - Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8. + Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8, Find-ESC8. - The script uses Out-GridView or Out-ConsoleGridView for interactive selection when the 'PromptMe' scan option is chosen. - The script returns a hash table containing the results of the scans. @@ -41,17 +42,17 @@ function Invoke-Scans { [CmdletBinding()] [OutputType([hashtable])] - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Invoke-Scans', Justification='Performing multiple scans.')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Invoke-Scans', Justification = 'Performing multiple scans.')] param ( - # Could split Scans and PromptMe into separate parameter sets. - [Parameter()] + # Could split Scans and PromptMe into separate parameter sets. + [Parameter()] $ClientAuthEkus, $DangerousRights, $EnrollmentAgentEKU, [int]$Mode, $SafeObjectTypes, $SafeOwners, - [ValidateSet('Auditing','ESC1','ESC2','ESC3','ESC4','ESC5','ESC6','ESC8','All','PromptMe')] + [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'All', 'PromptMe')] [array]$Scans = 'All', $UnsafeOwners, $UnsafeUsers, @@ -68,12 +69,10 @@ function Invoke-Scans { # Check for Out-GridView or Out-ConsoleGridView if ((Get-Command Out-ConsoleGridView -ErrorAction SilentlyContinue) -and ($PSVersionTable.PSVersion.Major -ge 7)) { - [array]$Scans = ($Dictionary | Select-Object Name,Category,Subcategory | Out-ConsoleGridView -OutputMode Multiple -Title $GridViewTitle).Name | Sort-Object -Property Name - } - elseif (Get-Command -Name Out-GridView -ErrorAction SilentlyContinue) { - [array]$Scans = ($Dictionary | Select-Object Name,Category,Subcategory | Out-GridView -PassThru -Title $GridViewTitle).Name | Sort-Object -Property Name - } - else { + [array]$Scans = ($Dictionary | Select-Object Name, Category, Subcategory | Out-ConsoleGridView -OutputMode Multiple -Title $GridViewTitle).Name | Sort-Object -Property Name + } elseif (Get-Command -Name Out-GridView -ErrorAction SilentlyContinue) { + [array]$Scans = ($Dictionary | Select-Object Name, Category, Subcategory | Out-GridView -PassThru -Title $GridViewTitle).Name | Sort-Object -Property Name + } else { # To Do: Check for admin and prompt to install features/modules or revert to 'All'. Write-Information "Out-GridView and Out-ConsoleGridView were not found on your system. Defaulting to `'All`'." $Scans = 'All' @@ -99,21 +98,25 @@ function Invoke-Scans { [array]$ESC3 += Find-ESC3Condition2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers } ESC4 { - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC4)...' + Write-Host 'Identifying AD CS templates with poor access control (ESC4)...' [array]$ESC4 = Find-ESC4 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeObjectTypes $SafeObjectTypes } ESC5 { - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC5)...' + Write-Host 'Identifying AD CS objects with poor access control (ESC5)...' [array]$ESC5 = Find-ESC5 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeObjectTypes $SafeObjectTypes } ESC6 { - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC6)...' + Write-Host 'Identifying Issuing CAs with EDITF_ATTRIBUTESUBJECTALTNAME2 enabled (ESC6)...' [array]$ESC6 = Find-ESC6 -ADCSObjects $ADCSObjects } ESC8 { Write-Host 'Identifying HTTP-based certificate enrollment interfaces (ESC8)...' [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects } + ESC6 { + Write-Host 'Identifying Issuing CAs with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' + [array]$ESC6 = Find-ESC6 -ADCSObjects $ADCSObjects + } All { Write-Host 'Identifying auditing issues...' [array]$AuditingIssues = Find-AuditingIssue -ADCSObjects $ADCSObjects @@ -124,35 +127,38 @@ function Invoke-Scans { Write-Host 'Identifying AD CS templates with dangerous ESC3 configurations...' [array]$ESC3 = Find-ESC3Condition1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers [array]$ESC3 += Find-ESC3Condition2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC4)...' + Write-Host 'Identifying AD CS templates with poor access control (ESC4)...' [array]$ESC4 = Find-ESC4 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeObjectTypes $SafeObjectTypes -Mode $Mode - Write-Host 'Identifying AD CS template and other objects with poor access control (ESC5)...' + Write-Host 'Identifying AD CS objects with poor access control (ESC5)...' [array]$ESC5 = Find-ESC5 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -DangerousRights $DangerousRights -SafeOwners $SafeOwners -SafeObjectTypes $SafeObjectTypes - Write-Host 'Identifying Certificate Authorities configured with dangerous flags (ESC6)...' + Write-Host 'Identifying Certificate Authorities with EDITF_ATTRIBUTESUBJECTALTNAME2 enabled v (ESC6)...' [array]$ESC6 = Find-ESC6 -ADCSObjects $ADCSObjects Write-Host 'Identifying HTTP-based certificate enrollment interfaces (ESC8)...' [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects + Write-Host 'Identifying Certificate Authorities with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' + [array]$ESC11 = Find-ESC11 -ADCSObjects $ADCSObjects } } - [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + $ESC11 # If these are all empty = no issues found, exit - if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) ) { + if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) -and ($ESC11) ) { Write-Host "`n$(Get-Date) : No ADCS issues were found." -ForegroundColor Green break } # Return a hash table of array names (keys) and arrays (values) so they can be directly referenced with other functions Return @{ - AllIssues = $AllIssues + AllIssues = $AllIssues AuditingIssues = $AuditingIssues - ESC1 = $ESC1 - ESC2 = $ESC2 - ESC3 = $ESC3 - ESC4 = $ESC4 - ESC5 = $ESC5 - ESC6 = $ESC6 - ESC8 = $ESC8 + ESC1 = $ESC1 + ESC2 = $ESC2 + ESC3 = $ESC3 + ESC4 = $ESC4 + ESC5 = $ESC5 + ESC6 = $ESC6 + ESC8 = $ESC8 + ESC11 = $ESC11 } } diff --git a/Public/Invoke-Locksmith.ps1 b/Public/Invoke-Locksmith.ps1 index 3882deb..ed38c0a 100644 --- a/Public/Invoke-Locksmith.ps1 +++ b/Public/Invoke-Locksmith.ps1 @@ -71,18 +71,18 @@ # The mode to run Locksmith in. Defaults to 0. [Parameter()] - [ValidateSet(0,1,2,3,4)] - [int]$Mode = 0, + [ValidateSet(0, 1, 2, 3, 4)] + [int]$Mode = 0, # The scans to run. Defaults to 'All'. [Parameter()] - [ValidateSet('Auditing','ESC1','ESC2','ESC3','ESC4','ESC5','ESC6','ESC8','All','PromptMe')] - [array]$Scans = 'All', - + [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'All', 'PromptMe')] + [array]$Scans = 'All', + # The directory to save the output in (defaults to the current working directory). [Parameter()] - [ValidateScript({Test-Path -Path $_ -PathType Container})] - [string]$OutputPath = $PWD, + [ValidateScript({ Test-Path -Path $_ -PathType Container })] + [string]$OutputPath = $PWD, # The credential to use for working with ADCS. [Parameter()] @@ -193,7 +193,7 @@ -517 = Cert Publishers -512 = Domain Admins group #> - $SafeGroupRIDs = @('-517','-512') + $SafeGroupRIDs = @('-517', '-512') # Administrators group $SafeGroupSIDs = @('S-1-5-32-544') @@ -207,7 +207,7 @@ $SafeUsers += '|' + $user } } - $SafeUsers = $SafeUsers.Replace('||','|') + $SafeUsers = $SafeUsers.Replace('||', '|') if ($Credential) { $Targets = Get-Target -Credential $Credential @@ -232,31 +232,31 @@ $CAHosts | ForEach-Object { $SafeUsers += '|' + $_.objectSid } #if ( $Scans ) { - # If the Scans parameter was used, Invoke-Scans with the specified checks. - $ScansParameters = @{ - ClientAuthEkus = $ClientAuthEKUs - DangerousRights = $DangerousRights - EnrollmentAgentEKU = $EnrollmentAgentEKU - Mode = $Mode - SafeObjectTypes = $SafeObjectTypes - SafeOwners = $SafeOwners - Scans = $Scans - UnsafeOwners = $UnsafeOwners - UnsafeUsers = $UnsafeUsers - PreferredOwner = $PreferredOwner - } - $Results = Invoke-Scans @ScansParameters - # Re-hydrate the findings arrays from the Results hash table - $AllIssues = $Results['AllIssues'] - $AuditingIssues = $Results['AuditingIssues'] - $ESC1 = $Results['ESC1'] - $ESC2 = $Results['ESC2'] - $ESC3 = $Results['ESC3'] - $ESC4 = $Results['ESC4'] - $ESC5 = $Results['ESC5'] - $ESC6 = $Results['ESC6'] - $ESC8 = $Results['ESC8'] - #} + # If the Scans parameter was used, Invoke-Scans with the specified checks. + $ScansParameters = @{ + ClientAuthEkus = $ClientAuthEKUs + DangerousRights = $DangerousRights + EnrollmentAgentEKU = $EnrollmentAgentEKU + Mode = $Mode + SafeObjectTypes = $SafeObjectTypes + SafeOwners = $SafeOwners + Scans = $Scans + UnsafeOwners = $UnsafeOwners + UnsafeUsers = $UnsafeUsers + PreferredOwner = $PreferredOwner + } + $Results = Invoke-Scans @ScansParameters + # Re-hydrate the findings arrays from the Results hash table + $AllIssues = $Results['AllIssues'] + $AuditingIssues = $Results['AuditingIssues'] + $ESC1 = $Results['ESC1'] + $ESC2 = $Results['ESC2'] + $ESC3 = $Results['ESC3'] + $ESC4 = $Results['ESC4'] + $ESC5 = $Results['ESC5'] + $ESC6 = $Results['ESC6'] + $ESC8 = $Results['ESC8'] + $ESC11 = $Results['ESC11'] # If these are all empty = no issues found, exit if ($null -eq $Results) { @@ -276,6 +276,7 @@ Format-Result $ESC5 '0' Format-Result $ESC6 '0' Format-Result $ESC8 '0' + Format-Result $ESC11 '0' } 1 { Format-Result $AuditingIssues '1' @@ -286,6 +287,7 @@ Format-Result $ESC5 '1' Format-Result $ESC6 '1' Format-Result $ESC8 '1' + Format-Result $ESC11 '1' } 2 { $Output = Join-Path -Path $OutputPath -ChildPath "$FilePrefix ADCSIssues.CSV" @@ -308,7 +310,7 @@ } } 4 { - Invoke-Remediation -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 + Invoke-Remediation -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 } } Write-Host 'Thank you for using ' -NoNewline From d0085242691959584f9ceafcc9c2add8dfa0d44b Mon Sep 17 00:00:00 2001 From: TrimarcJake Date: Fri, 25 Oct 2024 20:46:18 -0500 Subject: [PATCH 05/14] Cleanup, consistency, and speeding up Export-RevertScript --- Invoke-Locksmith.ps1 | 50 +++++++++++++++++++++------------ Private/Export-RevertScript.ps1 | 10 +++++-- Private/Find-ESC11.ps1 | 8 +++--- Private/New-Dictionary.ps1 | 18 ++++++------ Public/Invoke-Locksmith.ps1 | 14 +++++++-- 5 files changed, 64 insertions(+), 36 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 714819e..32dc1f2 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -74,6 +74,9 @@ function Export-RevertScript { .PARAMETER ESC6 An array of ESC6 changes to be reverted. + .PARAMETER ESC11 + An array of ESC11 changes to be reverted. + .EXAMPLE Export-RevertScript -AuditingIssues $auditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 Reverts the changes performed by Locksmith using the specified arrays of objects. @@ -92,15 +95,16 @@ function Export-RevertScript { ) begin { $Output = 'Invoke-RevertLocksmith.ps1' - Set-Content -Path $Output -Value "<#`nScript to revert changes performed by Locksmith`nCreated $(Get-Date)`n#>" -Force + $RevertScript = [System.Text.StringBuilder]::New() + [void]$RevertScript.Append("<#`nScript to revert changes performed by Locksmith`nCreated $(Get-Date)`n#>`n") $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC11 } process { if ($Objects) { $Objects | ForEach-Object { - Add-Content -Path $Output -Value $_.Revert - Start-Sleep -Seconds 1 + [void]$RevertScript.Append("$($_.Revert)`n") } + $RevertScript.ToString() | Out-File -FilePath $Output } } } @@ -249,11 +253,11 @@ Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = function Find-ESC11 { <# .SYNOPSIS - This script finds AD CS (Active Directory Certificate Services) objects that have the ESC6 vulnerability. + This script finds AD CS (Active Directory Certificate Services) objects that have the ESC11 vulnerability. .DESCRIPTION The script takes an array of ADCS objects as input and filters them based on objects that have the objectClass - 'pKIEnrollmentService' and the SANFlag set to 'Yes'. For each matching object, it creates a custom object with + 'pKIEnrollmentService' and the InterfaceFlag set to 'No'. For each matching object, it creates a custom object with properties representing various information about the object, such as Forest, Name, DistinguishedName, Technique, Issue, Fix, and Revert. @@ -264,8 +268,8 @@ function Find-ESC11 { The script outputs an array of custom objects representing the matching ADCS objects and their associated information. .EXAMPLE - $ADCSObjects = Get-ADCSObjects - $Results = $ADCSObjects | Find-ESC6 + $ADCSObjects = Get-ADCSObject -Target (Get-Target) + Find-ESC11 -ADCSObjects $ADCSObjects $Results #> [CmdletBinding()] @@ -1994,15 +1998,15 @@ function New-Dictionary { # FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} # ReferenceUrls = '' # }, - # [VulnerableConfigurationItem]@{ - # Name = 'ESC11' - # Category = 'Escalation Path' - # Subcategory = '' - # Summary = '' - # FindIt = {Find-ESC11} - # FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} - # ReferenceUrls = '' - # }, + [VulnerableConfigurationItem]@{ + Name = 'ESC11' + Category = 'Escalation Path' + Subcategory = 'IF_ENFORCEENCRYPTICERTREQUEST' + Summary = '' + FindIt = { Find-ESC11 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } + ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' + }, [VulnerableConfigurationItem]@{ Name = 'Auditing' Category = 'Server Configuration' @@ -3012,11 +3016,21 @@ function Invoke-Locksmith { } } 4 { - Invoke-Remediation -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 + $params = @{ + AuditingIssues = $AuditingIssues + ESC1 = $ESC1 + ESC2 = $ESC2 + ESC3 = $ESC3 + ESC4 = $ESC4 + ESC5 = $ESC5 + ESC6 = $ESC6 + ESC11 = $ESC11 + } + Invoke-Remediation @params } } Write-Host 'Thank you for using ' -NoNewline - Write-Host "❤ Locksmith ❤`n" -ForegroundColor Magenta + Write-Host "Locksmith ❤`n" -ForegroundColor Magenta } diff --git a/Private/Export-RevertScript.ps1 b/Private/Export-RevertScript.ps1 index 6dc6e69..a6d3dae 100644 --- a/Private/Export-RevertScript.ps1 +++ b/Private/Export-RevertScript.ps1 @@ -30,6 +30,9 @@ .PARAMETER ESC6 An array of ESC6 changes to be reverted. + .PARAMETER ESC11 + An array of ESC11 changes to be reverted. + .EXAMPLE Export-RevertScript -AuditingIssues $auditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 Reverts the changes performed by Locksmith using the specified arrays of objects. @@ -48,15 +51,16 @@ ) begin { $Output = 'Invoke-RevertLocksmith.ps1' - Set-Content -Path $Output -Value "<#`nScript to revert changes performed by Locksmith`nCreated $(Get-Date)`n#>" -Force + $RevertScript = [System.Text.StringBuilder]::New() + [void]$RevertScript.Append("<#`nScript to revert changes performed by Locksmith`nCreated $(Get-Date)`n#>`n") $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC11 } process { if ($Objects) { $Objects | ForEach-Object { - Add-Content -Path $Output -Value $_.Revert - Start-Sleep -Seconds 1 + [void]$RevertScript.Append("$($_.Revert)`n") } + $RevertScript.ToString() | Out-File -FilePath $Output } } } diff --git a/Private/Find-ESC11.ps1 b/Private/Find-ESC11.ps1 index 81a3196..87f4e9d 100644 --- a/Private/Find-ESC11.ps1 +++ b/Private/Find-ESC11.ps1 @@ -1,11 +1,11 @@ function Find-ESC11 { <# .SYNOPSIS - This script finds AD CS (Active Directory Certificate Services) objects that have the ESC6 vulnerability. + This script finds AD CS (Active Directory Certificate Services) objects that have the ESC11 vulnerability. .DESCRIPTION The script takes an array of ADCS objects as input and filters them based on objects that have the objectClass - 'pKIEnrollmentService' and the SANFlag set to 'Yes'. For each matching object, it creates a custom object with + 'pKIEnrollmentService' and the InterfaceFlag set to 'No'. For each matching object, it creates a custom object with properties representing various information about the object, such as Forest, Name, DistinguishedName, Technique, Issue, Fix, and Revert. @@ -16,8 +16,8 @@ The script outputs an array of custom objects representing the matching ADCS objects and their associated information. .EXAMPLE - $ADCSObjects = Get-ADCSObjects - $Results = $ADCSObjects | Find-ESC6 + $ADCSObjects = Get-ADCSObject -Target (Get-Target) + Find-ESC11 -ADCSObjects $ADCSObjects $Results #> [CmdletBinding()] diff --git a/Private/New-Dictionary.ps1 b/Private/New-Dictionary.ps1 index 9177062..8b9f453 100644 --- a/Private/New-Dictionary.ps1 +++ b/Private/New-Dictionary.ps1 @@ -125,15 +125,15 @@ function New-Dictionary { # FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} # ReferenceUrls = '' # }, - # [VulnerableConfigurationItem]@{ - # Name = 'ESC11' - # Category = 'Escalation Path' - # Subcategory = '' - # Summary = '' - # FindIt = {Find-ESC11} - # FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} - # ReferenceUrls = '' - # }, + [VulnerableConfigurationItem]@{ + Name = 'ESC11' + Category = 'Escalation Path' + Subcategory = 'IF_ENFORCEENCRYPTICERTREQUEST' + Summary = '' + FindIt = {Find-ESC11} + FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' + }, [VulnerableConfigurationItem]@{ Name = 'Auditing' Category = 'Server Configuration' diff --git a/Public/Invoke-Locksmith.ps1 b/Public/Invoke-Locksmith.ps1 index ed38c0a..46ccaf3 100644 --- a/Public/Invoke-Locksmith.ps1 +++ b/Public/Invoke-Locksmith.ps1 @@ -310,9 +310,19 @@ } } 4 { - Invoke-Remediation -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 + $params = @{ + AuditingIssues = $AuditingIssues + ESC1 = $ESC1 + ESC2 = $ESC2 + ESC3 = $ESC3 + ESC4 = $ESC4 + ESC5 = $ESC5 + ESC6 = $ESC6 + ESC11 = $ESC11 + } + Invoke-Remediation @params } } Write-Host 'Thank you for using ' -NoNewline - Write-Host "❤ Locksmith ❤`n" -ForegroundColor Magenta + Write-Host "Locksmith ❤`n" -ForegroundColor Magenta } From 1a3847374891c3621ce158af644b64373a6d281b Mon Sep 17 00:00:00 2001 From: TrimarcJake Date: Sat, 26 Oct 2024 05:24:29 -0500 Subject: [PATCH 06/14] Added logic to prevent custom C# tye from being added twice --- Invoke-Locksmith.ps1 | 87 ++++++++++++++++++++-------- Private/Set-AdditionalCAProperty.ps1 | 44 +++++++------- 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 5c51079..a52f16a 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -1037,7 +1037,7 @@ function Format-Result { ESC1 = 'ESC1 - Vulnerable Certificate Template - Authentication' ESC2 = 'ESC2 - Vulnerable Certificate Template - Subordinate CA' ESC3 = 'ESC3 - Vulnerable Certificate Template - Enrollment Agent' - ESC4 = 'ESC4 - Vulnerable Access Control - Certifcate Template' + ESC4 = 'ESC4 - Vulnerable Access Control - Certificate Template' ESC5 = 'ESC5 - Vulnerable Access Control - PKI Object' ESC6 = 'ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2 Flag Enabled' ESC8 = 'ESC8 - HTTP/S Enrollment Enabled' @@ -1975,32 +1975,34 @@ function Set-AdditionalCAProperty { begin { $CAEnrollmentEndpoint = @() - if ($PSVersionTable.PSEdition -eq 'Desktop') { - $code = @" - using System.Net; - using System.Security.Cryptography.X509Certificates; - public class TrustAllCertsPolicy : ICertificatePolicy { - public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { - return true; + if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy') ) { + if ($PSVersionTable.PSEdition -eq 'Desktop') { + $code = @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + public class TrustAllCertsPolicy : ICertificatePolicy { + public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { + return true; + } } - } "@ - Add-Type -TypeDefinition $code -Language CSharp - [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy - } - else { - Add-Type @" - using System.Net; - using System.Security.Cryptography.X509Certificates; - using System.Net.Security; - public class TrustAllCertsPolicy { - public static bool TrustAllCerts(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { - return true; + Add-Type -TypeDefinition $code -Language CSharp + [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy + } + else { + Add-Type @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + using System.Net.Security; + public class TrustAllCertsPolicy { + public static bool TrustAllCerts(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { + return true; + } } - } "@ - # Set the ServerCertificateValidationCallback - [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [TrustAllCertsPolicy]::TrustAllCerts + # Set the ServerCertificateValidationCallback + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [TrustAllCertsPolicy]::TrustAllCerts + } } } @@ -2163,6 +2165,44 @@ function Set-Severity { } } +function Show-LocksmithLogo { + Write-Host '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%%%%%%%#+==============#%%%%%%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%%%%#=====================#%%%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%%#=========================#%%%%%%%%%%%%' + Write-Host '%%%%%%%%%%%=============================%%%%%%%%%%%' + Write-Host '%%%%%%%%%#==============+++==============#%%%%%%%%%' + Write-Host '%%%%%%%%#===========#%%%%%%%%%#===========#%%%%%%%%' + Write-Host '%%%%%%%%==========%%%%%%%%%%%%%%%==========%%%%%%%%' + Write-Host '%%%%%%%*=========%%%%%%%%%%%%%%%%%=========*%%%%%%%' + Write-Host '%%%%%%%+========*%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%+========#%%%%%%%%%%%%%%%%%#=========%%%%%%%' + Write-Host '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' + Write-Host '#=================================================#' + Write-Host '#=================================================#' + Write-Host '#=================+%%%============================#' + Write-Host '#==================%%%%*==========================#' + Write-Host '#===================*%%%%+========================#' + Write-Host '#=====================#%%%%=======================#' + Write-Host '#======================+%%%%#=====================#' + Write-Host '#========================*%%%%*===================#' + Write-Host '#========================+%%%%%===================#' + Write-Host '#======================#%%%%%+====================#' + Write-Host '#===================+%%%%%%=======================#' + Write-Host '#=================#%%%%%+=========================#' + Write-Host '#==============+%%%%%#============================#' + Write-Host '#============*%%%%%+====+%%%%%%%%%%===============#' + Write-Host '#=============%%*========+********+===============#' + Write-Host '#=================================================#' + Write-Host '#=================================================#' + Write-Host '#=================================================#' +} + function Test-IsADAdmin { <# .SYNOPSIS @@ -2902,5 +2942,4 @@ function Invoke-Locksmith { } -# Export functions and aliases as required Invoke-Locksmith -Mode $Mode -Scans $Scans diff --git a/Private/Set-AdditionalCAProperty.ps1 b/Private/Set-AdditionalCAProperty.ps1 index 461ba51..f448eee 100644 --- a/Private/Set-AdditionalCAProperty.ps1 +++ b/Private/Set-AdditionalCAProperty.ps1 @@ -37,31 +37,33 @@ begin { $CAEnrollmentEndpoint = @() - if ($PSVersionTable.PSEdition -eq 'Desktop') { - $code= @" - using System.Net; - using System.Security.Cryptography.X509Certificates; - public class TrustAllCertsPolicy : ICertificatePolicy { - public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { - return true; + if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy') ) { + if ($PSVersionTable.PSEdition -eq 'Desktop') { + $code= @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + public class TrustAllCertsPolicy : ICertificatePolicy { + public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { + return true; + } } - } "@ - Add-Type -TypeDefinition $code -Language CSharp - [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy - } else { - Add-Type @" - using System.Net; - using System.Security.Cryptography.X509Certificates; - using System.Net.Security; - public class TrustAllCertsPolicy { - public static bool TrustAllCerts(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { - return true; + Add-Type -TypeDefinition $code -Language CSharp + [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy + } else { + Add-Type @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + using System.Net.Security; + public class TrustAllCertsPolicy { + public static bool TrustAllCerts(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { + return true; + } } - } "@ - # Set the ServerCertificateValidationCallback - [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [TrustAllCertsPolicy]::TrustAllCerts + # Set the ServerCertificateValidationCallback + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [TrustAllCertsPolicy]::TrustAllCerts + } } } From f184d8a3d782279f98d401e597553070d28b6c58 Mon Sep 17 00:00:00 2001 From: TrimarcJake Date: Sat, 26 Oct 2024 07:08:24 -0500 Subject: [PATCH 07/14] Improved formatting. Updated Invoke-TSS to support ESC11. --- Invoke-Locksmith.ps1 | 9 +- Private/Format-Result.ps1 | 9 +- Private/Invoke-Scans.ps1 | 1 + Tests/Invoke-TSS.ps1 | 193 +++++++++++++++++++++----------------- 4 files changed, 123 insertions(+), 89 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 32dc1f2..6893df7 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -1108,11 +1108,18 @@ function Format-Result { ESC5 = 'ESC5 - Vulnerable Access Control - PKI Object' ESC6 = 'ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2 Flag Enabled' ESC8 = 'ESC8 - HTTP/S Enrollment Enabled' + ESC11 = 'ESC11 - IF_ENFORCEENCRYPTICERTREQUEST Flag Disabled' } if ($null -ne $Issue) { $UniqueIssue = $Issue.Technique | Sort-Object -Unique - Write-Host "`n########## $($IssueTable[$UniqueIssue]) ##########`n" + $Title = $($IssueTable[$UniqueIssue]) + Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForegroundColor Magenta -BackgroundColor Magenta -NoNewline; Write-Host + Write-Host " " -BackgroundColor Magenta -NoNewline + Write-Host $Title -BackgroundColor Magenta -ForegroundColor White -NoNewline + Write-Host " " -BackgroundColor Magenta -NoNewline; Write-Host + Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForegroundColor Magenta -BackgroundColor Magenta -NoNewline; Write-Host + switch ($Mode) { 0 { $Issue | Format-Table Technique, Name, Issue -Wrap diff --git a/Private/Format-Result.ps1 b/Private/Format-Result.ps1 index 7104e27..7e2eaa7 100644 --- a/Private/Format-Result.ps1 +++ b/Private/Format-Result.ps1 @@ -39,11 +39,18 @@ function Format-Result { ESC5 = 'ESC5 - Vulnerable Access Control - PKI Object' ESC6 = 'ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2 Flag Enabled' ESC8 = 'ESC8 - HTTP/S Enrollment Enabled' + ESC11 = 'ESC11 - IF_ENFORCEENCRYPTICERTREQUEST Flag Disabled' } if ($null -ne $Issue) { $UniqueIssue = $Issue.Technique | Sort-Object -Unique - Write-Host "`n########## $($IssueTable[$UniqueIssue]) ##########`n" + $Title = $($IssueTable[$UniqueIssue]) + Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForeGroundColor Magenta -BackgroundColor Magenta -NoNewline; Write-Host + Write-Host " " -BackgroundColor Magenta -NoNewline + Write-Host $Title -BackgroundColor Magenta -ForeGroundColor White -NoNewline + Write-Host " " -BackgroundColor Magenta -NoNewline; Write-Host + Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForeGroundColor Magenta -BackgroundColor Magenta -NoNewline; Write-Host + switch ($Mode) { 0 { $Issue | Format-Table Technique, Name, Issue -Wrap diff --git a/Private/Invoke-Scans.ps1 b/Private/Invoke-Scans.ps1 index e6063f2..a70125e 100644 --- a/Private/Invoke-Scans.ps1 +++ b/Private/Invoke-Scans.ps1 @@ -137,6 +137,7 @@ function Invoke-Scans { [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects Write-Host 'Identifying Certificate Authorities with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' [array]$ESC11 = Find-ESC11 -ADCSObjects $ADCSObjects + Write-Host } } diff --git a/Tests/Invoke-TSS.ps1 b/Tests/Invoke-TSS.ps1 index 0c94fce..4f7fad8 100644 --- a/Tests/Invoke-TSS.ps1 +++ b/Tests/Invoke-TSS.ps1 @@ -17,51 +17,51 @@ #requires -Modules ActiveDirectory Write-Output @" - _______ _ _ _ + _______ _ _ _ |__ __| | | (_) | | | | __ _ ___| |_ _ ___ __ _| | | |/ _`` |/ __| __| |/ __/ _`` | | | | (_| | (__| |_| | (_| (_| | | _|_|\__,_|\___|\__|_|\___\__,_|_| - / ____| | | -| (___ _ __ ___ ___ __| | - \___ \| `'_ \ / _ \/ _ \/ _`` | - ____) | |_) | __/ __/ (_| | -|_____/| .__/ \___|\___|\__,_| - / ____| | -| (___ |_|_ _ _ _ __ _ _ __ ___ + / ____| | | +| (___ _ __ ___ ___ __| | + \___ \| `'_ \ / _ \/ _ \/ _`` | + ____) | |_) | __/ __/ (_| | +|_____/| .__/ \___|\___|\__,_| + / ____| | +| (___ |_|_ _ _ _ __ _ _ __ ___ \___ \ / _`` | | | |/ _`` | `'__/ _ \ ____) | (_| | |_| | (_| | | | __/ |_____/ \__, |\__,_|\__,_|_| \___| - | | - |_| - The UnLocksmith + | | + |_| + The UnLocksmith "@ $NewTemplates = @( 'ESC1and2AutoEnroll' 'ESC1and2Enroll' 'ESC1and2FilteredAutoEnroll' - 'ESC1and2FilteredEnroll' + 'ESC1and2FilteredEnroll' 'ESC1AutoEnroll' 'ESC1Enroll' - 'ESC1FilteredAutoEnroll' - 'ESC1FilteredEnroll' - 'ESC2AutoEnroll' + 'ESC1FilteredAutoEnroll' + 'ESC1FilteredEnroll' + 'ESC2AutoEnroll' 'ESC2Enroll' - 'ESC2FilteredAutoEnroll' + 'ESC2FilteredAutoEnroll' 'ESC2FilteredEnroll' 'ESC3Condition1' 'ESC3Condition2Schema1' 'ESC3Condition2Schema2' - 'ESC4FilteredAutoEnroll' - 'ESC4FilteredEnroll' + 'ESC4FilteredAutoEnroll' + 'ESC4FilteredEnroll' 'ESC4FilteredOwner' 'ESC4FilteredSafeUsers' 'ESC4GenericAll' 'ESC4UnsafeOwner' 'ESC4WriteProperty' - 'ESC4WriteOwner' + 'ESC4WriteOwner' ) $NewObjects = @( @@ -104,166 +104,166 @@ $NewObjects | ForEach-Object { $ESC1and2AutoEnroll = Get-ADObject "CN=ESC1and2AutoEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC1and2AutoEnrollProperties = @{ - 'DisplayName' = 'ESC1and2AutoEnroll' + 'DisplayName' = 'ESC1and2AutoEnroll' 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 - 'pKIExtendedKeyUsage' = '2.5.29.37.0' -} + 'msPKI-Enrollment-Flag' = 0 + 'pKIExtendedKeyUsage' = '2.5.29.37.0' +} Set-ADObject $ESC1and2AutoEnroll.DistinguishedName -Add $ESC1and2AutoEnrollProperties $ACL = Get-Acl "AD:$ESC1and2AutoEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$AutoEnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $AutoEnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC1and2AutoEnroll" -AclObject $ACL $ESC1and2Enroll = Get-ADObject "CN=ESC1and2Enroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC1and2EnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 - 'pKIExtendedKeyUsage' = '2.5.29.37.0' -} + 'msPKI-Enrollment-Flag' = 0 + 'pKIExtendedKeyUsage' = '2.5.29.37.0' +} Set-ADObject $ESC1and2Enroll.DistinguishedName -Add $ESC1and2EnrollProperties $ACL = Get-Acl "AD:$ESC1and2Enroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC1and2Enroll" -AclObject $ACL $ESC1and2FilteredAutoEnroll = Get-ADObject "CN=ESC1and2FilteredAutoEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC1and2FilteredAutoEnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 - 'pKIExtendedKeyUsage' = '2.5.29.37.0' -} + 'msPKI-Enrollment-Flag' = 0 + 'pKIExtendedKeyUsage' = '2.5.29.37.0' +} Set-ADObject $ESC1and2FilteredAutoEnroll.DistinguishedName -Add $ESC1and2FilteredAutoEnrollProperties $ACL = Get-Acl "AD:$ESC1and2FilteredAutoEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators,$DefaultRights,$Allow,$AutoEnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators, $DefaultRights, $Allow, $AutoEnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC1and2FilteredAutoEnroll" -AclObject $ACL $ESC1and2FilteredEnroll = Get-ADObject "CN=ESC1and2FilteredEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC1and2FilteredEnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 - 'pKIExtendedKeyUsage' = '2.5.29.37.0' -} + 'msPKI-Enrollment-Flag' = 0 + 'pKIExtendedKeyUsage' = '2.5.29.37.0' +} Set-ADObject $ESC1and2FilteredEnroll.DistinguishedName -Add $ESC1and2FilteredEnrollProperties $ACL = Get-Acl "AD:$ESC1and2FilteredEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC1and2FilteredEnroll" -AclObject $ACL $ESC1AutoEnroll = Get-ADObject "CN=ESC1AutoEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC1AutoEnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 - 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2' -} + 'msPKI-Enrollment-Flag' = 0 + 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2' +} Set-ADObject $ESC1AutoEnroll.DistinguishedName -Add $ESC1AutoEnrollProperties $ACL = Get-Acl "AD:$ESC1AutoEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$AutoEnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $AutoEnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC1AutoEnroll" -AclObject $ACL $ESC1Enroll = Get-ADObject "CN=ESC1Enroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC1EnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 - 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2' -} + 'msPKI-Enrollment-Flag' = 0 + 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2' +} Set-ADObject $ESC1Enroll.DistinguishedName -Add $ESC1EnrollProperties $ACL = Get-Acl "AD:$ESC1Enroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC1Enroll" -AclObject $ACL $ESC1FilteredAutoEnroll = Get-ADObject "CN=ESC1FilteredAutoEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC1FilteredAutoEnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 - 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2' -} + 'msPKI-Enrollment-Flag' = 0 + 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2' +} Set-ADObject $ESC1FilteredAutoEnroll.DistinguishedName -Add $ESC1FilteredAutoEnrollProperties $ACL = Get-Acl "AD:$ESC1FilteredAutoEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators,$DefaultRights,$Allow,$AutoEnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators, $DefaultRights, $Allow, $AutoEnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC1FilteredAutoEnroll" -AclObject $ACL $ESC1FilteredEnroll = Get-ADObject "CN=ESC1FilteredEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC1FilteredEnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 - 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2' -} + 'msPKI-Enrollment-Flag' = 0 + 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2' +} Set-ADObject $ESC1FilteredEnroll.DistinguishedName -Add $ESC1FilteredEnrollProperties $ACL = Get-Acl "AD:$ESC1FilteredEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC1FilteredEnroll" -AclObject $ACL $ESC2AutoEnroll = Get-ADObject "CN=ESC2AutoEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC2AutoEnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 -} + 'msPKI-Enrollment-Flag' = 0 +} Set-ADObject $ESC2AutoEnroll.DistinguishedName -Add $ESC2AutoEnrollProperties Set-ADObject $ESC2AutoEnroll.DistinguishedName -Clear pKIExtendedKeyUsage $ACL = Get-Acl "AD:$ESC2AutoEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$AutoEnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $AutoEnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC2AutoEnroll" -AclObject $ACL $ESC2Enroll = Get-ADObject "CN=ESC2Enroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC2EnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 -} + 'msPKI-Enrollment-Flag' = 0 +} Set-ADObject $ESC2Enroll.DistinguishedName -Add $ESC2EnrollProperties Set-ADObject $ESC2Enroll.DistinguishedName -Clear pKIExtendedKeyUsage $ACL = Get-Acl "AD:$ESC2Enroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC2Enroll" -AclObject $ACL $ESC2FilteredAutoEnroll = Get-ADObject "CN=ESC2FilteredAutoEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC2FilteredAutoEnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 -} + 'msPKI-Enrollment-Flag' = 0 +} Set-ADObject $ESC2FilteredAutoEnroll.DistinguishedName -Add $ESC2FilteredAutoEnrollProperties Set-ADObject $ESC2FilteredAutoEnroll.DistinguishedName -Clear pKIExtendedKeyUsage $ACL = Get-Acl "AD:$ESC2FilteredAutoEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators,$DefaultRights,$Allow,$AutoEnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators, $DefaultRights, $Allow, $AutoEnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC2FilteredAutoEnroll" -AclObject $ACL $ESC2FilteredEnroll = Get-ADObject "CN=ESC2FilteredEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ESC2FilteredEnrollProperties = @{ 'msPKI-Certificate-Name-Flag' = 1 - 'msPKI-Enrollment-Flag' = 0 -} + 'msPKI-Enrollment-Flag' = 0 +} Set-ADObject $ESC2FilteredEnroll.DistinguishedName -Add $ESC2FilteredEnrollProperties Set-ADObject $ESC2FilteredEnroll.DistinguishedName -Clear pKIExtendedKeyUsage $ACL = Get-Acl "AD:$ESC2FilteredEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC2FilteredEnroll" -AclObject $ACL $ESC3Condition1 = Get-ADObject "CN=ESC3Condition1,CN=Certificate Templates,$PKSContainer" -Properties * $ESC3Condition1Properties = @{ - 'DisplayName' = 'ESC3Condition1' - 'msPKI-Enrollment-Flag' = 0 + 'DisplayName' = 'ESC3Condition1' + 'msPKI-Enrollment-Flag' = 0 'msPKI-Certificate-Application-Policy' = '1.3.6.1.4.1.311.20.2.1' - 'msPKI-Certificate-Name-Flag' = -2113929216 + 'msPKI-Certificate-Name-Flag' = -2113929216 # 'msPKI-Cert-Template-OID' = '1.3.6.1.4.1.311.21.8.11772860.15111666.14435736.6562275.12440657.32.7694220.3484220' #'msPKI-Minimal-Key-Size' = 2048 #'msPKI-Private-Key-Flag' = 16842752 - 'msPKI-RA-Signature' = 0 + 'msPKI-RA-Signature' = 0 # 'msPKI-Template-Minor-Revision' = 7 - 'msPKI-Template-Schema-Version' = 2 - 'pKIExtendedKeyUsage' = '1.3.6.1.4.1.311.20.2.1' -} + 'msPKI-Template-Schema-Version' = 2 + 'pKIExtendedKeyUsage' = '1.3.6.1.4.1.311.20.2.1' +} Set-ADObject $ESC3Condition1.DistinguishedName -Add $ESC3Condition1Properties $ACL = Get-Acl "AD:$ESC3Condition1" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC3Condition1" -AclObject $ACL @@ -273,7 +273,7 @@ Set-Acl "AD:$ESC3Condition1" -AclObject $ACL # 'msPKI-Enrollment-Flag' = 0 # 'msPKI-RA-Signature' = 1 # # 'msPKI-Certificate-Application-Policy' = '' -# } +# } # Set-ADObject $ESC3Condition2Schema1.DistinguishedName -Add $ESC3Condition2Schema1Properties # Set-ADObject $ESC3Condition2Schema1.DistinguishedName -Clear pKIExtendedKeyUsage # $ACL = Get-Acl "AD:$ESC3Condition2Schema1" @@ -300,7 +300,7 @@ Set-Acl "AD:$ESC3Condition1" -AclObject $ACL # 'msPKI-Private-Key-Flag' = 16842768 # 'msPKI-Template-Minor-Revision' = 18 # 'msPKI-Template-Schema-Version' = 2 -# } +# } # Set-ADObject $ESC3Condition2Schema2.DistinguishedName -Add $ESC3Condition2Schema2Properties # Set-ADObject $ESC3Condition2Schema2.DistinguishedName -Clear pKIExtendedKeyUsage # $ACL = Get-Acl "AD:$ESC3Condition2Schema2" @@ -310,13 +310,13 @@ Set-Acl "AD:$ESC3Condition1" -AclObject $ACL $ESC4FilteredAutoEnroll = Get-ADObject "CN=ESC4FilteredAutoEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC4FilteredAutoEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$AutoEnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $AutoEnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC4FilteredAutoEnroll" -AclObject $ACL $ESC4FilteredEnroll = Get-ADObject "CN=ESC4FilteredEnroll,CN=Certificate Templates,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC4FilteredEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC4FilteredEnroll" -AclObject $ACL @@ -327,13 +327,13 @@ Set-Acl "AD:$ESC4FilteredOwner" -AclObject $ACL $ESC4FilteredSafeUsers = Get-ADObject "CN=ESC4FilteredSafeUsers,CN=Certificate Templates,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC4FilteredSafeUsers" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators,$GenericAll,$Allow +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators, $GenericAll, $Allow $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC4FilteredSafeUsers" -AclObject $ACL $ESC4GenericAll = Get-ADObject "CN=ESC4GenericAll,CN=Certificate Templates,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC4GenericAll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$GenericAll,$Allow +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $GenericAll, $Allow $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC4GenericAll" -AclObject $ACL @@ -344,25 +344,25 @@ Set-Acl "AD:$ESC4UnsafeOwner" -AclObject $ACL $ESC4WriteProperty = Get-ADObject "CN=ESC4WriteProperty,CN=Certificate Templates,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC4WriteProperty" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$WriteProperty,$Allow +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $WriteProperty, $Allow $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC4WriteProperty" -AclObject $ACL $ESC4WriteOwner = Get-ADObject "CN=ESC4WriteOwner,CN=Certificate Templates,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC4WriteOwner" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$WriteOwner,$Allow +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $WriteOwner, $Allow $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC4WriteOwner" -AclObject $ACL $ESC5FilteredAutoEnroll = Get-ADObject "CN=ESC5FilteredAutoEnroll,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC5FilteredAutoEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$AutoEnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $AutoEnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC5FilteredAutoEnroll" -AclObject $ACL $ESC5FilteredEnroll = Get-ADObject "CN=ESC5FilteredEnroll,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC5FilteredEnroll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$DefaultRights,$Allow,$EnrollGUID +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $DefaultRights, $Allow, $EnrollGUID $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC5FilteredEnroll" -AclObject $ACL @@ -373,13 +373,13 @@ Set-Acl "AD:$ESC5FilteredOwner" -AclObject $ACL $ESC5FilteredSafeUsers = Get-ADObject "CN=ESC5FilteredSafeUsers,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC5FilteredSafeUsers" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators,$GenericAll,$Allow +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Administrators, $GenericAll, $Allow $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC5FilteredSafeUsers" -AclObject $ACL $ESC5GenericAll = Get-ADObject "CN=ESC5GenericAll,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC5GenericAll" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$GenericAll,$Allow +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $GenericAll, $Allow $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC5GenericAll" -AclObject $ACL @@ -390,13 +390,13 @@ Set-Acl "AD:$ESC5UnsafeOwner" -AclObject $ACL $ESC5WriteProperty = Get-ADObject "CN=ESC5WriteProperty,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC5WriteProperty" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$WriteProperty,$Allow +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $WriteProperty, $Allow $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC5WriteProperty" -AclObject $ACL $ESC5WriteOwner = Get-ADObject "CN=ESC5WriteOwner,$PKSContainer" -Properties * $ACL = Get-Acl "AD:$ESC5WriteOwner" -$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers,$WriteOwner,$Allow +$AccessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $AuthenticatedUsers, $WriteOwner, $Allow $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC5WriteOwner" -AclObject $ACL @@ -417,9 +417,15 @@ Get-ADObject -Filter 'objectClass -eq "pKIEnrollmentService"' -SearchBase $PKSCo } catch { $AuditFilter = 'Failure' } + try { + $CertutilInterfaceFlag = certutil -config $CAFullName -getreg CA\InterfaceFlags + } catch { + $CertutilInterfaceFlag = 'Failure' + } } else { $AuditFilter = 'CA Unavailable' $SANFlag = 'CA Unavailable' + $InterfaceFlag = 'CA Unavailable' } if ($CertutilAudit) { try { @@ -442,6 +448,14 @@ Get-ADObject -Filter 'objectClass -eq "pKIEnrollmentService"' -SearchBase $PKSCo $SANFlag = 'No' } } + if ($CertutilInterfaceFlag) { + [string]$InterfaceFlag = $CertutilInterfaceFlag | Select-String ' IF_ENFORCEENCRYPTICERTREQUEST -- 200 \(' + if ($InterfaceFlag) { + $InterfaceFlag = 'Yes' + } else { + $InterfaceFlag = 'No' + } + } if ( ($AuditFilter -ne '0') -and ($AuditFilter -ne 'Never Configured') ) { certutil -config $CAFullname -setreg CA\AuditFilter 0 @@ -452,4 +466,9 @@ Get-ADObject -Filter 'objectClass -eq "pKIEnrollmentService"' -SearchBase $PKSCo certutil -config $CAFullname -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2 Invoke-Command -ComputerName $CAHostFQDN -ScriptBlock { Get-Service -Name 'certsvc' | Restart-Service -Force } } -} \ No newline at end of file + + if ($InterfaceFlag -eq 'Yes') { + certutil -config $CAFullname -setreg CA\InterfaceFlags -IF_ENFORCEENCRYPTICERTREQUEST + Invoke-Command -ComputerName $CAHostFQDN -ScriptBlock { Get-Service -Name 'certsvc' | Restart-Service -Force } + } +} From b43fc3ed2c9477584a4e93e2447e3acf21787122 Mon Sep 17 00:00:00 2001 From: TrimarcJake Date: Sat, 26 Oct 2024 07:11:24 -0500 Subject: [PATCH 08/14] Ope. Fresh build. --- Invoke-Locksmith.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 6893df7..216628e 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -1852,6 +1852,7 @@ function Invoke-Scans { [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects Write-Host 'Identifying Certificate Authorities with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' [array]$ESC11 = Find-ESC11 -ADCSObjects $ADCSObjects + Write-Host } } From cf29817eea50736513e4a0f88e3d2352fad2f8b4 Mon Sep 17 00:00:00 2001 From: Jake Hildreth Date: Sun, 3 Nov 2024 07:10:38 -0600 Subject: [PATCH 09/14] All fixes requested by @SamErde --- Invoke-Locksmith.ps1 | 6 +++--- Locksmith.psd1 | 2 +- Private/Find-ESC11.ps1 | 2 +- Private/Find-ESC6.ps1 | 2 +- Private/Invoke-Remediation.ps1 | 2 +- Private/Invoke-Scans.ps1 | 8 ++++---- Private/New-Dictionary.ps1 | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 216628e..10df4a5 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -288,7 +288,7 @@ function Find-ESC11 { Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'ESC11' - Issue = $_.AuditFilter + Issue = $_.InterfaceFlag Fix = 'N/A' Revert = 'N/A' } @@ -861,7 +861,7 @@ function Find-ESC6 { Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'ESC6' - Issue = $_.AuditFilter + Issue = $_.SANFlag Fix = 'N/A' Revert = 'N/A' } @@ -2798,7 +2798,7 @@ function Invoke-Locksmith { [System.Management.Automation.PSCredential]$Credential ) - $Version = '2024.10' + $Version = '2024.11' $LogoPart1 = @" _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| diff --git a/Locksmith.psd1 b/Locksmith.psd1 index 7d3dcd3..3f455f8 100644 --- a/Locksmith.psd1 +++ b/Locksmith.psd1 @@ -7,7 +7,7 @@ Description = 'A small tool to find and fix common misconfigurations in Active Directory Certificate Services.' FunctionsToExport = @('*') GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' - ModuleVersion = '2024.10' + ModuleVersion = '2024.11' PowerShellVersion = '5.1' PrivateData = @{ PSData = @{ diff --git a/Private/Find-ESC11.ps1 b/Private/Find-ESC11.ps1 index 87f4e9d..112ce6a 100644 --- a/Private/Find-ESC11.ps1 +++ b/Private/Find-ESC11.ps1 @@ -36,7 +36,7 @@ Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'ESC11' - Issue = $_.AuditFilter + Issue = $_.InterfaceFlag Fix = 'N/A' Revert = 'N/A' } diff --git a/Private/Find-ESC6.ps1 b/Private/Find-ESC6.ps1 index 0969f38..84d53cf 100644 --- a/Private/Find-ESC6.ps1 +++ b/Private/Find-ESC6.ps1 @@ -36,7 +36,7 @@ Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'ESC6' - Issue = $_.AuditFilter + Issue = $_.SANFlag Fix = 'N/A' Revert = 'N/A' } diff --git a/Private/Invoke-Remediation.ps1 b/Private/Invoke-Remediation.ps1 index 6ce2f6b..119ccf2 100644 --- a/Private/Invoke-Remediation.ps1 +++ b/Private/Invoke-Remediation.ps1 @@ -27,7 +27,7 @@ function Invoke-Remediation { .PARAMETER ESC6 A PS Object containing all necessary information about ESC6 issues. - .PARAMETER ESC6 + .PARAMETER ESC11 A PS Object containing all necessary information about ESC11 issues. .INPUTS diff --git a/Private/Invoke-Scans.ps1 b/Private/Invoke-Scans.ps1 index a70125e..581e52b 100644 --- a/Private/Invoke-Scans.ps1 +++ b/Private/Invoke-Scans.ps1 @@ -23,7 +23,7 @@ function Invoke-Scans { .NOTES - The script requires the following functions to be defined: Find-AuditingIssue, Find-ESC1, Find-ESC2, Find-ESC3Condition1, - Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8, Find-ESC8. + Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8, Find-ESC11. - The script uses Out-GridView or Out-ConsoleGridView for interactive selection when the 'PromptMe' scan option is chosen. - The script returns a hash table containing the results of the scans. @@ -113,9 +113,9 @@ function Invoke-Scans { Write-Host 'Identifying HTTP-based certificate enrollment interfaces (ESC8)...' [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects } - ESC6 { + ESC11 { Write-Host 'Identifying Issuing CAs with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' - [array]$ESC6 = Find-ESC6 -ADCSObjects $ADCSObjects + [array]$ESC11 = Find-ESC11 -ADCSObjects $ADCSObjects } All { Write-Host 'Identifying auditing issues...' @@ -144,7 +144,7 @@ function Invoke-Scans { [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + $ESC11 # If these are all empty = no issues found, exit - if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) -and ($ESC11) ) { + if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) -and (!$ESC11) ) { Write-Host "`n$(Get-Date) : No ADCS issues were found." -ForegroundColor Green break } diff --git a/Private/New-Dictionary.ps1 b/Private/New-Dictionary.ps1 index 8b9f453..4dc3487 100644 --- a/Private/New-Dictionary.ps1 +++ b/Private/New-Dictionary.ps1 @@ -21,7 +21,7 @@ descriptions, code used to find, code used to fix, and reference URLs. This is i function New-Dictionary { class VulnerableConfigurationItem { - static [string] $Version = '2023.10.01.000' + static [string] $Version = '2024.11.03.000' [string]$Name [ValidateSet('Escalation Path','Server Configuration','GPO Setting')][string]$Category [string]$Subcategory From 35317d5da0af15b954afd22b9026c12014987d5a Mon Sep 17 00:00:00 2001 From: Jake Hildreth Date: Sat, 9 Nov 2024 09:05:27 -0600 Subject: [PATCH 10/14] First attempt @ ESC13 detections --- Invoke-Locksmith.ps1 | 265 +++++++++++++++++++++++++------- Locksmith.psd1 | 2 +- Private/Export-RevertScript.ps1 | 21 ++- Private/Find-ESC1.ps1 | 17 +- Private/Find-ESC13.ps1 | 85 ++++++++++ Private/Find-ESC2.ps1 | 7 +- Private/Find-ESC3Condition1.ps1 | 6 +- Private/Find-ESC3Condition2.ps1 | 16 +- Private/Format-Result.ps1 | 1 + Private/Invoke-Remediation.ps1 | 55 ++++++- Private/Invoke-Scans.ps1 | 41 ++--- Private/New-Dictionary.ps1 | 9 ++ Public/Invoke-Locksmith.ps1 | 6 +- 13 files changed, 425 insertions(+), 106 deletions(-) create mode 100644 Private/Find-ESC13.ps1 diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 216628e..6cd9514 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -77,8 +77,22 @@ function Export-RevertScript { .PARAMETER ESC11 An array of ESC11 changes to be reverted. + .PARAMETER ESC13 + An array of ESC13 changes to be reverted. + .EXAMPLE - Export-RevertScript -AuditingIssues $auditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 + $params = @{ + AuditingIssues = $AuditingIssues + ESC1 = $ESC1 + ESC2 = $ESC2 + ESC3 = $ESC3 + ESC4 = $ESC4 + ESC5 = $ESC5 + ESC6 = $ESC6 + ESC11 = $ESC11 + ESC13 = $ESC13 + } + Export-RevertScript @params Reverts the changes performed by Locksmith using the specified arrays of objects. #> @@ -91,13 +105,14 @@ function Export-RevertScript { [array]$ESC4, [array]$ESC5, [array]$ESC6, - [array]$ESC11 + [array]$ESC11, + [array]$ESC13 ) begin { $Output = 'Invoke-RevertLocksmith.ps1' $RevertScript = [System.Text.StringBuilder]::New() [void]$RevertScript.Append("<#`nScript to revert changes performed by Locksmith`nCreated $(Get-Date)`n#>`n") - $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC11 + $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC11 + $ESC13 } process { if ($Objects) { @@ -195,21 +210,27 @@ function Find-ESC1 { .PARAMETER SafeUsers Specifies the list of SIDs of safe users who are allowed to have specific rights on the objects. This parameter is mandatory. + .PARAMETER ClientAuthEKUs + A list of EKUs that can be used for client authentication. + .OUTPUTS The script outputs an array of custom objects representing the matching ADCS objects and their associated information. .EXAMPLE $ADCSObjects = Get-ADCSObjects $SafeUsers = '-512$|-519$|-544$|-18$|-517$|-500$|-516$|-9$|-526$|-527$|S-1-5-10' - $Results = $ADCSObjects | Find-ESC1 -SafeUsers $SafeUsers + $ClientAuthEKUs = '1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.5\.2\.3\.4|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|2\.5\.29\.37\.0' + $Results = $ADCSObjects | Find-ESC1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEKUs $Results #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects, - [Parameter(Mandatory = $true)] - [array]$SafeUsers + [Parameter(Mandatory)] + [array]$SafeUsers, + [Parameter(Mandatory)] + $ClientAuthEKUs ) $ADCSObjects | Where-Object { ($_.objectClass -eq 'pKICertificateTemplate') -and @@ -312,6 +333,93 @@ Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { } } +function Find-ESC13 { + <# + .SYNOPSIS + This script finds AD CS (Active Directory Certificate Services) objects that have the ESC13 vulnerability. + + .DESCRIPTION + The script takes an array of ADCS objects as input and filters them based on the specified conditions. + For each matching object, it creates a custom object with properties representing various information about + the object, such as Forest, Name, DistinguishedName, IdentityReference, ActiveDirectoryRights, Issue, Fix, Revert, and Technique. + + .PARAMETER ADCSObjects + Specifies the array of ADCS objects to be processed. This parameter is mandatory. + + .PARAMETER SafeUsers + Specifies the list of SIDs of safe users who are allowed to have specific rights on the objects. This parameter is mandatory. + + .PARAMETER ClientAuthEKUs + A list of EKUs that can be used for client authentication. + + .OUTPUTS + The script outputs an array of custom objects representing the matching ADCS objects and their associated information. + + .EXAMPLE + $ADCSObjects = Get-ADCSObjects + $SafeUsers = '-512$|-519$|-544$|-18$|-517$|-500$|-516$|-9$|-526$|-527$|S-1-5-10' + $ClientAuthEKUs = '1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.5\.2\.3\.4|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|2\.5\.29\.37\.0' + $Results = $ADCSObjects | Find-ESC13 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEKUs + $Results + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [Microsoft.ActiveDirectory.Management.ADEntity[]]$ADCSObjects, + [Parameter(Mandatory)] + [array]$SafeUsers, + [Parameter(Mandatory)] + $ClientAuthEKUs + ) + + $ADCSObjects | Where-Object { + ($_.objectClass -eq 'pKICertificateTemplate') -and + ($_.pkiExtendedKeyUsage -match $ClientAuthEKUs) -and + ($_.'msPKI-Certificate-Policy') + } | ForEach-Object { + foreach ($policy in $_.'msPKI-Certificate-Policy') { + if ($ADCSObjects.'msPKI-Cert-Template-OID' -contains $policy) { + $OidToCheck = $ADCSObjects | Where-Object 'msPKI-Cert-Template-OID' -EQ $policy + if ($OidToCheck.'msDS-OIDToGroupLink') { + foreach ($entry in $_.nTSecurityDescriptor.Access) { + $Principal = New-Object System.Security.Principal.NTAccount($entry.IdentityReference) + if ($Principal -match '^(S-1|O:)') { + $SID = $Principal + } + else { + $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value + } + if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + $Issue = [pscustomobject]@{ + Forest = $_.CanonicalName.split('/')[0] + Name = $_.Name + DistinguishedName = $_.DistinguishedName + IdentityReference = $entry.IdentityReference + ActiveDirectoryRights = $entry.ActiveDirectoryRights + LinkedGroup = $OidToCheck.'msDS-OIDToGroupLink' + Issue = "$($entry.IdentityReference) can enroll in this Client " + + "Authentication template which is linked to $($OidToCheck.'msDS-OIDToGroupLink')." + Fix = @" +# Enable Manager Approval +`$Object = `'$($_.DistinguishedName)`' +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} +"@ + Revert = @" +# Disable Manager Approval +`$Object = `'$($_.DistinguishedName)`' +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} +"@ + Technique = 'ESC13' + } + $Issue + } + } + } + } + } + } +} + function Find-ESC2 { <# .SYNOPSIS @@ -347,7 +455,6 @@ function Find-ESC2 { $ADCSObjects | Where-Object { ($_.ObjectClass -eq 'pKICertificateTemplate') -and ( (!$_.pkiExtendedKeyUsage) -or ($_.pkiExtendedKeyUsage -match '2.5.29.37.0') ) -and - ($_.'msPKI-Certificate-Name-Flag' -band 1) -and !($_.'msPKI-Enrollment-Flag' -band 2) -and ( ($_.'msPKI-RA-Signature' -eq 0) -or ($null -eq $_.'msPKI-RA-Signature') ) } | ForEach-Object { @@ -369,11 +476,11 @@ function Find-ESC2 { Issue = "$($entry.IdentityReference) can request a SubCA certificate without Manager Approval" Fix = @" `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ Technique = 'ESC2' } @@ -438,12 +545,14 @@ function Find-ESC3Condition1 { ActiveDirectoryRights = $entry.ActiveDirectoryRights Issue = "$($entry.IdentityReference) can enroll in this Enrollment Agent template without Manager Approval" Fix = @" +# Enabled Manager Approval `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" +# Disable Manager Approval `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ Technique = 'ESC3' } @@ -508,14 +617,14 @@ function Find-ESC3Condition2 { IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval" - Fix = @" -`$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} -"@ - Revert = @" -`$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} -"@ + # Fix = @" + # `$Object = `'$($_.DistinguishedName)`' + # Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} + # "@ + # Revert = @" + # `$Object = `'$($_.DistinguishedName)`' + # Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} + # "@ Technique = 'ESC3' } $Issue @@ -1109,6 +1218,7 @@ function Format-Result { ESC6 = 'ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2 Flag Enabled' ESC8 = 'ESC8 - HTTP/S Enrollment Enabled' ESC11 = 'ESC11 - IF_ENFORCEENCRYPTICERTREQUEST Flag Disabled' + ESC13 = 'ESC13 - Vulnerable Certificate Temple - Group-Linked' } if ($null -ne $Issue) { @@ -1421,9 +1531,12 @@ function Invoke-Remediation { .PARAMETER ESC6 A PS Object containing all necessary information about ESC6 issues. - .PARAMETER ESC6 + .PARAMETER ESC11 A PS Object containing all necessary information about ESC11 issues. + .PARAMETER ESC13 + A PS Object containing all necessary information about ESC13 issues. + .INPUTS PS Objects @@ -1440,7 +1553,8 @@ function Invoke-Remediation { $ESC4, $ESC5, $ESC6, - $ESC11 + $ESC11, + $ESC13 ) Write-Host "`nExecuting Mode 4 - Attempting to fix identified issues!`n" -ForegroundColor Green @@ -1448,7 +1562,18 @@ function Invoke-Remediation { Write-Host 'Invoke-RevertLocksmith.ps1' -ForegroundColor White -NoNewline Write-Host ") which can be used to revert all changes made by Locksmith...`n" try { - Export-RevertScript -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 + $params = @{ + AuditingIssues = $AuditingIssues + ESC1 = $ESC1 + ESC2 = $ESC2 + ESC3 = $ESC3 + ESC4 = $ESC4 + ESC5 = $ESC5 + ESC6 = $ESC6 + ESC11 = $ESC11 + ESC13 = $ESC13 + } + Export-RevertScript @params } catch { Write-Warning 'Creation of Invoke-RevertLocksmith.ps1 failed.' @@ -1597,7 +1722,7 @@ function Invoke-Remediation { } } if ($ESC5) { - $ESC5 | Where-Object Issue -Like "* Owner rights *" | ForEach-Object { # This selector sucks - Jake + $ESC5 | Where-Object Issue -Like "* Owner rights *" | ForEach-Object { # TODO This selector sucks - Jake $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White Write-Host "$($_.Issue)`n" @@ -1699,6 +1824,40 @@ function Invoke-Remediation { } } + if ($ESC13) { + $ESC13 | ForEach-Object { + $FixBlock = [scriptblock]::Create($_.Fix) + Write-Host 'ISSUE:' -ForegroundColor White + Write-Host "Security Principals can enroll in `"$($_.Name)`" template which is linked to $($_.LinkedGroup).`n" + Write-Host 'TECHNIQUE:' -ForegroundColor White + Write-Host "$($_.Technique)`n" + Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White + Write-Host "Locksmith will attempt to enable Manager Approval on the `"$($_.Name)`" template.`n" + Write-Host 'CCOMMAND(S) TO BE RUN:' + Write-Host 'PS> ' -NoNewline + Write-Host "$($_.Fix)`n" -ForegroundColor Cyan + Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White + Write-Host "WARNING: This change could cause some services to stop working until certificates are approved.`n" -ForegroundColor Yellow + Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow + Write-Host "Continue with this operation? [Y] Yes " -NoNewline + Write-Host "[N] " -ForegroundColor Yellow -NoNewline + Write-Host "No: " -NoNewline + $WarningError = '' + $WarningError = Read-Host + if ($WarningError -like 'y') { + try { + Invoke-Command -ScriptBlock $FixBlock + } + catch { + Write-Error 'Could not enable Manager Approval. Are you an Active Directory or AD CS admin?' + } + } + else { + Write-Host "SKIPPED!`n" -ForegroundColor Yellow + } + } + } + Write-Host "Mode 4 Complete! There are no more issues that Locksmith can automatically resolve.`n" -ForegroundColor Green Write-Host 'If you experience any operational impact from using Locksmith Mode 4, use ' -NoNewline Write-Host 'Invoke-RevertLocksmith.ps1 ' -ForegroundColor White @@ -1716,41 +1875,27 @@ function Invoke-Scans { .SYNOPSIS Invoke-Scans.ps1 is a script that performs various scans on ADCS (Active Directory Certificate Services) objects. - .DESCRIPTION - This script accepts a parameter named $Scans, which specifies the type of scans to perform. The available scan options are: - - Auditing - - ESC1 - - ESC2 - - ESC3 - - ESC4 - - ESC5 - - ESC6 - - ESC8 - - ESC11 - - All - - PromptMe - .PARAMETER Scans Specifies the type of scans to perform. Multiple scan options can be provided as an array. The default value is 'All'. - The available scan options are: 'Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'All', 'PromptMe'. + The available scan options are: 'Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'ESC13', 'All', 'PromptMe'. .NOTES - The script requires the following functions to be defined: Find-AuditingIssue, Find-ESC1, Find-ESC2, Find-ESC3Condition1, - Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8, Find-ESC8. + Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8, Find-ESC11, Find-ESC13 - The script uses Out-GridView or Out-ConsoleGridView for interactive selection when the 'PromptMe' scan option is chosen. - The script returns a hash table containing the results of the scans. .EXAMPLE - # Perform all scans - Invoke-Scans + Invoke-Scans + # Perform all scans .EXAMPLE - # Perform only the 'Auditing' and 'ESC1' scans - Invoke-Scans -Scans 'Auditing', 'ESC1' + Invoke-Scans -Scans 'Auditing', 'ESC1' + # Perform only the 'Auditing' and 'ESC1' scans .EXAMPLE - # Prompt the user to select the scans to perform - Invoke-Scans -Scans 'PromptMe' + Invoke-Scans -Scans 'PromptMe' + # Prompt the user to select the scans to perform #> [CmdletBinding()] @@ -1765,7 +1910,7 @@ function Invoke-Scans { [int]$Mode, $SafeObjectTypes, $SafeOwners, - [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'All', 'PromptMe')] + [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'ESC13', 'All', 'PromptMe')] [array]$Scans = 'All', $UnsafeOwners, $UnsafeUsers, @@ -1836,7 +1981,7 @@ function Invoke-Scans { Write-Host 'Identifying auditing issues...' [array]$AuditingIssues = Find-AuditingIssue -ADCSObjects $ADCSObjects Write-Host 'Identifying AD CS templates with dangerous ESC1 configurations...' - [array]$ESC1 = Find-ESC1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers + [array]$ESC1 = Find-ESC1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEkus Write-Host 'Identifying AD CS templates with dangerous ESC2 configurations...' [array]$ESC2 = Find-ESC2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers Write-Host 'Identifying AD CS templates with dangerous ESC3 configurations...' @@ -1852,14 +1997,16 @@ function Invoke-Scans { [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects Write-Host 'Identifying Certificate Authorities with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' [array]$ESC11 = Find-ESC11 -ADCSObjects $ADCSObjects + Write-Host 'Identifying AD CS templates with dangerous ESC13 configurations...' + [array]$ESC13 = Find-ESC13 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEkus Write-Host } } - [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + $ESC11 + [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + $ESC11 + $ESC13 # If these are all empty = no issues found, exit - if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) -and ($ESC11) ) { + if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) -and ($ESC11) -and ($ESC13) ) { Write-Host "`n$(Get-Date) : No ADCS issues were found." -ForegroundColor Green break } @@ -1876,6 +2023,7 @@ function Invoke-Scans { ESC6 = $ESC6 ESC8 = $ESC8 ESC11 = $ESC11 + ESC13 = $ESC13 } } @@ -2015,6 +2163,15 @@ function New-Dictionary { FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' }, + [VulnerableConfigurationItem]@{ + Name = 'ESC13' + Category = 'Escalation Path' + Subcategory = 'Certificate Template linked to Group' + Summary = '' + FindIt = { Find-ESC13 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } + ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' + }, [VulnerableConfigurationItem]@{ Name = 'Auditing' Category = 'Server Configuration' @@ -2798,7 +2955,7 @@ function Invoke-Locksmith { [System.Management.Automation.PSCredential]$Credential ) - $Version = '2024.10' + $Version = '2024.11' $LogoPart1 = @" _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| @@ -2835,7 +2992,7 @@ function Invoke-Locksmith { # For output filenames [string]$FilePrefix = "Locksmith $(Get-Date -Format 'yyyy-MM-dd hh-mm-ss')" - # Extended Key Usages for client authentication. A requirement for ESC1 + # Extended Key Usages for client authentication. A requirement for ESC1, ESC3 Condition 2, and ESC13 $ClientAuthEKUs = '1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.5\.2\.3\.4|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|2\.5\.29\.37\.0' # GenericAll, WriteDacl, and WriteOwner all permit full control of an AD object. @@ -2969,6 +3126,7 @@ function Invoke-Locksmith { $ESC6 = $Results['ESC6'] $ESC8 = $Results['ESC8'] $ESC11 = $Results['ESC11'] + $ESC13 = $Results['ESC13'] # If these are all empty = no issues found, exit if ($null -eq $Results) { @@ -2989,6 +3147,7 @@ function Invoke-Locksmith { Format-Result $ESC6 '0' Format-Result $ESC8 '0' Format-Result $ESC11 '0' + Format-Result $ESC13 '0' } 1 { Format-Result $AuditingIssues '1' @@ -3000,6 +3159,7 @@ function Invoke-Locksmith { Format-Result $ESC6 '1' Format-Result $ESC8 '1' Format-Result $ESC11 '1' + Format-Result $ESC13 '1' } 2 { $Output = Join-Path -Path $OutputPath -ChildPath "$FilePrefix ADCSIssues.CSV" @@ -3033,6 +3193,7 @@ function Invoke-Locksmith { ESC5 = $ESC5 ESC6 = $ESC6 ESC11 = $ESC11 + ESC13 = $ESC13 } Invoke-Remediation @params } diff --git a/Locksmith.psd1 b/Locksmith.psd1 index 7d3dcd3..3f455f8 100644 --- a/Locksmith.psd1 +++ b/Locksmith.psd1 @@ -7,7 +7,7 @@ Description = 'A small tool to find and fix common misconfigurations in Active Directory Certificate Services.' FunctionsToExport = @('*') GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' - ModuleVersion = '2024.10' + ModuleVersion = '2024.11' PowerShellVersion = '5.1' PrivateData = @{ PSData = @{ diff --git a/Private/Export-RevertScript.ps1 b/Private/Export-RevertScript.ps1 index a6d3dae..8632239 100644 --- a/Private/Export-RevertScript.ps1 +++ b/Private/Export-RevertScript.ps1 @@ -33,8 +33,22 @@ .PARAMETER ESC11 An array of ESC11 changes to be reverted. + .PARAMETER ESC13 + An array of ESC13 changes to be reverted. + .EXAMPLE - Export-RevertScript -AuditingIssues $auditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 + $params = @{ + AuditingIssues = $AuditingIssues + ESC1 = $ESC1 + ESC2 = $ESC2 + ESC3 = $ESC3 + ESC4 = $ESC4 + ESC5 = $ESC5 + ESC6 = $ESC6 + ESC11 = $ESC11 + ESC13 = $ESC13 + } + Export-RevertScript @params Reverts the changes performed by Locksmith using the specified arrays of objects. #> @@ -47,13 +61,14 @@ [array]$ESC4, [array]$ESC5, [array]$ESC6, - [array]$ESC11 + [array]$ESC11, + [array]$ESC13 ) begin { $Output = 'Invoke-RevertLocksmith.ps1' $RevertScript = [System.Text.StringBuilder]::New() [void]$RevertScript.Append("<#`nScript to revert changes performed by Locksmith`nCreated $(Get-Date)`n#>`n") - $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC11 + $Objects = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC11 + $ESC13 } process { if ($Objects) { diff --git a/Private/Find-ESC1.ps1 b/Private/Find-ESC1.ps1 index a6499e3..ab22a79 100644 --- a/Private/Find-ESC1.ps1 +++ b/Private/Find-ESC1.ps1 @@ -14,21 +14,27 @@ .PARAMETER SafeUsers Specifies the list of SIDs of safe users who are allowed to have specific rights on the objects. This parameter is mandatory. + .PARAMETER ClientAuthEKUs + A list of EKUs that can be used for client authentication. + .OUTPUTS The script outputs an array of custom objects representing the matching ADCS objects and their associated information. .EXAMPLE $ADCSObjects = Get-ADCSObjects $SafeUsers = '-512$|-519$|-544$|-18$|-517$|-500$|-516$|-9$|-526$|-527$|S-1-5-10' - $Results = $ADCSObjects | Find-ESC1 -SafeUsers $SafeUsers + $ClientAuthEKUs = '1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.5\.2\.3\.4|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|2\.5\.29\.37\.0' + $Results = $ADCSObjects | Find-ESC1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEKUs $Results #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects, - [Parameter(Mandatory = $true)] - [array]$SafeUsers + [Parameter(Mandatory)] + [array]$SafeUsers, + [Parameter(Mandatory)] + $ClientAuthEKUs ) $ADCSObjects | Where-Object { ($_.objectClass -eq 'pKICertificateTemplate') -and @@ -37,6 +43,7 @@ !($_.'msPKI-Enrollment-Flag' -band 2) -and ( ($_.'msPKI-RA-Signature' -eq 0) -or ($null -eq $_.'msPKI-RA-Signature') ) } | ForEach-Object { + # Write-Host $_; continue foreach ($entry in $_.nTSecurityDescriptor.Access) { $Principal = New-Object System.Security.Principal.NTAccount($entry.IdentityReference) if ($Principal -match '^(S-1|O:)') { @@ -44,7 +51,7 @@ } else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } - if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + if ( ($SID -notmatch $SafeUsers) -and ( ($entry.ActiveDirectoryRights -match 'ExtendedRight') -or ($entry.ActiveDirectoryRights -match 'GenericAll') ) ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name diff --git a/Private/Find-ESC13.ps1 b/Private/Find-ESC13.ps1 new file mode 100644 index 0000000..fb6047d --- /dev/null +++ b/Private/Find-ESC13.ps1 @@ -0,0 +1,85 @@ +function Find-ESC13 { + <# + .SYNOPSIS + This script finds AD CS (Active Directory Certificate Services) objects that have the ESC13 vulnerability. + + .DESCRIPTION + The script takes an array of ADCS objects as input and filters them based on the specified conditions. + For each matching object, it creates a custom object with properties representing various information about + the object, such as Forest, Name, DistinguishedName, IdentityReference, ActiveDirectoryRights, Issue, Fix, Revert, and Technique. + + .PARAMETER ADCSObjects + Specifies the array of ADCS objects to be processed. This parameter is mandatory. + + .PARAMETER SafeUsers + Specifies the list of SIDs of safe users who are allowed to have specific rights on the objects. This parameter is mandatory. + + .PARAMETER ClientAuthEKUs + A list of EKUs that can be used for client authentication. + + .OUTPUTS + The script outputs an array of custom objects representing the matching ADCS objects and their associated information. + + .EXAMPLE + $ADCSObjects = Get-ADCSObjects + $SafeUsers = '-512$|-519$|-544$|-18$|-517$|-500$|-516$|-9$|-526$|-527$|S-1-5-10' + $ClientAuthEKUs = '1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.5\.2\.3\.4|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|2\.5\.29\.37\.0' + $Results = $ADCSObjects | Find-ESC13 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEKUs + $Results + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [Microsoft.ActiveDirectory.Management.ADEntity[]]$ADCSObjects, + [Parameter(Mandatory)] + [array]$SafeUsers, + [Parameter(Mandatory)] + $ClientAuthEKUs + ) + + $ADCSObjects | Where-Object { + ($_.objectClass -eq 'pKICertificateTemplate') -and + ($_.pkiExtendedKeyUsage -match $ClientAuthEKUs) -and + ($_.'msPKI-Certificate-Policy') + } | ForEach-Object { + foreach ($policy in $_.'msPKI-Certificate-Policy') { + if ($ADCSObjects.'msPKI-Cert-Template-OID' -contains $policy) { + $OidToCheck = $ADCSObjects | Where-Object 'msPKI-Cert-Template-OID' -eq $policy + if ($OidToCheck.'msDS-OIDToGroupLink') { + foreach ($entry in $_.nTSecurityDescriptor.Access) { + $Principal = New-Object System.Security.Principal.NTAccount($entry.IdentityReference) + if ($Principal -match '^(S-1|O:)') { + $SID = $Principal + } else { + $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value + } + if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + $Issue = [pscustomobject]@{ + Forest = $_.CanonicalName.split('/')[0] + Name = $_.Name + DistinguishedName = $_.DistinguishedName + IdentityReference = $entry.IdentityReference + ActiveDirectoryRights = $entry.ActiveDirectoryRights + LinkedGroup = $OidToCheck.'msDS-OIDToGroupLink' + Issue = "$($entry.IdentityReference) can enroll in this Client " + + "Authentication template which is linked to $($OidToCheck.'msDS-OIDToGroupLink')." + Fix = @" +# Enable Manager Approval +`$Object = `'$($_.DistinguishedName)`' +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} +"@ + Revert = @" +# Disable Manager Approval +`$Object = `'$($_.DistinguishedName)`' +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} +"@ + Technique = 'ESC13' + } + $Issue + } + } + } + } + } + } +} diff --git a/Private/Find-ESC2.ps1 b/Private/Find-ESC2.ps1 index 9d333a1..793bbe5 100644 --- a/Private/Find-ESC2.ps1 +++ b/Private/Find-ESC2.ps1 @@ -32,8 +32,7 @@ ) $ADCSObjects | Where-Object { ($_.ObjectClass -eq 'pKICertificateTemplate') -and - ( (!$_.pkiExtendedKeyUsage) -or ($_.pkiExtendedKeyUsage -match '2.5.29.37.0') )-and - ($_.'msPKI-Certificate-Name-Flag' -band 1) -and + ( (!$_.pkiExtendedKeyUsage) -or ($_.pkiExtendedKeyUsage -match '2.5.29.37.0') ) -and !($_.'msPKI-Enrollment-Flag' -band 2) -and ( ($_.'msPKI-RA-Signature' -eq 0) -or ($null -eq $_.'msPKI-RA-Signature') ) } | ForEach-Object { @@ -54,11 +53,11 @@ Issue = "$($entry.IdentityReference) can request a SubCA certificate without Manager Approval" Fix = @" `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ Technique = 'ESC2' } diff --git a/Private/Find-ESC3Condition1.ps1 b/Private/Find-ESC3Condition1.ps1 index 7da08ee..be25217 100644 --- a/Private/Find-ESC3Condition1.ps1 +++ b/Private/Find-ESC3Condition1.ps1 @@ -52,12 +52,14 @@ ActiveDirectoryRights = $entry.ActiveDirectoryRights Issue = "$($entry.IdentityReference) can enroll in this Enrollment Agent template without Manager Approval" Fix = @" +# Enabled Manager Approval `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" +# Disable Manager Approval `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ Technique = 'ESC3' } diff --git a/Private/Find-ESC3Condition2.ps1 b/Private/Find-ESC3Condition2.ps1 index c8ab537..c49c35f 100644 --- a/Private/Find-ESC3Condition2.ps1 +++ b/Private/Find-ESC3Condition2.ps1 @@ -52,14 +52,14 @@ IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval" - Fix = @" -`$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} -"@ - Revert = @" -`$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} -"@ +# Fix = @" +# `$Object = `'$($_.DistinguishedName)`' +# Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} +# "@ +# Revert = @" +# `$Object = `'$($_.DistinguishedName)`' +# Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} +# "@ Technique = 'ESC3' } $Issue diff --git a/Private/Format-Result.ps1 b/Private/Format-Result.ps1 index 7e2eaa7..6a661fa 100644 --- a/Private/Format-Result.ps1 +++ b/Private/Format-Result.ps1 @@ -40,6 +40,7 @@ function Format-Result { ESC6 = 'ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2 Flag Enabled' ESC8 = 'ESC8 - HTTP/S Enrollment Enabled' ESC11 = 'ESC11 - IF_ENFORCEENCRYPTICERTREQUEST Flag Disabled' + ESC13 = 'ESC13 - Vulnerable Certificate Temple - Group-Linked' } if ($null -ne $Issue) { diff --git a/Private/Invoke-Remediation.ps1 b/Private/Invoke-Remediation.ps1 index 6ce2f6b..80c0194 100644 --- a/Private/Invoke-Remediation.ps1 +++ b/Private/Invoke-Remediation.ps1 @@ -27,9 +27,12 @@ function Invoke-Remediation { .PARAMETER ESC6 A PS Object containing all necessary information about ESC6 issues. - .PARAMETER ESC6 + .PARAMETER ESC11 A PS Object containing all necessary information about ESC11 issues. + .PARAMETER ESC13 + A PS Object containing all necessary information about ESC13 issues. + .INPUTS PS Objects @@ -46,7 +49,8 @@ function Invoke-Remediation { $ESC4, $ESC5, $ESC6, - $ESC11 + $ESC11, + $ESC13 ) Write-Host "`nExecuting Mode 4 - Attempting to fix identified issues!`n" -ForegroundColor Green @@ -54,7 +58,18 @@ function Invoke-Remediation { Write-Host 'Invoke-RevertLocksmith.ps1' -ForegroundColor White -NoNewline Write-Host ") which can be used to revert all changes made by Locksmith...`n" try { - Export-RevertScript -AuditingIssues $AuditingIssues -ESC1 $ESC1 -ESC2 $ESC2 -ESC3 $ESC3 -ESC4 $ESC4 -ESC5 $ESC5 -ESC6 $ESC6 -ESC11 $ESC11 + $params = @{ + AuditingIssues = $AuditingIssues + ESC1 = $ESC1 + ESC2 = $ESC2 + ESC3 = $ESC3 + ESC4 = $ESC4 + ESC5 = $ESC5 + ESC6 = $ESC6 + ESC11 = $ESC11 + ESC13 = $ESC13 + } + Export-RevertScript @params } catch { Write-Warning 'Creation of Invoke-RevertLocksmith.ps1 failed.' Write-Host "Continue with this operation? [Y] Yes " -NoNewline @@ -194,7 +209,7 @@ function Invoke-Remediation { } } if ($ESC5) { - $ESC5 | Where-Object Issue -like "* Owner rights *" | ForEach-Object { # This selector sucks - Jake + $ESC5 | Where-Object Issue -like "* Owner rights *" | ForEach-Object { # TODO This selector sucks - Jake $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White Write-Host "$($_.Issue)`n" @@ -290,6 +305,38 @@ function Invoke-Remediation { } } + if ($ESC13) { + $ESC13 | ForEach-Object { + $FixBlock = [scriptblock]::Create($_.Fix) + Write-Host 'ISSUE:' -ForegroundColor White + Write-Host "Security Principals can enroll in `"$($_.Name)`" template which is linked to $($_.LinkedGroup).`n" + Write-Host 'TECHNIQUE:' -ForegroundColor White + Write-Host "$($_.Technique)`n" + Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White + Write-Host "Locksmith will attempt to enable Manager Approval on the `"$($_.Name)`" template.`n" + Write-Host 'CCOMMAND(S) TO BE RUN:' + Write-Host 'PS> ' -NoNewline + Write-Host "$($_.Fix)`n" -ForegroundColor Cyan + Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White + Write-Host "WARNING: This change could cause some services to stop working until certificates are approved.`n" -ForegroundColor Yellow + Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow + Write-Host "Continue with this operation? [Y] Yes " -NoNewline + Write-Host "[N] " -ForegroundColor Yellow -NoNewline + Write-Host "No: " -NoNewLine + $WarningError = '' + $WarningError = Read-Host + if ($WarningError -like 'y') { + try { + Invoke-Command -ScriptBlock $FixBlock + } catch { + Write-Error 'Could not enable Manager Approval. Are you an Active Directory or AD CS admin?' + } + } else { + Write-Host "SKIPPED!`n" -ForegroundColor Yellow + } + } + } + Write-Host "Mode 4 Complete! There are no more issues that Locksmith can automatically resolve.`n" -ForegroundColor Green Write-Host 'If you experience any operational impact from using Locksmith Mode 4, use ' -NoNewline Write-Host 'Invoke-RevertLocksmith.ps1 ' -ForegroundColor White diff --git a/Private/Invoke-Scans.ps1 b/Private/Invoke-Scans.ps1 index a70125e..6af6c28 100644 --- a/Private/Invoke-Scans.ps1 +++ b/Private/Invoke-Scans.ps1 @@ -3,41 +3,27 @@ function Invoke-Scans { .SYNOPSIS Invoke-Scans.ps1 is a script that performs various scans on ADCS (Active Directory Certificate Services) objects. - .DESCRIPTION - This script accepts a parameter named $Scans, which specifies the type of scans to perform. The available scan options are: - - Auditing - - ESC1 - - ESC2 - - ESC3 - - ESC4 - - ESC5 - - ESC6 - - ESC8 - - ESC11 - - All - - PromptMe - .PARAMETER Scans Specifies the type of scans to perform. Multiple scan options can be provided as an array. The default value is 'All'. - The available scan options are: 'Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'All', 'PromptMe'. + The available scan options are: 'Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'ESC13', 'All', 'PromptMe'. .NOTES - The script requires the following functions to be defined: Find-AuditingIssue, Find-ESC1, Find-ESC2, Find-ESC3Condition1, - Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8, Find-ESC8. + Find-ESC3Condition2, Find-ESC4, Find-ESC5, Find-ESC6, Find-ESC8, Find-ESC11, Find-ESC13 - The script uses Out-GridView or Out-ConsoleGridView for interactive selection when the 'PromptMe' scan option is chosen. - The script returns a hash table containing the results of the scans. .EXAMPLE - # Perform all scans - Invoke-Scans + Invoke-Scans + # Perform all scans .EXAMPLE - # Perform only the 'Auditing' and 'ESC1' scans - Invoke-Scans -Scans 'Auditing', 'ESC1' + Invoke-Scans -Scans 'Auditing', 'ESC1' + # Perform only the 'Auditing' and 'ESC1' scans .EXAMPLE - # Prompt the user to select the scans to perform - Invoke-Scans -Scans 'PromptMe' + Invoke-Scans -Scans 'PromptMe' + # Prompt the user to select the scans to perform #> [CmdletBinding()] @@ -52,7 +38,7 @@ function Invoke-Scans { [int]$Mode, $SafeObjectTypes, $SafeOwners, - [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'All', 'PromptMe')] + [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'ESC13', 'All', 'PromptMe')] [array]$Scans = 'All', $UnsafeOwners, $UnsafeUsers, @@ -121,7 +107,7 @@ function Invoke-Scans { Write-Host 'Identifying auditing issues...' [array]$AuditingIssues = Find-AuditingIssue -ADCSObjects $ADCSObjects Write-Host 'Identifying AD CS templates with dangerous ESC1 configurations...' - [array]$ESC1 = Find-ESC1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers + [array]$ESC1 = Find-ESC1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEkus Write-Host 'Identifying AD CS templates with dangerous ESC2 configurations...' [array]$ESC2 = Find-ESC2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers Write-Host 'Identifying AD CS templates with dangerous ESC3 configurations...' @@ -137,14 +123,16 @@ function Invoke-Scans { [array]$ESC8 = Find-ESC8 -ADCSObjects $ADCSObjects Write-Host 'Identifying Certificate Authorities with IF_ENFORCEENCRYPTICERTREQUEST disabled (ESC11)...' [array]$ESC11 = Find-ESC11 -ADCSObjects $ADCSObjects + Write-Host 'Identifying AD CS templates with dangerous ESC13 configurations...' + [array]$ESC13 = Find-ESC13 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers -ClientAuthEKUs $ClientAuthEkus Write-Host } } - [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + $ESC11 + [array]$AllIssues = $AuditingIssues + $ESC1 + $ESC2 + $ESC3 + $ESC4 + $ESC5 + $ESC6 + $ESC8 + $ESC11 + $ESC13 # If these are all empty = no issues found, exit - if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) -and ($ESC11) ) { + if ((!$AuditingIssues) -and (!$ESC1) -and (!$ESC2) -and (!$ESC3) -and (!$ESC4) -and (!$ESC5) -and (!$ESC6) -and (!$ESC8) -and ($ESC11) -and ($ESC13) ) { Write-Host "`n$(Get-Date) : No ADCS issues were found." -ForegroundColor Green break } @@ -161,5 +149,6 @@ function Invoke-Scans { ESC6 = $ESC6 ESC8 = $ESC8 ESC11 = $ESC11 + ESC13 = $ESC13 } } diff --git a/Private/New-Dictionary.ps1 b/Private/New-Dictionary.ps1 index 8b9f453..ba7c566 100644 --- a/Private/New-Dictionary.ps1 +++ b/Private/New-Dictionary.ps1 @@ -134,6 +134,15 @@ function New-Dictionary { FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' }, + [VulnerableConfigurationItem]@{ + Name = 'ESC13' + Category = 'Escalation Path' + Subcategory = 'Certificate Template linked to Group' + Summary = '' + FindIt = {Find-ESC13} + FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' + }, [VulnerableConfigurationItem]@{ Name = 'Auditing' Category = 'Server Configuration' diff --git a/Public/Invoke-Locksmith.ps1 b/Public/Invoke-Locksmith.ps1 index 46ccaf3..8f34719 100644 --- a/Public/Invoke-Locksmith.ps1 +++ b/Public/Invoke-Locksmith.ps1 @@ -125,7 +125,7 @@ # For output filenames [string]$FilePrefix = "Locksmith $(Get-Date -format 'yyyy-MM-dd hh-mm-ss')" - # Extended Key Usages for client authentication. A requirement for ESC1 + # Extended Key Usages for client authentication. A requirement for ESC1, ESC3 Condition 2, and ESC13 $ClientAuthEKUs = '1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.5\.2\.3\.4|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|2\.5\.29\.37\.0' # GenericAll, WriteDacl, and WriteOwner all permit full control of an AD object. @@ -257,6 +257,7 @@ $ESC6 = $Results['ESC6'] $ESC8 = $Results['ESC8'] $ESC11 = $Results['ESC11'] + $ESC13 = $Results['ESC13'] # If these are all empty = no issues found, exit if ($null -eq $Results) { @@ -277,6 +278,7 @@ Format-Result $ESC6 '0' Format-Result $ESC8 '0' Format-Result $ESC11 '0' + Format-Result $ESC13 '0' } 1 { Format-Result $AuditingIssues '1' @@ -288,6 +290,7 @@ Format-Result $ESC6 '1' Format-Result $ESC8 '1' Format-Result $ESC11 '1' + Format-Result $ESC13 '1' } 2 { $Output = Join-Path -Path $OutputPath -ChildPath "$FilePrefix ADCSIssues.CSV" @@ -319,6 +322,7 @@ ESC5 = $ESC5 ESC6 = $ESC6 ESC11 = $ESC11 + ESC13 = $ESC13 } Invoke-Remediation @params } From 021cb9af92794ab46c36edc322c11b25308daf02 Mon Sep 17 00:00:00 2001 From: Jake Hildreth Date: Sat, 9 Nov 2024 09:53:08 -0600 Subject: [PATCH 11/14] Formatting + additional issue detail. --- Invoke-Locksmith.ps1 | 68 +++++++++++++++++++-------------- Private/Find-AuditingIssue.ps1 | 2 +- Private/Find-ESC1.ps1 | 12 +++--- Private/Find-ESC11.ps1 | 8 ++-- Private/Find-ESC13.ps1 | 4 +- Private/Find-ESC2.ps1 | 6 +-- Private/Find-ESC3Condition1.ps1 | 6 +-- Private/Find-ESC3Condition2.ps1 | 9 +++-- Private/Find-ESC4.ps1 | 20 +++++----- Private/Find-ESC5.ps1 | 16 ++++---- Private/Find-ESC6.ps1 | 9 +++-- Private/Find-ESC8.ps1 | 6 +-- Private/Format-Result.ps1 | 10 +++-- Private/New-Dictionary.ps1 | 2 +- 14 files changed, 98 insertions(+), 80 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 6cd9514..228e5f3 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -171,7 +171,7 @@ function Find-AuditingIssue { Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'DETECT' - Issue = "Auditing is not fully enabled on $($_.CAFullName). Current value is $($_.AuditFilter)" + Issue = "Auditing is not fully enabled on $($_.CAFullName). Important security events may be left unrecorded." Fix = @" certutil.exe -config `'$($_.CAFullname)`' -setreg `'CA\AuditFilter`' 127 Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { @@ -212,7 +212,7 @@ function Find-ESC1 { .PARAMETER ClientAuthEKUs A list of EKUs that can be used for client authentication. - + .OUTPUTS The script outputs an array of custom objects representing the matching ADCS objects and their associated information. @@ -239,6 +239,7 @@ function Find-ESC1 { !($_.'msPKI-Enrollment-Flag' -band 2) -and ( ($_.'msPKI-RA-Signature' -eq 0) -or ($null -eq $_.'msPKI-RA-Signature') ) } | ForEach-Object { + # Write-Host $_; continue foreach ($entry in $_.nTSecurityDescriptor.Access) { $Principal = New-Object System.Security.Principal.NTAccount($entry.IdentityReference) if ($Principal -match '^(S-1|O:)') { @@ -247,21 +248,21 @@ function Find-ESC1 { else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } - if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + if ( ($SID -notmatch $SafeUsers) -and ( ($entry.ActiveDirectoryRights -match 'ExtendedRight') -or ($entry.ActiveDirectoryRights -match 'GenericAll') ) ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval" + Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval." Fix = @" `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ Technique = 'ESC1' } @@ -314,16 +315,16 @@ function Find-ESC11 { Revert = 'N/A' } if ($_.InterfaceFlag -eq 'No') { - $Issue.Issue = 'IF_ENFORCEENCRYPTICERTREQUEST is disabled.' + $Issue.Issue = 'The IF_ENFORCEENCRYPTICERTREQUEST flag is disabled on this CA. It is possible to relay NTLM authentication to the ICPR RPC endpoint.' $Issue.Fix = @" certutil -config $CAFullname -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST -Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { +Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ $Issue.Revert = @" certutil -config $CAFullname -setreg CA\InterfaceFlags -IF_ENFORCEENCRYPTICERTREQUEST -Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { +Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ @@ -398,7 +399,9 @@ function Find-ESC13 { ActiveDirectoryRights = $entry.ActiveDirectoryRights LinkedGroup = $OidToCheck.'msDS-OIDToGroupLink' Issue = "$($entry.IdentityReference) can enroll in this Client " + - "Authentication template which is linked to $($OidToCheck.'msDS-OIDToGroupLink')." + "Authentication template which is linked to $($OidToCheck.'msDS-OIDToGroupLink')." + + "If $($entry.IdentityReference) uses this certificate for authentication, they will" + + "gain the rights of the linked group while the group membership appears empty." Fix = @" # Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' @@ -466,7 +469,7 @@ function Find-ESC2 { else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } - if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + if ( ($SID -notmatch $SafeUsers) -and ( ($entry.ActiveDirectoryRights -match 'ExtendedRight') -or ($entry.ActiveDirectoryRights -match 'GenericAll') ) ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name @@ -536,7 +539,7 @@ function Find-ESC3Condition1 { else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } - if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + if ( ($SID -notmatch $SafeUsers) -and ( ($entry.ActiveDirectoryRights -match 'ExtendedRight') -or ($entry.ActiveDirectoryRights -match 'GenericAll') ) ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name @@ -609,19 +612,20 @@ function Find-ESC3Condition2 { else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } - if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + if ( ($SID -notmatch $SafeUsers) -and ( ($entry.ActiveDirectoryRights -match 'ExtendedRight') -or ($entry.ActiveDirectoryRights -match 'GenericAll') ) ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval" - # Fix = @" + Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using an Enrollment Agent template." + Fix = '[TODO]' + # Fix = @" # `$Object = `'$($_.DistinguishedName)`' # Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} # "@ - # Revert = @" + # Revert = @" # `$Object = `'$($_.DistinguishedName)`' # Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} # "@ @@ -725,7 +729,7 @@ function Find-ESC4 { Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName - Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this template" + Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this template. $($_.nTSecurityDescriptor.Owner) can modify this template into an ESC1 template." Fix = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$PreferredOwner`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' @@ -763,7 +767,7 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this template" + Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this template. $($entry.IdentityReference) can modify this template into an ESC1 template." Fix = @" `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' foreach ( `$ace in `$ACL.access ) { @@ -877,7 +881,8 @@ function Find-ESC5 { Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName - Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this template" + objectClass = $_.objectClass + Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this object" Fix = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$PreferredOwner`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' @@ -911,6 +916,7 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL" Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName + objectClass = $_.objectClass IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this object" @@ -975,16 +981,17 @@ function Find-ESC6 { Revert = 'N/A' } if ($_.SANFlag -eq 'Yes') { - $Issue.Issue = 'EDITF_ATTRIBUTESUBJECTALTNAME2 is enabled.' + $Issue.Issue = 'EDITF_ATTRIBUTESUBJECTALTNAME2 is enabled. If Strong Mapping enforcement has been ' + + 'disabled on Domain Controllers, all templates will accept a SAN during enrollment.' $Issue.Fix = @" certutil -config $CAFullname -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 -Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { +Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ $Issue.Revert = @" certutil -config $CAFullname -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2 -Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { +Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ @@ -1040,7 +1047,7 @@ function Find-ESC8 { DistinguishedName = $_.DistinguishedName CAEnrollmentEndpoint = $endpoint.URL AuthType = $endpoint.Auth - Issue = 'An HTTP enrollment endpoint is available.' + Issue = 'An HTTP enrollment endpoint is available. It is possible to relay NTLM authentication to this HTTP endpoint.' Fix = @' Disable HTTP access and enforce HTTPS. Enable EPA. @@ -1050,7 +1057,7 @@ Disable NTLM authentication (if possible.) Technique = 'ESC8' } if ($endpoint.URL -match '^https:') { - $Issue.Issue = 'An HTTPS enrollment endpoint is available.' + $Issue.Issue = 'An HTTPS enrollment endpoint is available. It may be possible to relay NTLM authentication to this HTTPS endpoint.' $Issue.Fix = @' Ensure EPA is enabled. Disable NTLM authentication (if possible.) @@ -1224,18 +1231,21 @@ function Format-Result { if ($null -ne $Issue) { $UniqueIssue = $Issue.Technique | Sort-Object -Unique $Title = $($IssueTable[$UniqueIssue]) - Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForegroundColor Magenta -BackgroundColor Magenta -NoNewline; Write-Host + Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForegroundColor Black -BackgroundColor Magenta -NoNewline; Write-Host Write-Host " " -BackgroundColor Magenta -NoNewline - Write-Host $Title -BackgroundColor Magenta -ForegroundColor White -NoNewline + Write-Host $Title -BackgroundColor Magenta -ForegroundColor Black -NoNewline Write-Host " " -BackgroundColor Magenta -NoNewline; Write-Host - Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForegroundColor Magenta -BackgroundColor Magenta -NoNewline; Write-Host + Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForegroundColor Black -BackgroundColor Magenta -NoNewline; Write-Host switch ($Mode) { 0 { $Issue | Format-Table Technique, Name, Issue -Wrap } 1 { - if ($Issue.Technique -eq 'ESC8') { + if ($Issue.Technique -eq 'ESC5') { + $Issue | Format-List Technique, Name, objectClass, DistinguishedName, Issue, Fix + } + elseif ($Issue.Technique -eq 'ESC8') { $Issue | Format-List Technique, Name, DistinguishedName, CAEnrollmentEndpoint, AuthType, Issue, Fix } else { @@ -2170,7 +2180,7 @@ function New-Dictionary { Summary = '' FindIt = { Find-ESC13 } FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } - ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' + ReferenceUrls = 'https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53' }, [VulnerableConfigurationItem]@{ Name = 'Auditing' diff --git a/Private/Find-AuditingIssue.ps1 b/Private/Find-AuditingIssue.ps1 index 7a2e99d..4205190 100644 --- a/Private/Find-AuditingIssue.ps1 +++ b/Private/Find-AuditingIssue.ps1 @@ -45,7 +45,7 @@ Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'DETECT' - Issue = "Auditing is not fully enabled on $($_.CAFullName). Current value is $($_.AuditFilter)" + Issue = "Auditing is not fully enabled on $($_.CAFullName). Important security events may be left unrecorded." Fix = @" certutil.exe -config `'$($_.CAFullname)`' -setreg `'CA\AuditFilter`' 127 Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { diff --git a/Private/Find-ESC1.ps1 b/Private/Find-ESC1.ps1 index ab22a79..5c16e97 100644 --- a/Private/Find-ESC1.ps1 +++ b/Private/Find-ESC1.ps1 @@ -58,16 +58,16 @@ DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval" - Fix = @" + Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval." + Fix = @" `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ - Revert = @" + Revert = @" `$Object = `'$($_.DistinguishedName)`' -Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ - Technique = 'ESC1' + Technique = 'ESC1' } $Issue } diff --git a/Private/Find-ESC11.ps1 b/Private/Find-ESC11.ps1 index 87f4e9d..001eb89 100644 --- a/Private/Find-ESC11.ps1 +++ b/Private/Find-ESC11.ps1 @@ -41,16 +41,16 @@ Revert = 'N/A' } if ($_.InterfaceFlag -eq 'No') { - $Issue.Issue = 'IF_ENFORCEENCRYPTICERTREQUEST is disabled.' - $Issue.Fix = @" + $Issue.Issue = 'The IF_ENFORCEENCRYPTICERTREQUEST flag is disabled on this CA. It is possible to relay NTLM authentication to the ICPR RPC endpoint.' + $Issue.Fix = @" certutil -config $CAFullname -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST -Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { +Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ $Issue.Revert = @" certutil -config $CAFullname -setreg CA\InterfaceFlags -IF_ENFORCEENCRYPTICERTREQUEST -Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { +Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ diff --git a/Private/Find-ESC13.ps1 b/Private/Find-ESC13.ps1 index fb6047d..c668ba6 100644 --- a/Private/Find-ESC13.ps1 +++ b/Private/Find-ESC13.ps1 @@ -62,7 +62,9 @@ function Find-ESC13 { ActiveDirectoryRights = $entry.ActiveDirectoryRights LinkedGroup = $OidToCheck.'msDS-OIDToGroupLink' Issue = "$($entry.IdentityReference) can enroll in this Client " + - "Authentication template which is linked to $($OidToCheck.'msDS-OIDToGroupLink')." + "Authentication template which is linked to $($OidToCheck.'msDS-OIDToGroupLink')." + + "If $($entry.IdentityReference) uses this certificate for authentication, they will" + + "gain the rights of the linked group while the group membership appears empty." Fix = @" # Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' diff --git a/Private/Find-ESC2.ps1 b/Private/Find-ESC2.ps1 index 793bbe5..d3734ef 100644 --- a/Private/Find-ESC2.ps1 +++ b/Private/Find-ESC2.ps1 @@ -43,7 +43,7 @@ } else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } - if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + if ( ($SID -notmatch $SafeUsers) -and ( ($entry.ActiveDirectoryRights -match 'ExtendedRight') -or ($entry.ActiveDirectoryRights -match 'GenericAll') ) ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name @@ -51,11 +51,11 @@ IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights Issue = "$($entry.IdentityReference) can request a SubCA certificate without Manager Approval" - Fix = @" + Fix = @" `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ - Revert = @" + Revert = @" `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ diff --git a/Private/Find-ESC3Condition1.ps1 b/Private/Find-ESC3Condition1.ps1 index be25217..25438be 100644 --- a/Private/Find-ESC3Condition1.ps1 +++ b/Private/Find-ESC3Condition1.ps1 @@ -43,7 +43,7 @@ } else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } - if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + if ( ($SID -notmatch $SafeUsers) -and ( ($entry.ActiveDirectoryRights -match 'ExtendedRight') -or ($entry.ActiveDirectoryRights -match 'GenericAll') ) ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name @@ -51,12 +51,12 @@ IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights Issue = "$($entry.IdentityReference) can enroll in this Enrollment Agent template without Manager Approval" - Fix = @" + Fix = @" # Enabled Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ - Revert = @" + Revert = @" # Disable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} diff --git a/Private/Find-ESC3Condition2.ps1 b/Private/Find-ESC3Condition2.ps1 index c49c35f..3f00661 100644 --- a/Private/Find-ESC3Condition2.ps1 +++ b/Private/Find-ESC3Condition2.ps1 @@ -44,19 +44,20 @@ } else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } - if ( ($SID -notmatch $SafeUsers) -and ($entry.ActiveDirectoryRights -match 'ExtendedRight') ) { + if ( ($SID -notmatch $SafeUsers) -and ( ($entry.ActiveDirectoryRights -match 'ExtendedRight') -or ($entry.ActiveDirectoryRights -match 'GenericAll') ) ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval" -# Fix = @" + Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using an Enrollment Agent template." + Fix = '[TODO]' + # Fix = @" # `$Object = `'$($_.DistinguishedName)`' # Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} # "@ -# Revert = @" + # Revert = @" # `$Object = `'$($_.DistinguishedName)`' # Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} # "@ diff --git a/Private/Find-ESC4.ps1 b/Private/Find-ESC4.ps1 index 1f103be..e25ed6a 100644 --- a/Private/Find-ESC4.ps1 +++ b/Private/Find-ESC4.ps1 @@ -86,23 +86,23 @@ if ( ($_.objectClass -eq 'pKICertificateTemplate') -and ($SID -notmatch $SafeOwners) ) { $Issue = [pscustomobject]@{ - Forest = $_.CanonicalName.split('/')[0] - Name = $_.Name - DistinguishedName = $_.DistinguishedName - Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this template" - Fix = @" + Forest = $_.CanonicalName.split('/')[0] + Name = $_.Name + DistinguishedName = $_.DistinguishedName + Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this template. $($_.nTSecurityDescriptor.Owner) can modify this template into an ESC1 template." + Fix = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$PreferredOwner`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' `$ACL.SetOwner(`$Owner) Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL "@ - Revert = @" + Revert = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$($_.nTSecurityDescriptor.Owner)`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' `$ACL.SetOwner(`$Owner) Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL "@ - Technique = 'ESC4' + Technique = 'ESC4' } $Issue } @@ -119,14 +119,14 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL ($entry.AccessControlType -eq 'Allow') -and ($entry.ActiveDirectoryRights -match $DangerousRights) -and ($entry.ObjectType -notmatch $SafeObjectTypes) - ) { + ) { $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this template" + Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this template. $($entry.IdentityReference) can modify this template into an ESC1 template." Fix = @" `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' foreach ( `$ace in `$ACL.access ) { @@ -140,7 +140,7 @@ Set-Acl -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL Technique = 'ESC4' } - if ( $Mode -in @(1,3,4) ) { + if ( $Mode -in @(1, 3, 4) ) { Update-ESC4Remediation -Issue $Issue } diff --git a/Private/Find-ESC5.ps1 b/Private/Find-ESC5.ps1 index 25f3d31..b09461b 100644 --- a/Private/Find-ESC5.ps1 +++ b/Private/Find-ESC5.ps1 @@ -84,22 +84,23 @@ if ( ($_.objectClass -ne 'pKICertificateTemplate') -and ($SID -notmatch $SafeOwners) ) { $Issue = [pscustomobject]@{ - Forest = $_.CanonicalName.split('/')[0] - Name = $_.Name - DistinguishedName = $_.DistinguishedName - Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this template" - Fix = @" + Forest = $_.CanonicalName.split('/')[0] + Name = $_.Name + DistinguishedName = $_.DistinguishedName + objectClass = $_.objectClass + Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this object" + Fix = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$PreferredOwner`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' `$ACL.SetOwner(`$Owner) Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL "@ - Revert = " + Revert = " `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$($_.nTSecurityDescriptor.Owner)`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' `$ACL.SetOwner(`$Owner) Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL" - Technique = 'ESC5' + Technique = 'ESC5' } $Issue } @@ -120,6 +121,7 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL" Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName + objectClass = $_.objectClass IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this object" diff --git a/Private/Find-ESC6.ps1 b/Private/Find-ESC6.ps1 index 0969f38..01d7228 100644 --- a/Private/Find-ESC6.ps1 +++ b/Private/Find-ESC6.ps1 @@ -41,16 +41,17 @@ Revert = 'N/A' } if ($_.SANFlag -eq 'Yes') { - $Issue.Issue = 'EDITF_ATTRIBUTESUBJECTALTNAME2 is enabled.' - $Issue.Fix = @" + $Issue.Issue = 'EDITF_ATTRIBUTESUBJECTALTNAME2 is enabled. If Strong Mapping enforcement has been ' + + 'disabled on Domain Controllers, all templates will accept a SAN during enrollment.' + $Issue.Fix = @" certutil -config $CAFullname -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 -Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { +Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ $Issue.Revert = @" certutil -config $CAFullname -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2 -Invoke-Command -ComputerName `"$($_.dNSHostName)`" -ScriptBlock { +Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ diff --git a/Private/Find-ESC8.ps1 b/Private/Find-ESC8.ps1 index 6d8137e..96f8bae 100644 --- a/Private/Find-ESC8.ps1 +++ b/Private/Find-ESC8.ps1 @@ -44,7 +44,7 @@ DistinguishedName = $_.DistinguishedName CAEnrollmentEndpoint = $endpoint.URL AuthType = $endpoint.Auth - Issue = 'An HTTP enrollment endpoint is available.' + Issue = 'An HTTP enrollment endpoint is available. It is possible to relay NTLM authentication to this HTTP endpoint.' Fix = @' Disable HTTP access and enforce HTTPS. Enable EPA. @@ -54,8 +54,8 @@ Disable NTLM authentication (if possible.) Technique = 'ESC8' } if ($endpoint.URL -match '^https:') { - $Issue.Issue = 'An HTTPS enrollment endpoint is available.' - $Issue.Fix = @' + $Issue.Issue = 'An HTTPS enrollment endpoint is available. It may be possible to relay NTLM authentication to this HTTPS endpoint.' + $Issue.Fix = @' Ensure EPA is enabled. Disable NTLM authentication (if possible.) '@ diff --git a/Private/Format-Result.ps1 b/Private/Format-Result.ps1 index 6a661fa..a58cccc 100644 --- a/Private/Format-Result.ps1 +++ b/Private/Format-Result.ps1 @@ -46,18 +46,20 @@ function Format-Result { if ($null -ne $Issue) { $UniqueIssue = $Issue.Technique | Sort-Object -Unique $Title = $($IssueTable[$UniqueIssue]) - Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForeGroundColor Magenta -BackgroundColor Magenta -NoNewline; Write-Host + Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForeGroundColor Black -BackgroundColor Magenta -NoNewline; Write-Host Write-Host " " -BackgroundColor Magenta -NoNewline - Write-Host $Title -BackgroundColor Magenta -ForeGroundColor White -NoNewline + Write-Host $Title -BackgroundColor Magenta -ForeGroundColor Black -NoNewline Write-Host " " -BackgroundColor Magenta -NoNewline; Write-Host - Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForeGroundColor Magenta -BackgroundColor Magenta -NoNewline; Write-Host + Write-Host "$('-'*($($Title.ToString().Length + 10)))" -ForeGroundColor Black -BackgroundColor Magenta -NoNewline; Write-Host switch ($Mode) { 0 { $Issue | Format-Table Technique, Name, Issue -Wrap } 1 { - if ($Issue.Technique -eq 'ESC8') { + if ($Issue.Technique -eq 'ESC5') { + $Issue | Format-List Technique, Name, objectClass, DistinguishedName, Issue, Fix + } elseif ($Issue.Technique -eq 'ESC8') { $Issue | Format-List Technique, Name, DistinguishedName, CAEnrollmentEndpoint, AuthType, Issue, Fix } else { $Issue | Format-List Technique, Name, DistinguishedName, Issue, Fix diff --git a/Private/New-Dictionary.ps1 b/Private/New-Dictionary.ps1 index ba7c566..0ba99e8 100644 --- a/Private/New-Dictionary.ps1 +++ b/Private/New-Dictionary.ps1 @@ -141,7 +141,7 @@ function New-Dictionary { Summary = '' FindIt = {Find-ESC13} FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} - ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' + ReferenceUrls = 'https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53' }, [VulnerableConfigurationItem]@{ Name = 'Auditing' From ce1f882316ebcfe53e30304ca9c74da13ae9fa2a Mon Sep 17 00:00:00 2001 From: Jake Hildreth Date: Sat, 9 Nov 2024 09:57:18 -0600 Subject: [PATCH 12/14] Gonna be releasing more frequently, so changed Versioning to yyyy.M.d for now --- Build/Build-Module.ps1 | 4 ++-- Invoke-Locksmith.ps1 | 2 +- Locksmith.psd1 | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Build/Build-Module.ps1 b/Build/Build-Module.ps1 index 410bcfd..42ebc8d 100644 --- a/Build/Build-Module.ps1 +++ b/Build/Build-Module.ps1 @@ -1,5 +1,5 @@ param ( - # A CalVer string if you need to manually override the default yyyy.M version string. + # A CalVer string if you need to manually override the default yyyy.M.d version string. [string]$CalVer ) @@ -22,7 +22,7 @@ Import-Module -Name PSPublishModule -Force Build-Module -ModuleName 'Locksmith' { # Usual defaults as per standard module $Manifest = [ordered] @{ - ModuleVersion = if ($Calver) {$CalVer} else {(Get-Date -Format yyyy.M)} + ModuleVersion = if ($Calver) {$CalVer} else {(Get-Date -Format yyyy.M.d)} CompatiblePSEditions = @('Desktop', 'Core') GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' Author = 'Jake Hildreth' diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 228e5f3..5e5cd92 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -2965,7 +2965,7 @@ function Invoke-Locksmith { [System.Management.Automation.PSCredential]$Credential ) - $Version = '2024.11' + $Version = '2024.11.9' $LogoPart1 = @" _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| diff --git a/Locksmith.psd1 b/Locksmith.psd1 index 3f455f8..443d92e 100644 --- a/Locksmith.psd1 +++ b/Locksmith.psd1 @@ -7,7 +7,7 @@ Description = 'A small tool to find and fix common misconfigurations in Active Directory Certificate Services.' FunctionsToExport = @('*') GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' - ModuleVersion = '2024.11' + ModuleVersion = '2024.11.9' PowerShellVersion = '5.1' PrivateData = @{ PSData = @{ From 0e41b969caacd40a221d87779d94e8d88879993b Mon Sep 17 00:00:00 2001 From: Jake Hildreth Date: Sun, 10 Nov 2024 06:00:00 -0600 Subject: [PATCH 13/14] Updated descriptions, fixes, and reverts on every issue type. --- Invoke-Locksmith.ps1 | 336 +++++++++++++++++----- Locksmith.psd1 | 2 +- Private/ConvertFrom-IdentityReference.ps1 | 2 +- Private/Find-AuditingIssue.ps1 | 4 +- Private/Find-ESC1.ps1 | 13 +- Private/Find-ESC11.ps1 | 21 +- Private/Find-ESC13.ps1 | 12 +- Private/Find-ESC2.ps1 | 22 +- Private/Find-ESC3Condition1.ps1 | 15 +- Private/Find-ESC3Condition2.ps1 | 32 ++- Private/Find-ESC4.ps1 | 20 +- Private/Find-ESC5.ps1 | 119 +++++++- Private/Find-ESC6.ps1 | 22 +- Private/Find-ESC8.ps1 | 26 +- Private/Find-ESC9.ps1 | 2 +- Private/Format-Result.ps1 | 2 +- Private/Invoke-Remediation.ps1 | 12 +- Private/Set-Severity.ps1 | 2 +- Public/Invoke-Locksmith.ps1 | 2 +- 19 files changed, 537 insertions(+), 129 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 5e5cd92..14afed3 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -28,7 +28,7 @@ function ConvertFrom-IdentityReference { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$Object ) @@ -158,7 +158,7 @@ function Find-AuditingIssue { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects ) @@ -171,7 +171,7 @@ function Find-AuditingIssue { Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'DETECT' - Issue = "Auditing is not fully enabled on $($_.CAFullName). Important security events may be left unrecorded." + Issue = "Auditing is not fully enabled on $($_.CAFullName). Important security events may go unnoticed." Fix = @" certutil.exe -config `'$($_.CAFullname)`' -setreg `'CA\AuditFilter`' 127 Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { @@ -255,12 +255,23 @@ function Find-ESC1 { DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval." + Issue = @" +$($entry.IdentityReference) can provide a Subject Alternative Name (SAN) while +enrolling in this Client Authentication template, and enrollment does not require +Manager Approval. + +The resultant certificate can be used by an attacker to authenticate as any +principal listed in the SAN up to and including Domain Admins, Enterprise Admins, +or Domain Controllers. + +"@ Fix = @" +# Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" +# Disable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ @@ -296,7 +307,7 @@ function Find-ESC11 { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects ) process { @@ -310,20 +321,35 @@ function Find-ESC11 { Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'ESC11' - Issue = $_.AuditFilter + Issue = $_.InterfaceFlag Fix = 'N/A' Revert = 'N/A' } if ($_.InterfaceFlag -eq 'No') { - $Issue.Issue = 'The IF_ENFORCEENCRYPTICERTREQUEST flag is disabled on this CA. It is possible to relay NTLM authentication to the ICPR RPC endpoint.' + $Issue.Issue = @" +The IF_ENFORCEENCRYPTICERTREQUEST flag is disabled on this Certification +Authority (CA). It is possible to relay NTLM authentication to the RPC interface +of this CA. + +If the LAN Manager authentication level of any domain in this forest is 2 or +less, an attacker can coerce authentication from a Domain Controller (DC) to +receive a certificate which can be used to authenticate as that DC. + +"@ $Issue.Fix = @" +# Enable the flag certutil -config $CAFullname -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST + +# Restart the Ceritification Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ $Issue.Revert = @" +# Disable the flag certutil -config $CAFullname -setreg CA\InterfaceFlags -IF_ENFORCEENCRYPTICERTREQUEST + +# Restart the Ceritification Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } @@ -398,10 +424,14 @@ function Find-ESC13 { IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights LinkedGroup = $OidToCheck.'msDS-OIDToGroupLink' - Issue = "$($entry.IdentityReference) can enroll in this Client " + - "Authentication template which is linked to $($OidToCheck.'msDS-OIDToGroupLink')." + - "If $($entry.IdentityReference) uses this certificate for authentication, they will" + - "gain the rights of the linked group while the group membership appears empty." + Issue = @" +$($entry.IdentityReference) can enroll in this Client Authentication template +which is linked to the group $($OidToCheck.'msDS-OIDToGroupLink'). + +If $($entry.IdentityReference) uses this certificate for authentication, they +will gain the rights of the linked group while the group membership appears empty. + +"@ Fix = @" # Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' @@ -450,9 +480,9 @@ function Find-ESC2 { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [string]$SafeUsers ) $ADCSObjects | Where-Object { @@ -476,12 +506,28 @@ function Find-ESC2 { DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can request a SubCA certificate without Manager Approval" + Issue = @" +$($entry.IdentityReference) can use this template to request a Subordinate +Certification Authority (SubCA) certificate without Manager Approval. + +The resultant certificate can be used by an attacker to instantiate their own +SubCA which is trusted by AD. + +By default, certificates created from this attacker-controlled SubCA cannot be +used for authentication, but they can be used for other purposes such as TLS +certs and code signing. + +However, if an attacker can modify the NtAuthCertificates object (see ESC5), +they can convert their rogue CA into one trusted for authentication. + +"@ Fix = @" +# Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" +# Disable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ @@ -520,9 +566,9 @@ function Find-ESC3Condition1 { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$SafeUsers ) $ADCSObjects | Where-Object { @@ -546,9 +592,16 @@ function Find-ESC3Condition1 { DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Enrollment Agent template without Manager Approval" + Issue = @" +$($entry.IdentityReference) can use this template to request an Enrollment Agent +certificate without Manager Approval. + +The resulting certificate can be used to enroll in any template that requires +an Enrollment Agent to submit the request. + +"@ Fix = @" -# Enabled Manager Approval +# Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ @@ -592,9 +645,9 @@ function Find-ESC3Condition2 { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$SafeUsers ) $ADCSObjects | Where-Object { @@ -619,16 +672,24 @@ function Find-ESC3Condition2 { DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using an Enrollment Agent template." - Fix = '[TODO]' - # Fix = @" - # `$Object = `'$($_.DistinguishedName)`' - # Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} - # "@ - # Revert = @" - # `$Object = `'$($_.DistinguishedName)`' - # Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} - # "@ + Issue = @" +If the holder of an Enrollment Agent certificate requests a certificate using +this template, they will receive a certificate which allows them to authenticate +as $($entry.IdentityReference). + +"@ + Fix = @" +First, eliminate unused Enrollment Agent templates. +Then, tightly scope any Enrollment Agent templates that remain and: +# Enable Manager Approval +`$Object = `'$($_.DistinguishedName)`' +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} +"@ + Revert = @" +# Disable Manager Approval +`$Object = `'$($_.DistinguishedName)`' +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} +"@ Technique = 'ESC3' } $Issue @@ -701,15 +762,15 @@ function Find-ESC4 { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $DangerousRights, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeOwners, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeUsers, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeObjectTypes, [int]$Mode ) @@ -729,7 +790,11 @@ function Find-ESC4 { Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName - Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this template. $($_.nTSecurityDescriptor.Owner) can modify this template into an ESC1 template." + Issue = @" +$($_.nTSecurityDescriptor.Owner) has Owner rights on this template and can +modify it into a template that can create ESC1, ESC2, and ESC3 templates. + +"@ Fix = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$PreferredOwner`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' @@ -767,7 +832,9 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this template. $($entry.IdentityReference) can modify this template into an ESC1 template." + Issue = "$($entry.IdentityReference) has been granted " + + "$($entry.ActiveDirectoryRights) rights on this template.`n" + + "$($entry.IdentityReference) can likely modify this template into an ESC1 template." Fix = @" `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' foreach ( `$ace in `$ACL.access ) { @@ -854,15 +921,15 @@ function Find-ESC5 { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $DangerousRights, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeOwners, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeUsers, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeObjectTypes ) $ADCSObjects | ForEach-Object { @@ -877,12 +944,62 @@ function Find-ESC5 { } if ( ($_.objectClass -ne 'pKICertificateTemplate') -and ($SID -notmatch $SafeOwners) ) { + switch ($_.objectClass) { + computer { + $IssueDetail = @" +This computer is hosting a Certification Authority (CA). $($_.nTSecurityDescriptor.Owner) +has full control of this object. + +There is little reason for anyone other than AD Admins to have elevated rights +to this CA host. +"@ + } + 'msPKI-Cert-Template-OID' { + $IssueDetail = @" +This Object Identifier (OID) can be modified into an Application Policy and linked +to an empty Universal Group. + +If $($_.nTSecurityDescriptor.Owner) also has control over a certificate template +(see ESC4), an attacker could link this Application Policy to the template. Once +linked, any certificates issued from that template would allow an attacker to +act as a member of the linked group (see ESC13). +"@ + } + pKIEnrollmentService { + $IssueDetail = @" +$($_.nTSecurityDescriptor.Owner) can use these elevated rights to publish currently +unpublished templates. + +If $($_.nTSecurityDescriptor.Owner) also has control over an unpublished certificate +template (see ESC4), they could modify the template into an ESC1 template then +publish the certificate. This published certificate could be use for privilege +escalation and persistence. +"@ + } + } + if ($_.objectClass -eq 'certificationAuthority' -and $_.Name -eq 'NTAuthCertificates') { + $IssueDetail = @" +The NTAuthCertificates object determines which Certification Authorities are +trusted by Active Directory (AD) for client authentication of all forms. + +$($_.nTSecurityDescriptor.Owner) can use their granted rights on NTAuthCertificates +to add their own rogue CAs. Once the rogue CA is trusted, any client authentication +certificates generated by the it can be used by the attacker. +"@ + } + $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName objectClass = $_.objectClass - Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this object" + Issue = @" +$($_.nTSecurityDescriptor.Owner) has Owner rights on this object. They are able +to modify this object in whatever way they wish. + +$IssueDetail + +"@ Fix = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$PreferredOwner`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' @@ -907,6 +1024,51 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL" else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } + + switch ($_.objectClass) { + computer { + $IssueDetail = @" +This computer is hosting a Certification Authority (CA). It is likely +$($entry.IdentityReference) can take control of this object. + +There is little reason for anyone other than AD Admins to have elevated rights +to this CA host. +"@ + } + 'msPKI-Cert-Template-OID' { + $IssueDetail = @" +This Object Identifier (OID) can be modified into an Application Policy and linked +to an empty Universal Group. + +If $($entry.IdentityReference) also has control over a certificate template +(see ESC4), an attacker could link this Application Policy to the template. Once +linked, any certificates issued from that template would allow an attacker to +act as a member of the linked group (see ESC13). +"@ + } + pKIEnrollmentService { + $IssueDetail = @" +$($entry.IdentityReference) can use these elevated rights to publish currently +unpublished templates. + +If $($entry.IdentityReference) also has control over an unpublished certificate +template (see ESC4), they could modify the template into an ESC1 template then +publish the certificate. This published certificate could be use for privilege +escalation and persistence. +"@ + } + } + if ($_.objectClass -eq 'certificationAuthority' -and $_.Name -eq 'NTAuthCertificates') { + $IssueDetail = @" +The NTAuthCertificates object determines which Certification Authorities are +trusted by Active Directory (AD) for client authentication of all forms. + +$($entry.IdentityReference) can use their granted rights on NTAuthCertificates +to add their own rogue CAs. Once the rogue CA is trusted, any client authentication +certificates generated by the it can be used by the attacker. +"@ + } + if ( ($_.objectClass -ne 'pKICertificateTemplate') -and ($SID -notmatch $SafeUsers) -and ($entry.AccessControlType -eq 'Allow') -and @@ -916,14 +1078,20 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL" Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName - objectClass = $_.objectClass IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this object" + Issue = @" +$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) elevated rights +on this $($_.objectClass) object. + +$IssueDetail + +"@ Fix = @" `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' foreach ( `$ace in `$ACL.access ) { - if ( (`$ace.IdentityReference.Value -like '$($Principal.Value)' ) -and ( `$ace.ActiveDirectoryRights -notmatch '^ExtendedRight$') ) { + if ( (`$ace.IdentityReference.Value -like '$($Principal.Value)' ) -and + ( `$ace.ActiveDirectoryRights -notmatch '^ExtendedRight$') ) { `$ACL.RemoveAccessRule(`$ace) | Out-Null } } @@ -962,7 +1130,7 @@ function Find-ESC6 { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects ) process { @@ -981,16 +1149,32 @@ function Find-ESC6 { Revert = 'N/A' } if ($_.SANFlag -eq 'Yes') { - $Issue.Issue = 'EDITF_ATTRIBUTESUBJECTALTNAME2 is enabled. If Strong Mapping enforcement has been ' + - 'disabled on Domain Controllers, all templates will accept a SAN during enrollment.' + $Issue.Issue = @" +The dangerous EDITF_ATTRIBUTESUBJECTALTNAME2 flag is enabled on $CAFullname. +All templates published on this CA will accept a Subject Alternative Name (SAN) +during enrollment even if the template is not specifically configured to allow a SAN. + +As of May 2022, Microsoft has neutered this situation by requiring all SANs to +be strongly mapped to certificates. + +However, if strong mapping has been explicitly disabled on Domain Controllers, +this configuration remains vulnerable to privilege escalation attacks. + +"@ $Issue.Fix = @" +# Disable the flag certutil -config $CAFullname -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 + +# Restart the Ceritification Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ $Issue.Revert = @" +# Enable the flag certutil -config $CAFullname -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2 + +# Restart the Ceritification Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } @@ -1032,7 +1216,7 @@ function Find-ESC8 { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects ) @@ -1047,7 +1231,16 @@ function Find-ESC8 { DistinguishedName = $_.DistinguishedName CAEnrollmentEndpoint = $endpoint.URL AuthType = $endpoint.Auth - Issue = 'An HTTP enrollment endpoint is available. It is possible to relay NTLM authentication to this HTTP endpoint.' + Issue = @' +An HTTP enrollment endpoint is available. It is possible to relay NTLM +authentication to this HTTP endpoint. + +If the LAN Manager authentication level of any domain in this forest is 2 or +less, an attacker can coerce authentication from a Domain Controller (DC) and +relay it to this HTTP enrollment enpoint to receive a certificate which can be +used to authenticate as that DC. + +'@ Fix = @' Disable HTTP access and enforce HTTPS. Enable EPA. @@ -1057,7 +1250,18 @@ Disable NTLM authentication (if possible.) Technique = 'ESC8' } if ($endpoint.URL -match '^https:') { - $Issue.Issue = 'An HTTPS enrollment endpoint is available. It may be possible to relay NTLM authentication to this HTTPS endpoint.' + $Issue.Issue = @' +An HTTPS enrollment endpoint is available. It may be possible to relay NTLM +authentication to this HTTPS endpoint. Enabling IIS Extended Protection for +Authentication or disabling NTLM authentication completely, NTLM relay is not +possible. + +If those protection are not in place, and the LAN Manager authentication level +of any domain in this forest is 2 or less, an attacker can coerce authentication +from a Domain Controller (DC) and relay it to this HTTPS enrollment enpoint to +receive a certificate which can be used to authenticate as that DC. + +'@ $Issue.Fix = @' Ensure EPA is enabled. Disable NTLM authentication (if possible.) @@ -1121,7 +1325,7 @@ function Find-ESC9 { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects ) @@ -1211,7 +1415,7 @@ function Format-Result { [CmdletBinding()] param( $Issue, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [int]$Mode ) @@ -1603,7 +1807,7 @@ function Invoke-Remediation { $AuditingIssues | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "Auditing is not fully enabled on Certification Authority `"$($_.Name)`".`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -1636,7 +1840,7 @@ function Invoke-Remediation { $ESC1 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "Security Principals can enroll in `"$($_.Name)`" template using a Subject Alternative Name without Manager Approval.`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -1669,7 +1873,7 @@ function Invoke-Remediation { $ESC2 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "Security Principals can enroll in `"$($_.Name)`" template and create a Subordinate Certification Authority without Manager Approval.`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -1768,7 +1972,7 @@ function Invoke-Remediation { $ESC6 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "The Certification Authority `"$($_.Name)`" has the dangerous EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled.`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -1803,7 +2007,7 @@ function Invoke-Remediation { $ESC11 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "The Certification Authority `"$($_.Name)`" has the IF_ENFORCEENCRYPTICERTREQUEST flag disabled.`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -1838,7 +2042,7 @@ function Invoke-Remediation { $ESC13 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "Security Principals can enroll in `"$($_.Name)`" template which is linked to $($_.LinkedGroup).`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -2425,7 +2629,7 @@ function Set-Severity { [OutputType([string])] [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$Issue ) foreach ($Finding in $Issue) { @@ -2471,9 +2675,9 @@ function Test-IsADAdmin { #> if ( # Need to test to make sure this checks domain groups and not local groups, particularly for 'Administrators' (reference SID instead of name?). - ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Domain Admins") -or - ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrators") -or - ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Enterprise Admins") + ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('512') -or + ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('544') -or + ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('519') ) { Return $true } @@ -2880,7 +3084,7 @@ function Invoke-Locksmith { Finds the most common malconfigurations of Active Directory Certificate Services (AD CS). .DESCRIPTION - Locksmith uses the Active Directory (AD) Powershell (PS) module to identify 7 misconfigurations + Locksmith uses the Active Directory (AD) Powershell (PS) module to identify 10 misconfigurations commonly found in Enterprise mode AD CS installations. .COMPONENT @@ -2965,7 +3169,7 @@ function Invoke-Locksmith { [System.Management.Automation.PSCredential]$Credential ) - $Version = '2024.11.9' + $Version = '2024.11.10' $LogoPart1 = @" _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| diff --git a/Locksmith.psd1 b/Locksmith.psd1 index 443d92e..c5564ac 100644 --- a/Locksmith.psd1 +++ b/Locksmith.psd1 @@ -7,7 +7,7 @@ Description = 'A small tool to find and fix common misconfigurations in Active Directory Certificate Services.' FunctionsToExport = @('*') GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' - ModuleVersion = '2024.11.9' + ModuleVersion = '2024.11.10' PowerShellVersion = '5.1' PrivateData = @{ PSData = @{ diff --git a/Private/ConvertFrom-IdentityReference.ps1 b/Private/ConvertFrom-IdentityReference.ps1 index 5e3ec68..79f2a5a 100644 --- a/Private/ConvertFrom-IdentityReference.ps1 +++ b/Private/ConvertFrom-IdentityReference.ps1 @@ -22,7 +22,7 @@ function ConvertFrom-IdentityReference { #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$Object ) diff --git a/Private/Find-AuditingIssue.ps1 b/Private/Find-AuditingIssue.ps1 index 4205190..4b42398 100644 --- a/Private/Find-AuditingIssue.ps1 +++ b/Private/Find-AuditingIssue.ps1 @@ -32,7 +32,7 @@ [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects ) @@ -45,7 +45,7 @@ Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'DETECT' - Issue = "Auditing is not fully enabled on $($_.CAFullName). Important security events may be left unrecorded." + Issue = "Auditing is not fully enabled on $($_.CAFullName). Important security events may go unnoticed." Fix = @" certutil.exe -config `'$($_.CAFullname)`' -setreg `'CA\AuditFilter`' 127 Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { diff --git a/Private/Find-ESC1.ps1 b/Private/Find-ESC1.ps1 index 5c16e97..f632b2f 100644 --- a/Private/Find-ESC1.ps1 +++ b/Private/Find-ESC1.ps1 @@ -58,12 +58,23 @@ DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using a SAN without Manager Approval." + Issue = @" +$($entry.IdentityReference) can provide a Subject Alternative Name (SAN) while +enrolling in this Client Authentication template, and enrollment does not require +Manager Approval. + +The resultant certificate can be used by an attacker to authenticate as any +principal listed in the SAN up to and including Domain Admins, Enterprise Admins, +or Domain Controllers. + +"@ Fix = @" +# Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" +# Disable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ diff --git a/Private/Find-ESC11.ps1 b/Private/Find-ESC11.ps1 index 001eb89..da501b8 100644 --- a/Private/Find-ESC11.ps1 +++ b/Private/Find-ESC11.ps1 @@ -22,7 +22,7 @@ #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects ) process { @@ -36,20 +36,35 @@ Name = $_.Name DistinguishedName = $_.DistinguishedName Technique = 'ESC11' - Issue = $_.AuditFilter + Issue = $_.InterfaceFlag Fix = 'N/A' Revert = 'N/A' } if ($_.InterfaceFlag -eq 'No') { - $Issue.Issue = 'The IF_ENFORCEENCRYPTICERTREQUEST flag is disabled on this CA. It is possible to relay NTLM authentication to the ICPR RPC endpoint.' + $Issue.Issue = @" +The IF_ENFORCEENCRYPTICERTREQUEST flag is disabled on this Certification +Authority (CA). It is possible to relay NTLM authentication to the RPC interface +of this CA. + +If the LAN Manager authentication level of any domain in this forest is 2 or +less, an attacker can coerce authentication from a Domain Controller (DC) to +receive a certificate which can be used to authenticate as that DC. + +"@ $Issue.Fix = @" +# Enable the flag certutil -config $CAFullname -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST + +# Restart the Ceritification Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ $Issue.Revert = @" +# Disable the flag certutil -config $CAFullname -setreg CA\InterfaceFlags -IF_ENFORCEENCRYPTICERTREQUEST + +# Restart the Ceritification Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } diff --git a/Private/Find-ESC13.ps1 b/Private/Find-ESC13.ps1 index c668ba6..d3007d1 100644 --- a/Private/Find-ESC13.ps1 +++ b/Private/Find-ESC13.ps1 @@ -61,10 +61,14 @@ function Find-ESC13 { IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights LinkedGroup = $OidToCheck.'msDS-OIDToGroupLink' - Issue = "$($entry.IdentityReference) can enroll in this Client " + - "Authentication template which is linked to $($OidToCheck.'msDS-OIDToGroupLink')." + - "If $($entry.IdentityReference) uses this certificate for authentication, they will" + - "gain the rights of the linked group while the group membership appears empty." + Issue = @" +$($entry.IdentityReference) can enroll in this Client Authentication template +which is linked to the group $($OidToCheck.'msDS-OIDToGroupLink'). + +If $($entry.IdentityReference) uses this certificate for authentication, they +will gain the rights of the linked group while the group membership appears empty. + +"@ Fix = @" # Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' diff --git a/Private/Find-ESC2.ps1 b/Private/Find-ESC2.ps1 index d3734ef..b45b1c7 100644 --- a/Private/Find-ESC2.ps1 +++ b/Private/Find-ESC2.ps1 @@ -25,9 +25,9 @@ #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [string]$SafeUsers ) $ADCSObjects | Where-Object { @@ -50,12 +50,28 @@ DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can request a SubCA certificate without Manager Approval" + Issue = @" +$($entry.IdentityReference) can use this template to request a Subordinate +Certification Authority (SubCA) certificate without Manager Approval. + +The resultant certificate can be used by an attacker to instantiate their own +SubCA which is trusted by AD. + +By default, certificates created from this attacker-controlled SubCA cannot be +used for authentication, but they can be used for other purposes such as TLS +certs and code signing. + +However, if an attacker can modify the NtAuthCertificates object (see ESC5), +they can convert their rogue CA into one trusted for authentication. + +"@ Fix = @" +# Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ Revert = @" +# Disable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} "@ diff --git a/Private/Find-ESC3Condition1.ps1 b/Private/Find-ESC3Condition1.ps1 index 25438be..b9fee01 100644 --- a/Private/Find-ESC3Condition1.ps1 +++ b/Private/Find-ESC3Condition1.ps1 @@ -25,9 +25,9 @@ #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$SafeUsers ) $ADCSObjects | Where-Object { @@ -50,9 +50,16 @@ DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Enrollment Agent template without Manager Approval" + Issue = @" +$($entry.IdentityReference) can use this template to request an Enrollment Agent +certificate without Manager Approval. + +The resulting certificate can be used to enroll in any template that requires +an Enrollment Agent to submit the request. + +"@ Fix = @" -# Enabled Manager Approval +# Enable Manager Approval `$Object = `'$($_.DistinguishedName)`' Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} "@ diff --git a/Private/Find-ESC3Condition2.ps1 b/Private/Find-ESC3Condition2.ps1 index 3f00661..4a82fc3 100644 --- a/Private/Find-ESC3Condition2.ps1 +++ b/Private/Find-ESC3Condition2.ps1 @@ -25,9 +25,9 @@ #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$SafeUsers ) $ADCSObjects | Where-Object { @@ -51,16 +51,24 @@ DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) can enroll in this Client Authentication template using an Enrollment Agent template." - Fix = '[TODO]' - # Fix = @" -# `$Object = `'$($_.DistinguishedName)`' -# Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 0} -# "@ - # Revert = @" -# `$Object = `'$($_.DistinguishedName)`' -# Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Certificate-Name-Flag' = 1} -# "@ + Issue = @" +If the holder of an Enrollment Agent certificate requests a certificate using +this template, they will receive a certificate which allows them to authenticate +as $($entry.IdentityReference). + +"@ + Fix = @" +First, eliminate unused Enrollment Agent templates. +Then, tightly scope any Enrollment Agent templates that remain and: +# Enable Manager Approval +`$Object = `'$($_.DistinguishedName)`' +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2} +"@ + Revert = @" +# Disable Manager Approval +`$Object = `'$($_.DistinguishedName)`' +Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0} +"@ Technique = 'ESC3' } $Issue diff --git a/Private/Find-ESC4.ps1 b/Private/Find-ESC4.ps1 index e25ed6a..0c5ce72 100644 --- a/Private/Find-ESC4.ps1 +++ b/Private/Find-ESC4.ps1 @@ -62,15 +62,15 @@ #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $DangerousRights, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeOwners, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeUsers, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeObjectTypes, [int]$Mode ) @@ -89,7 +89,11 @@ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName - Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this template. $($_.nTSecurityDescriptor.Owner) can modify this template into an ESC1 template." + Issue = @" +$($_.nTSecurityDescriptor.Owner) has Owner rights on this template and can +modify it into a template that can create ESC1, ESC2, and ESC3 templates. + +"@ Fix = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$PreferredOwner`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' @@ -126,7 +130,9 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL DistinguishedName = $_.DistinguishedName IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this template. $($entry.IdentityReference) can modify this template into an ESC1 template." + Issue = "$($entry.IdentityReference) has been granted " + + "$($entry.ActiveDirectoryRights) rights on this template.`n" + + "$($entry.IdentityReference) can likely modify this template into an ESC1 template." Fix = @" `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' foreach ( `$ace in `$ACL.access ) { diff --git a/Private/Find-ESC5.ps1 b/Private/Find-ESC5.ps1 index b09461b..02ee876 100644 --- a/Private/Find-ESC5.ps1 +++ b/Private/Find-ESC5.ps1 @@ -61,15 +61,15 @@ #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $DangerousRights, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeOwners, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeUsers, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $SafeObjectTypes ) $ADCSObjects | ForEach-Object { @@ -83,12 +83,62 @@ } if ( ($_.objectClass -ne 'pKICertificateTemplate') -and ($SID -notmatch $SafeOwners) ) { + switch ($_.objectClass) { + computer { + $IssueDetail = @" +This computer is hosting a Certification Authority (CA). $($_.nTSecurityDescriptor.Owner) +has full control of this object. + +There is little reason for anyone other than AD Admins to have elevated rights +to this CA host. +"@ + } + 'msPKI-Cert-Template-OID' { + $IssueDetail = @" +This Object Identifier (OID) can be modified into an Application Policy and linked +to an empty Universal Group. + +If $($_.nTSecurityDescriptor.Owner) also has control over a certificate template +(see ESC4), an attacker could link this Application Policy to the template. Once +linked, any certificates issued from that template would allow an attacker to +act as a member of the linked group (see ESC13). +"@ + } + pKIEnrollmentService { + $IssueDetail = @" +$($_.nTSecurityDescriptor.Owner) can use these elevated rights to publish currently +unpublished templates. + +If $($_.nTSecurityDescriptor.Owner) also has control over an unpublished certificate +template (see ESC4), they could modify the template into an ESC1 template then +publish the certificate. This published certificate could be use for privilege +escalation and persistence. +"@ + } + } + if ($_.objectClass -eq 'certificationAuthority' -and $_.Name -eq 'NTAuthCertificates') { + $IssueDetail = @" +The NTAuthCertificates object determines which Certification Authorities are +trusted by Active Directory (AD) for client authentication of all forms. + +$($_.nTSecurityDescriptor.Owner) can use their granted rights on NTAuthCertificates +to add their own rogue CAs. Once the rogue CA is trusted, any client authentication +certificates generated by the it can be used by the attacker. +"@ + } + $Issue = [pscustomobject]@{ Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName objectClass = $_.objectClass - Issue = "$($_.nTSecurityDescriptor.Owner) has Owner rights on this object" + Issue = @" +$($_.nTSecurityDescriptor.Owner) has Owner rights on this object. They are able +to modify this object in whatever way they wish. + +$IssueDetail + +"@ Fix = @" `$Owner = New-Object System.Security.Principal.SecurityIdentifier(`'$PreferredOwner`') `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' @@ -112,6 +162,51 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL" } else { $SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value } + + switch ($_.objectClass) { + computer { + $IssueDetail = @" +This computer is hosting a Certification Authority (CA). It is likely +$($entry.IdentityReference) can take control of this object. + +There is little reason for anyone other than AD Admins to have elevated rights +to this CA host. +"@ + } + 'msPKI-Cert-Template-OID' { + $IssueDetail = @" +This Object Identifier (OID) can be modified into an Application Policy and linked +to an empty Universal Group. + +If $($entry.IdentityReference) also has control over a certificate template +(see ESC4), an attacker could link this Application Policy to the template. Once +linked, any certificates issued from that template would allow an attacker to +act as a member of the linked group (see ESC13). +"@ + } + pKIEnrollmentService { + $IssueDetail = @" +$($entry.IdentityReference) can use these elevated rights to publish currently +unpublished templates. + +If $($entry.IdentityReference) also has control over an unpublished certificate +template (see ESC4), they could modify the template into an ESC1 template then +publish the certificate. This published certificate could be use for privilege +escalation and persistence. +"@ + } + } + if ($_.objectClass -eq 'certificationAuthority' -and $_.Name -eq 'NTAuthCertificates') { + $IssueDetail = @" +The NTAuthCertificates object determines which Certification Authorities are +trusted by Active Directory (AD) for client authentication of all forms. + +$($entry.IdentityReference) can use their granted rights on NTAuthCertificates +to add their own rogue CAs. Once the rogue CA is trusted, any client authentication +certificates generated by the it can be used by the attacker. +"@ + } + if ( ($_.objectClass -ne 'pKICertificateTemplate') -and ($SID -notmatch $SafeUsers) -and ($entry.AccessControlType -eq 'Allow') -and @@ -121,14 +216,20 @@ Set-ACL -Path `'AD:$($_.DistinguishedName)`' -AclObject `$ACL" Forest = $_.CanonicalName.split('/')[0] Name = $_.Name DistinguishedName = $_.DistinguishedName - objectClass = $_.objectClass IdentityReference = $entry.IdentityReference ActiveDirectoryRights = $entry.ActiveDirectoryRights - Issue = "$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) rights on this object" + Issue = @" +$($entry.IdentityReference) has $($entry.ActiveDirectoryRights) elevated rights +on this $($_.objectClass) object. + +$IssueDetail + +"@ Fix = @" `$ACL = Get-Acl -Path `'AD:$($_.DistinguishedName)`' foreach ( `$ace in `$ACL.access ) { - if ( (`$ace.IdentityReference.Value -like '$($Principal.Value)' ) -and ( `$ace.ActiveDirectoryRights -notmatch '^ExtendedRight$') ) { + if ( (`$ace.IdentityReference.Value -like '$($Principal.Value)' ) -and + ( `$ace.ActiveDirectoryRights -notmatch '^ExtendedRight$') ) { `$ACL.RemoveAccessRule(`$ace) | Out-Null } } diff --git a/Private/Find-ESC6.ps1 b/Private/Find-ESC6.ps1 index 01d7228..869b914 100644 --- a/Private/Find-ESC6.ps1 +++ b/Private/Find-ESC6.ps1 @@ -22,7 +22,7 @@ #> [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects ) process { @@ -41,16 +41,32 @@ Revert = 'N/A' } if ($_.SANFlag -eq 'Yes') { - $Issue.Issue = 'EDITF_ATTRIBUTESUBJECTALTNAME2 is enabled. If Strong Mapping enforcement has been ' + - 'disabled on Domain Controllers, all templates will accept a SAN during enrollment.' + $Issue.Issue = @" +The dangerous EDITF_ATTRIBUTESUBJECTALTNAME2 flag is enabled on $CAFullname. +All templates published on this CA will accept a Subject Alternative Name (SAN) +during enrollment even if the template is not specifically configured to allow a SAN. + +As of May 2022, Microsoft has neutered this situation by requiring all SANs to +be strongly mapped to certificates. + +However, if strong mapping has been explicitly disabled on Domain Controllers, +this configuration remains vulnerable to privilege escalation attacks. + +"@ $Issue.Fix = @" +# Disable the flag certutil -config $CAFullname -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 + +# Restart the Ceritification Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } "@ $Issue.Revert = @" +# Enable the flag certutil -config $CAFullname -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2 + +# Restart the Ceritification Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } diff --git a/Private/Find-ESC8.ps1 b/Private/Find-ESC8.ps1 index 96f8bae..48dca56 100644 --- a/Private/Find-ESC8.ps1 +++ b/Private/Find-ESC8.ps1 @@ -29,7 +29,7 @@ [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects ) @@ -44,7 +44,16 @@ DistinguishedName = $_.DistinguishedName CAEnrollmentEndpoint = $endpoint.URL AuthType = $endpoint.Auth - Issue = 'An HTTP enrollment endpoint is available. It is possible to relay NTLM authentication to this HTTP endpoint.' + Issue = @' +An HTTP enrollment endpoint is available. It is possible to relay NTLM +authentication to this HTTP endpoint. + +If the LAN Manager authentication level of any domain in this forest is 2 or +less, an attacker can coerce authentication from a Domain Controller (DC) and +relay it to this HTTP enrollment enpoint to receive a certificate which can be +used to authenticate as that DC. + +'@ Fix = @' Disable HTTP access and enforce HTTPS. Enable EPA. @@ -54,7 +63,18 @@ Disable NTLM authentication (if possible.) Technique = 'ESC8' } if ($endpoint.URL -match '^https:') { - $Issue.Issue = 'An HTTPS enrollment endpoint is available. It may be possible to relay NTLM authentication to this HTTPS endpoint.' + $Issue.Issue = @' +An HTTPS enrollment endpoint is available. It may be possible to relay NTLM +authentication to this HTTPS endpoint. Enabling IIS Extended Protection for +Authentication or disabling NTLM authentication completely, NTLM relay is not +possible. + +If those protection are not in place, and the LAN Manager authentication level +of any domain in this forest is 2 or less, an attacker can coerce authentication +from a Domain Controller (DC) and relay it to this HTTPS enrollment enpoint to +receive a certificate which can be used to authenticate as that DC. + +'@ $Issue.Fix = @' Ensure EPA is enabled. Disable NTLM authentication (if possible.) diff --git a/Private/Find-ESC9.ps1 b/Private/Find-ESC9.ps1 index 0208f0e..ee4f992 100644 --- a/Private/Find-ESC9.ps1 +++ b/Private/Find-ESC9.ps1 @@ -50,7 +50,7 @@ function Find-ESC9 { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] $ADCSObjects ) diff --git a/Private/Format-Result.ps1 b/Private/Format-Result.ps1 index a58cccc..f8554db 100644 --- a/Private/Format-Result.ps1 +++ b/Private/Format-Result.ps1 @@ -26,7 +26,7 @@ function Format-Result { [CmdletBinding()] param( $Issue, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [int]$Mode ) diff --git a/Private/Invoke-Remediation.ps1 b/Private/Invoke-Remediation.ps1 index 80c0194..b26ab02 100644 --- a/Private/Invoke-Remediation.ps1 +++ b/Private/Invoke-Remediation.ps1 @@ -87,7 +87,7 @@ function Invoke-Remediation { $AuditingIssues | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "Auditing is not fully enabled on Certification Authority `"$($_.Name)`".`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -118,7 +118,7 @@ function Invoke-Remediation { $ESC1 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "Security Principals can enroll in `"$($_.Name)`" template using a Subject Alternative Name without Manager Approval.`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -150,7 +150,7 @@ function Invoke-Remediation { $ESC2 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "Security Principals can enroll in `"$($_.Name)`" template and create a Subordinate Certification Authority without Manager Approval.`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -243,7 +243,7 @@ function Invoke-Remediation { $ESC6 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "The Certification Authority `"$($_.Name)`" has the dangerous EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled.`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -276,7 +276,7 @@ function Invoke-Remediation { $ESC11 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "The Certification Authority `"$($_.Name)`" has the IF_ENFORCEENCRYPTICERTREQUEST flag disabled.`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White @@ -309,7 +309,7 @@ function Invoke-Remediation { $ESC13 | ForEach-Object { $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White - Write-Host "Security Principals can enroll in `"$($_.Name)`" template which is linked to $($_.LinkedGroup).`n" + Write-Host "$($_.Issue)`n" Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White diff --git a/Private/Set-Severity.ps1 b/Private/Set-Severity.ps1 index 76e1362..d6909e9 100644 --- a/Private/Set-Severity.ps1 +++ b/Private/Set-Severity.ps1 @@ -2,7 +2,7 @@ function Set-Severity { [OutputType([string])] [CmdletBinding()] param( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory)] [array]$Issue ) foreach ($Finding in $Issue) { diff --git a/Public/Invoke-Locksmith.ps1 b/Public/Invoke-Locksmith.ps1 index 8f34719..dfc4775 100644 --- a/Public/Invoke-Locksmith.ps1 +++ b/Public/Invoke-Locksmith.ps1 @@ -4,7 +4,7 @@ Finds the most common malconfigurations of Active Directory Certificate Services (AD CS). .DESCRIPTION - Locksmith uses the Active Directory (AD) Powershell (PS) module to identify 7 misconfigurations + Locksmith uses the Active Directory (AD) Powershell (PS) module to identify 10 misconfigurations commonly found in Enterprise mode AD CS installations. .COMPONENT From d0bbe92bc32a77f36db200cd07d7d60b9004e8d8 Mon Sep 17 00:00:00 2001 From: Jake Hildreth Date: Sun, 10 Nov 2024 06:06:43 -0600 Subject: [PATCH 14/14] Fresh build. --- Invoke-Locksmith.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 14afed3..7880f6a 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -2675,9 +2675,9 @@ function Test-IsADAdmin { #> if ( # Need to test to make sure this checks domain groups and not local groups, particularly for 'Administrators' (reference SID instead of name?). - ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('512') -or - ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('544') -or - ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('519') + ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Domain Admins") -or + ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrators") -or + ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Enterprise Admins") ) { Return $true }