diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index cd7cf6e7..b645bb57 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,20 +7,21 @@ assignees: '' --- -**Describe the Bug** +## Describe the Bug -**Steps To Reproduce** +### Steps To Reproduce -**Expected Behavior** +### Expected Behavior -**Environment** - - Locksmith Version: [e.g. 2024.8] - - OS: [e.g. Windows Server 2019] - - PowerShell Version: [e.g. Windows PowerShell 5.1 or PowerShell 7.4.5] - - PowerShell Host: [e.g. Windows Terminal, PowerShell, PowerShell ISE, VS Code Terminal +### Environment -**Additional Context** +- Locksmith Version: (e.g. 2024.8) +- OS: (e.g. Windows Server 2019) +- PowerShell Version: (e.g. Windows PowerShell 5.1 or PowerShell 7.5.1) +- PowerShell Host: (e.g. Windows Terminal, PowerShell, PowerShell ISE, VS Code Terminal) + +### Additional Context diff --git a/.github/workflows/mega-linter.yml b/.github/workflows/mega-linter.yml index 98eb6467..679bdeb9 100644 --- a/.github/workflows/mega-linter.yml +++ b/.github/workflows/mega-linter.yml @@ -13,7 +13,7 @@ on: # branches: # - main # - testing - # workflow_dispatch: + workflow_dispatch: concurrency: group: ${{ github.ref }}-${{ github.workflow }} @@ -45,7 +45,7 @@ jobs: # You can override MegaLinter flavor used to have faster performances # More info at https://megalinter.io/flavors/ # The dotnet flavor includes PowerShell, MD, YAML, JSON, spelling, and more. - uses: oxsecurity/megalinter/flavors/dotnet@v8.3.0 + uses: oxsecurity/megalinter/flavors/dotnet@v8.7.0 id: ml diff --git a/CHANGELOG.MD b/CHANGELOG.MD deleted file mode 100644 index 4b4ad473..00000000 --- a/CHANGELOG.MD +++ /dev/null @@ -1,2 +0,0 @@ -### 1.0.0 -- Initial release \ No newline at end of file diff --git a/Docs/requirements.txt b/Docs/requirements.txt index a384e051..25f02a7d 100644 --- a/Docs/requirements.txt +++ b/Docs/requirements.txt @@ -1,6 +1,6 @@ # https://github.com/readthedocs-examples/example-mkdocs-basic/blob/main/docs/requirements.txt # requirements.txt -jinja2==3.1.5 #https://pypi.org/project/Jinja2/ +jinja2==3.1.6 #https://pypi.org/project/Jinja2/ mkdocs>=1.6.0 #https://github.com/mkdocs/mkdocs mkdocs-material==9.5.25 #https://github.com/squidfunk/mkdocs-material pygments>=2.18.0 #https://pypi.org/project/Pygments/ diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index dbd8022e..64e6476c 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -2043,7 +2043,7 @@ function Get-CAHostObject { [CmdletBinding()] param ( [parameter( - Mandatory = $true, + Mandatory, ValueFromPipeline = $true)] [Microsoft.ActiveDirectory.Management.ADEntity[]]$ADCSObjects, [System.Management.Automation.PSCredential]$Credential, @@ -2053,20 +2053,20 @@ function Get-CAHostObject { if ($Credential) { $ADCSObjects | Where-Object objectClass -Match 'pKIEnrollmentService' | ForEach-Object { if ($_.CAHostDistinguishedName) { - Get-ADObject $_.CAHostDistinguishedName -Properties * -Server $ForestGC -Credential $Credential + Get-ADObject $_.CAHostDistinguishedName -Properties * -Server $ForestGC -Credential $Credential } else { - Write-Warning "Get-CAHostObject: Unable to get information from $($_.DisplayName)" + Write-Warning "Get-CAHostObject: Unable to get information from $($_.DisplayName)" } } } else { $ADCSObjects | Where-Object objectClass -Match 'pKIEnrollmentService' | ForEach-Object { if ($_.CAHostDistinguishedName) { - Get-ADObject -Identity $_.CAHostDistinguishedName -Properties * -Server $ForestGC + Get-ADObject -Identity $_.CAHostDistinguishedName -Properties * -Server $ForestGC } else { - Write-Warning "Get-CAHostObject: Unable to get information from $($_.DisplayName)" + Write-Warning "Get-CAHostObject: Unable to get information from $($_.DisplayName)" } } } @@ -3029,7 +3029,7 @@ function Set-AdditionalCAProperty { [CmdletBinding(SupportsShouldProcess)] param ( [parameter( - Mandatory = $true, + Mandatory, ValueFromPipeline = $true)] [Microsoft.ActiveDirectory.Management.ADEntity[]]$ADCSObjects, [PSCredential]$Credential, @@ -3134,10 +3134,10 @@ function Set-AdditionalCAProperty { $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname } $ping = if ($CAHostFQDN) { - Test-Connection -ComputerName $CAHostFQDN -Count 1 -Quiet + Test-Connection -ComputerName $CAHostFQDN -Count 1 -Quiet } else { - Write-Warning "Unable to resolve $($_.Name) Fully Qualified Domain Name (FQDN)" + Write-Warning "Unable to resolve $($_.Name) Fully Qualified Domain Name (FQDN)" } if ($ping) { try { @@ -3727,23 +3727,23 @@ function Set-RiskRating { switch ($Issue.objectClass) { # Being able to modify Root CA Objects is very bad. 'certificationAuthority' { - $RiskValue += 2; $RiskScoring += 'Root Certification Authority bject: +2' + $RiskValue += 2; $RiskScoring += 'Root Certification Authority bject: +2' } # Being able to modify Issuing CA Objects is also very bad. 'pKIEnrollmentService' { - $RiskValue += 2; $RiskScoring += 'Issuing Certification Authority Object: +2' + $RiskValue += 2; $RiskScoring += 'Issuing Certification Authority Object: +2' } # Being able to modify CA Hosts? Yeah... very bad. 'computer' { - $RiskValue += 2; $RiskScoring += 'Certification Authority Host Computer: +2' + $RiskValue += 2; $RiskScoring += 'Certification Authority Host Computer: +2' } # Being able to modify OIDs could result in ESC13 vulns. 'msPKI-Enterprise-Oid' { - $RiskValue += 1; $RiskScoring += 'OID: +1' + $RiskValue += 1; $RiskScoring += 'OID: +1' } # Being able to modify PKS containers is bad. 'container' { - $RiskValue += 1; $RiskScoring += 'Container: +1' + $RiskValue += 1; $RiskScoring += 'Container: +1' } } } @@ -3764,19 +3764,19 @@ function Set-RiskRating { # Convert Value to Name $RiskName = switch ($RiskValue) { { $_ -le 1 } { - 'Informational' + 'Informational' } 2 { - 'Low' + 'Low' } 3 { - 'Medium' + 'Medium' } 4 { - 'High' + 'High' } { $_ -ge 5 } { - 'Critical' + 'Critical' } } @@ -4335,7 +4335,7 @@ Set-Acl -Path `$Path -AclObject `$ACL "@ } 4 { - break + break } 5 { $Issue.Fix = @" @@ -4670,15 +4670,15 @@ Function Write-HostColorized { # * At least for now, we remain PSv2-COMPATIBLE. # * Thus: # * no `[ordered]`, `::new()`, `[pscustomobject]`, ... - # * No implicit Boolean properties in [CmdletBinding()] and [Parameter()] attributes (`Mandatory = $true` instead of just `Mandatory`) + # * No implicit Boolean properties in [CmdletBinding()] and [Parameter()] attributes (`Mandatory` instead of just `Mandatory`) # === [CmdletBinding(DefaultParameterSetName = 'SingleColor')] param( - [Parameter(ParameterSetName = 'SingleColor', Position = 0, Mandatory = $True)] [string[]] $Pattern, + [Parameter(ParameterSetName = 'SingleColor', Position = 0, Mandatory)] [string[]] $Pattern, [Parameter(ParameterSetName = 'SingleColor', Position = 1)] [ConsoleColor] $ForegroundColor = [ConsoleColor]::Yellow, [Parameter(ParameterSetName = 'SingleColor', Position = 2)] [ConsoleColor] $BackgroundColor, - [Parameter(ParameterSetName = 'PerPatternColor', Position = 0, Mandatory = $True)] [System.Collections.IDictionary] $PatternColorMap, + [Parameter(ParameterSetName = 'PerPatternColor', Position = 0, Mandatory)] [System.Collections.IDictionary] $PatternColorMap, [Parameter(ValueFromPipeline = $True)] $InputObject, [switch] $WholeLine, [switch] $SimpleMatch, @@ -4706,10 +4706,10 @@ Function Write-HostColorized { # We precompile them for better performance with many input objects. [System.Text.RegularExpressions.RegexOptions] $reOpts = if ($CaseSensitive) { - 'Compiled, ExplicitCapture' + 'Compiled, ExplicitCapture' } else { - 'Compiled, ExplicitCapture, IgnoreCase' + 'Compiled, ExplicitCapture, IgnoreCase' } # Transform the dictionary: @@ -4731,10 +4731,10 @@ Function Write-HostColorized { } $colorArgs = @{ } if ($fg) { - $colorArgs['ForegroundColor'] = [ConsoleColor] $fg + $colorArgs['ForegroundColor'] = [ConsoleColor] $fg } if ($bg) { - $colorArgs['BackgroundColor'] = [ConsoleColor] $bg + $colorArgs['BackgroundColor'] = [ConsoleColor] $bg } # Consolidate the patterns into a single pattern with alternation ('|'), @@ -4753,7 +4753,7 @@ Function Write-HostColorized { } } catch { - throw + throw } # Construct the arguments to pass to Out-String. @@ -4776,7 +4776,7 @@ Function Write-HostColorized { foreach ($m in $entry.Key.Matches($_)) { @{ Index = $m.Index; Text = $m.Value; ColorArgs = $entry.Value } if ($WholeLine) { - break patternLoop + break patternLoop } } } diff --git a/Private/Get-CAHostObject.ps1 b/Private/Get-CAHostObject.ps1 index b435074e..079afaee 100644 --- a/Private/Get-CAHostObject.ps1 +++ b/Private/Get-CAHostObject.ps1 @@ -31,7 +31,7 @@ [CmdletBinding()] param ( [parameter( - Mandatory = $true, + Mandatory, ValueFromPipeline = $true)] [Microsoft.ActiveDirectory.Management.ADEntity[]]$ADCSObjects, [System.Management.Automation.PSCredential]$Credential, diff --git a/Private/Set-AdditionalCAProperty.ps1 b/Private/Set-AdditionalCAProperty.ps1 index 16975ccd..4f872ced 100644 --- a/Private/Set-AdditionalCAProperty.ps1 +++ b/Private/Set-AdditionalCAProperty.ps1 @@ -30,7 +30,7 @@ [CmdletBinding(SupportsShouldProcess)] param ( [parameter( - Mandatory = $true, + Mandatory, ValueFromPipeline = $true)] [Microsoft.ActiveDirectory.Management.ADEntity[]]$ADCSObjects, [PSCredential]$Credential, @@ -40,7 +40,7 @@ begin { if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy') ) { if ($PSVersionTable.PSEdition -eq 'Desktop') { - $code = @" + $code = @' using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { @@ -48,11 +48,11 @@ return true; } } -"@ +'@ Add-Type -TypeDefinition $code -Language CSharp [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy } else { - Add-Type @" + Add-Type @' using System.Net; using System.Security.Cryptography.X509Certificates; using System.Net.Security; @@ -61,7 +61,7 @@ return true; } } -"@ +'@ # Set the ServerCertificateValidationCallback [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [TrustAllCertsPolicy]::TrustAllCerts } @@ -72,7 +72,7 @@ $ADCSObjects | Where-Object objectClass -Match 'pKIEnrollmentService' | ForEach-Object { $CAEnrollmentEndpoint = @() #[array]$CAEnrollmentEndpoint = $_.'msPKI-Enrollment-Servers' | Select-String 'http.*' | ForEach-Object { $_.Matches[0].Value } - foreach ($directory in @("certsrv/", "$($_.Name)_CES_Kerberos/service.svc", "$($_.Name)_CES_Kerberos/service.svc/CES", "ADPolicyProvider_CEP_Kerberos/service.svc", "certsrv/mscep/")) { + foreach ($directory in @('certsrv/', "$($_.Name)_CES_Kerberos/service.svc", "$($_.Name)_CES_Kerberos/service.svc/CES", 'ADPolicyProvider_CEP_Kerberos/service.svc', 'certsrv/mscep/')) { $URL = "://$($_.dNSHostName)/$directory" try { $Auth = 'NTLM' @@ -116,6 +116,7 @@ 'Auth' = $Auth } } catch { + Write-Debug "There may have been an error or something nothing found. $_" } } } diff --git a/Private/Set-AdditionalTemplateProperty.ps1 b/Private/Set-AdditionalTemplateProperty.ps1 index df659760..1e25345e 100644 --- a/Private/Set-AdditionalTemplateProperty.ps1 +++ b/Private/Set-AdditionalTemplateProperty.ps1 @@ -28,18 +28,20 @@ [Microsoft.ActiveDirectory.Management.ADEntity[]]$ADCSObjects ) - $ADCSObjects | Where-Object objectClass -match 'pKICertificateTemplate' -PipelineVariable template | ForEach-Object { - # Write-Host "[?] Checking if template `"$($template.Name)`" is Enabled on any Certification Authority." -ForegroundColor Blue - $Enabled = $false - $EnabledOn = @() - foreach ($ca in ($ADCSObjects | Where-Object objectClass -eq 'pKIEnrollmentService')) { - if ($ca.certificateTemplates -contains $template.Name) { - $Enabled = $true - $EnabledOn += $ca.Name - } + process { + $ADCSObjects | Where-Object objectClass -Match 'pKICertificateTemplate' -PipelineVariable template | ForEach-Object { + # Write-Host "[?] Checking if template `"$($template.Name)`" is Enabled on any Certification Authority." -ForegroundColor Blue + $Enabled = $false + $EnabledOn = @() + foreach ($ca in ($ADCSObjects | Where-Object objectClass -EQ 'pKIEnrollmentService')) { + if ($ca.certificateTemplates -contains $template.Name) { + $Enabled = $true + $EnabledOn += $ca.Name + } - $template | Add-Member -NotePropertyName Enabled -NotePropertyValue $Enabled -Force - $template | Add-Member -NotePropertyName EnabledOn -NotePropertyValue $EnabledOn -Force + $template | Add-Member -NotePropertyName Enabled -NotePropertyValue $Enabled -Force + $template | Add-Member -NotePropertyName EnabledOn -NotePropertyValue $EnabledOn -Force + } } } } diff --git a/Private/Test-IsMemberOfProtectedUsers.ps1 b/Private/Test-IsMemberOfProtectedUsers.ps1 index 15380ccd..d0d8e983 100644 --- a/Private/Test-IsMemberOfProtectedUsers.ps1 +++ b/Private/Test-IsMemberOfProtectedUsers.ps1 @@ -39,36 +39,44 @@ function Test-IsMemberOfProtectedUsers { $User ) - Import-Module ActiveDirectory - - # Use the currently logged in user if none is specified - # Get the user from Active Directory - if (-not($User)) { - # These two are different types. Fixed by referencing $CheckUser.SID later, but should fix here by using one type. - $CurrentUser = ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name).Split('\')[-1] - $CheckUser = Get-ADUser $CurrentUser -Properties primaryGroupID - } else { - $CheckUser = Get-ADUser $User -Properties primaryGroupID + begin { + Import-Module ActiveDirectory } - # Get the Protected Users group by SID instead of by its name to ensure compatibility with any locale or language. - $DomainSID = (Get-ADDomain).DomainSID.Value - $ProtectedUsersSID = "$DomainSID-525" + process { + # Use the currently logged in user if none is specified + # Get the user from Active Directory + if (-not($User)) { + # These two are different types. Fixed by referencing $CheckUser.SID later, but should fix here by using one type. + $CurrentUser = ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name).Split('\')[-1] + $CheckUser = Get-ADUser $CurrentUser -Properties primaryGroupID + } else { + $CheckUser = Get-ADUser $User -Properties primaryGroupID + } + + # Get the Protected Users group by SID instead of by its name to ensure compatibility with any locale or language. + $DomainSID = (Get-ADDomain).DomainSID.Value + $ProtectedUsersSID = "$DomainSID-525" - # Get members of the Protected Users group for the current domain. Recuse in case groups are nested in it. - $ProtectedUsers = Get-ADGroupMember -Identity $ProtectedUsersSID -Recursive | Select-Object -Unique + # Get members of the Protected Users group for the current domain. Recuse in case groups are nested in it. + $ProtectedUsers = Get-ADGroupMember -Identity $ProtectedUsersSID -Recursive | Select-Object -Unique - # Check if the current user is in the 'Protected Users' group - if ($ProtectedUsers.SID.Value -contains $CheckUser.SID) { - Write-Verbose "$($CheckUser.Name) ($($CheckUser.DistinguishedName)) is a member of the Protected Users group." - $true - } else { - # Check if the user's PGID (primary group ID) is set to the Protected Users group RID (525). - if ( $CheckUser.primaryGroupID -eq '525' ) { + # Check if the current user is in the 'Protected Users' group + if ($ProtectedUsers.SID.Value -contains $CheckUser.SID) { + Write-Verbose "$($CheckUser.Name) ($($CheckUser.DistinguishedName)) is a member of the Protected Users group." $true } else { - Write-Verbose "$($CheckUser.Name) ($($CheckUser.DistinguishedName)) is not a member of the Protected Users group." - $false + # Check if the user's PGID (primary group ID) is set to the Protected Users group RID (525). + if ( $CheckUser.primaryGroupID -eq '525' ) { + $true + } else { + Write-Verbose "$($CheckUser.Name) ($($CheckUser.DistinguishedName)) is not a member of the Protected Users group." + $false + } } } + + end { + + } } diff --git a/Private/Write-HostColorized.ps1 b/Private/Write-HostColorized.ps1 index cd911a88..492defb0 100644 --- a/Private/Write-HostColorized.ps1 +++ b/Private/Write-HostColorized.ps1 @@ -97,15 +97,15 @@ Function Write-HostColorized { # * At least for now, we remain PSv2-COMPATIBLE. # * Thus: # * no `[ordered]`, `::new()`, `[pscustomobject]`, ... - # * No implicit Boolean properties in [CmdletBinding()] and [Parameter()] attributes (`Mandatory = $true` instead of just `Mandatory`) + # * No implicit Boolean properties in [CmdletBinding()] and [Parameter()] attributes (`Mandatory` instead of just `Mandatory`) # === [CmdletBinding(DefaultParameterSetName = 'SingleColor')] param( - [Parameter(ParameterSetName = 'SingleColor', Position = 0, Mandatory = $True)] [string[]] $Pattern, + [Parameter(ParameterSetName = 'SingleColor', Position = 0, Mandatory)] [string[]] $Pattern, [Parameter(ParameterSetName = 'SingleColor', Position = 1)] [ConsoleColor] $ForegroundColor = [ConsoleColor]::Yellow, [Parameter(ParameterSetName = 'SingleColor', Position = 2)] [ConsoleColor] $BackgroundColor, - [Parameter(ParameterSetName = 'PerPatternColor', Position = 0, Mandatory = $True)] [System.Collections.IDictionary] $PatternColorMap, + [Parameter(ParameterSetName = 'PerPatternColor', Position = 0, Mandatory)] [System.Collections.IDictionary] $PatternColorMap, [Parameter(ValueFromPipeline = $True)] $InputObject, [switch] $WholeLine, [switch] $SimpleMatch, diff --git a/README.md b/README.md index 2a53ab79..e17f1fbc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ + # Locksmith + ```text _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| @@ -9,6 +11,7 @@ \'-' .---'-''-'-' \'-' .--'--''-'-' \'-' .--'--'-''-' '--' '--' '--' ``` + A small tool built to find and fix common misconfigurations in Active Directory Certificate Services. @@ -20,146 +23,171 @@ A small tool built to find and fix common misconfigurations in Active Directory ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/jakehildreth/Locksmith/powershell.yml?logo=github&label=PSScriptAnalyzer) ![PowerShell Gallery Downloads](https://img.shields.io/powershellgallery/dt/locksmith?logo=powershell&label=PowerShell%20Gallery%20Downloads&color=blue) + ## Contents + 1. [Installation](#Installation) -2. [Run Locksmith](#RunLocksmith) - 1. [Mode 0](#Mode0) - 2. [Mode 1](#Mode1) - 3. [Mode 2](#Mode2) - 4. [Mode 3](#Mode3) - 5. [Mode 4](#Mode4) - 6. [Scans](#Scans) - +2. [Run Locksmith](#Run Locksmith) + 1. [Mode 0](#mode-0-identify-issues-output-to-console-default) + 2. [Mode 1](#mode-1-identify-issues-and-fixes-output-to-console) + 3. [Mode 2](#mode-2-identify-issues-output-to-csv) + 4. [Mode 3](#mode-3-identify-issues-and-fixes-output-to-csv) + 5. [Mode 4](#mode-4-fix-all-issues) + 6. [Scans](#scans) + ## Installation ### Prerequisites + 1. Locksmith must be run on a domain joined system. 2. The ActiveDirectory and ServerManager PowerShell modules must be installed before importing the Locksmith module. 3. Administrative rights may be required for some checks and for remediation. ### Standard Module Installation + Open a PowerShell prompt and install Locksmith from the PowerShell Gallery: + ```powershell Install-Module -Name Locksmith -Scope CurrentUser ``` ### Alternative Installation Methods + 1. Download and Use the Module Without Installing It 1. Download the [latest module version](https://github.com/jakehildreth/Locksmith/releases/latest/download/Locksmith.zip). 2. Open a PowerShell prompt to the location of the extracted file and run: + ```powershell Unblock-File .\Locksmith.zip # if necessary to unblock the download Expand-Archive .\Locksmith.zip Import-Module .\Locksmith\Locksmith.psd1 Invoke-Locksmith ``` + 2. Download the Standalone Script Without Module 1. Download the [latest monolithic (all-in-one) script version](https://github.com/jakehildreth/Locksmith/releases/latest/download/Invoke-Locksmith.zip). 2. Open a PowerShell prompt to the location of the downloaded file and run: + ```powershell Unblock-File .\Invoke-Locksmith.zip Expand-Archive .\Invoke-Locksmith.zip -DestinationPath .\ .\Invoke-Locksmith.ps1 ``` - + ## Run Locksmith -There are several modes you can chose from when running `Invoke-Locksmith`. You can also use the **Scans** parameter to choose which scans you want to invoke. - +There are several modes you can chose from when running `Invoke-Locksmith`. You can also use the **Scans** parameter to choose which scans you want to invoke. + ### Mode 0: Identify Issues, Output to Console (Default) Running `Invoke-Locksmith.ps1` with no parameters or with `-Mode 0` will scan the current Active Directory forest and output all discovered AD CS issues to the console in **Table** format. + ``` powershell # Module Syntax Invoke-Locksmith ``` + ``` powershell # Script Syntax .\Invoke-Locksmith.ps1 ``` -Example Output for Mode 0: - +Example Output for Mode 0: + ### Mode 1: Identify Issues and Fixes, Output to Console + This mode scans the current forest and outputs all discovered AD CS issues and possible fixes to the console in **List** format. ``` powershell # Module Syntax Invoke-Locksmith -Mode 1 ``` + ``` powershell # Script Syntax .\Invoke-Locksmith.ps1 -Mode 1 ``` -Example Output for Mode 1: - +Example Output for Mode 1: + ### Mode 2: Identify Issues, Output to CSV + Locksmith Mode 2 scans the current forest and outputs all discovered AD CS issues to ADCSIssues.CSV in the present working directory. ``` powershell # Module Syntax Invoke-Locksmith -Mode 2 ``` + ``` powershell # Script Syntax .\Invoke-Locksmith.ps1 -Mode 2 ``` -Example Output for Mode 2: - +Example Output for Mode 2: + ### Mode 3: Identify Issues and Fixes, Output to CSV + In Mode 3, Locksmith scans the current forest and outputs all discovered AD CS issues and example fixes to ADCSRemediation.CSV in the present working directory. + ``` powershell # Module Syntax Invoke-Locksmith -Mode 3 ``` + ``` powershell # Script Syntax .\Invoke-Locksmith.ps1 -Mode 3 ``` -Example Output for Mode 3: - +Example Output for Mode 3: + ### Mode 4: Fix All Issues + Mode 4 is the "easy button." Running Locksmith in Mode 4 will identify all misconfigurations and offer to fix each issue. If there is any possible operational impact, Locksmith will warn you. ``` powershell # Module Syntax Invoke-Locksmith -Mode 4 ``` + ``` powershell # Script Syntax .\Invoke-Locksmith.ps1 -Mode 4 ``` + Example Output for Mode 4: +### Scans -### Scans: Select Which Scans to Invoke -Use the `-Scans` parameter to choose which vulnerabilities to scan for. Acceptable values include `All`, `Auditing`, `ESC1`, `ESC2`, `ESC3`, `ESC4`, `ESC5`, `ESC6`, `ESC7`, `ESC8`, `ESC11`, `ESC13`, `ESC15`, `EKEUwu`, `ESC16` or `PromptMe`. The `PromptMe` option presents an interactive list allowing you to select one or more scans. +Use the `-Scans` parameter to choose which vulnerabilities to scan for. Acceptable values include `All`, `Auditing`, `ESC1`, `ESC2`, `ESC3`, `ESC4`, `ESC5`, `ESC6`, `ESC7`, `ESC8`, `ESC9`, `ESC11`, `ESC13`, `ESC15`, `EKEUwu`, `ESC16` or `PromptMe`. The `PromptMe` option presents an interactive list allowing you to select one or more scans. ``` powershell # Run all scans Invoke-Locksmith -Scan All ``` + ``` powershell # Prompt the user for a list of scans to select Invoke-Locksmith.ps1 -Scans PromptMe ``` + ``` powershell # Scan for ESC1 vulnerable paths Invoke-Locksmith.ps1 -Scans ESC1 ``` + ``` powershell # Scan for ESC1, ESC2, and ESC8 vulnerable paths Invoke-Locksmith.ps1 -Scans ESC1,ESC2,ESC8 ``` + Thank you for using Locksmith! 💜 diff --git a/Tests/Compare-ADObjects.ps1 b/Tests/Compare-ADObjects.ps1 index d0f604f1..03b4699c 100644 --- a/Tests/Compare-ADObjects.ps1 +++ b/Tests/Compare-ADObjects.ps1 @@ -1,17 +1,17 @@ # Source: https://learn.microsoft.com/en-us/archive/blogs/janesays/compare-all-properties-of-two-objects-in-windows-powershell param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory)] [string]$DN1, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory)] [string]$DN2 ) $ReferenceObject = Get-ADObject -Identity $DN1 -Properties * $DifferenceObject = Get-ADObject -Identity $DN2 -Properties * -$ObjectProperties = $ReferenceObject | Get-Member -MemberType Property,NoteProperty | % Name -$ObjectProperties += $DifferenceObject | Get-Member -MemberType Property,NoteProperty | % Name -$ObjectProperties = $ObjectProperties | Sort | Select -Unique +$ObjectProperties = $ReferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name +$ObjectProperties += $DifferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name +$ObjectProperties = $ObjectProperties | Sort-Object | Select-Object -Unique $Differences = @() foreach ($objectproperty in $ObjectProperties) { @@ -19,14 +19,14 @@ foreach ($objectproperty in $ObjectProperties) { if ($difference) { $differenceproperties = @{ PropertyName = $objectproperty - RefValue = ($difference | ? {$_.SideIndicator -eq '<='} | % $($objectproperty)) - DiffValue = ($difference | ? {$_.SideIndicator -eq '=>'} | % $($objectproperty)) + RefValue = ($difference | Where-Object { $_.SideIndicator -eq '<=' } | ForEach-Object $($objectproperty)) + DiffValue = ($difference | Where-Object { $_.SideIndicator -eq '=>' } | ForEach-Object $($objectproperty)) } $Differences += New-Object PSObject -Property $differenceproperties } } if ($Differences) { return ( - $Differences | Select PropertyName,RefValue,DiffValue + $Differences | Select-Object PropertyName, RefValue, DiffValue ) -} \ No newline at end of file +} diff --git a/Tests/Invoke-TSS.ps1 b/Tests/Invoke-TSS.ps1 index 4f7fad8e..5d7a8b86 100644 --- a/Tests/Invoke-TSS.ps1 +++ b/Tests/Invoke-TSS.ps1 @@ -401,7 +401,7 @@ $ACL.AddAccessRule($AccessRule) Set-Acl "AD:$ESC5WriteOwner" -AclObject $ACL Get-ADObject -Filter 'objectClass -eq "pKIEnrollmentService"' -SearchBase $PKSContainer -Properties * | ForEach-Object { - $ForestGC = $(Get-ADDomainController -Discover -Service GlobalCatalog -ForceDiscover | Select-Object -ExpandProperty Hostname) + ":3268" + $ForestGC = $(Get-ADDomainController -Discover -Service GlobalCatalog -ForceDiscover | Select-Object -ExpandProperty Hostname) + ':3268' [string]$CAFullName = "$($_.dNSHostName)\$($_.Name)" $CAHostname = $_.dNSHostName.split('.')[0] $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname diff --git a/Tests/Show-ADObjectChanges.ps1 b/Tests/Show-ADObjectChanges.ps1 index e1a2b36d..2fa3845e 100644 --- a/Tests/Show-ADObjectChanges.ps1 +++ b/Tests/Show-ADObjectChanges.ps1 @@ -1,16 +1,16 @@ # Source: https://learn.microsoft.com/en-us/archive/blogs/janesays/compare-all-properties-of-two-objects-in-windows-powershell param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory)] [string]$DN ) $ReferenceObject = Get-ADObject -Identity $DN -Properties * -Read-Host "Make your changes and press Enter" +Read-Host 'Make your changes and press Enter' $DifferenceObject = Get-ADObject -Identity $DN -Properties * -$ObjectProperties = $ReferenceObject | Get-Member -MemberType Property,NoteProperty | % Name -$ObjectProperties += $DifferenceObject | Get-Member -MemberType Property,NoteProperty | % Name -$ObjectProperties = $ObjectProperties | Sort | Select -Unique +$ObjectProperties = $ReferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name +$ObjectProperties += $DifferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name +$ObjectProperties = $ObjectProperties | Sort-Object | Select-Object -Unique $Differences = @() foreach ($objectproperty in $ObjectProperties) { @@ -18,14 +18,14 @@ foreach ($objectproperty in $ObjectProperties) { if ($difference) { $differenceproperties = @{ PropertyName = $objectproperty - RefValue = ($difference | ? {$_.SideIndicator -eq '<='} | % $($objectproperty)) - DiffValue = ($difference | ? {$_.SideIndicator -eq '=>'} | % $($objectproperty)) + RefValue = ($difference | Where-Object { $_.SideIndicator -eq '<=' } | ForEach-Object $($objectproperty)) + DiffValue = ($difference | Where-Object { $_.SideIndicator -eq '=>' } | ForEach-Object $($objectproperty)) } $Differences += New-Object PSObject -Property $differenceproperties } } if ($Differences) { return ( - $Differences | Select PropertyName,RefValue,DiffValue + $Differences | Select-Object PropertyName, RefValue, DiffValue ) -} \ No newline at end of file +} diff --git a/Tests/TSS Specs.md b/Tests/TSS Specs.md index 89934948..22b44443 100644 --- a/Tests/TSS Specs.md +++ b/Tests/TSS Specs.md @@ -1,125 +1,147 @@ # Tactical Speed Square: *the UnLocksmith* ## Auditing + Check if Audit level is set: - - No: leave it - - Yes: Set to 0 + +- No: leave it +- Yes: Set to 0 ## ESC1 + Find: - - Name: ESC1 - - Config: Typical ESC1 - - Principal: Authenticated Users - - Name: ESC1and2 - - Config: "Any purpose" EKU - - Principal: Authenticated Users +- Name: ESC1 +- Config: Typical ESC1 +- Principal: Authenticated Users + +- Name: ESC1and2 +- Config: "Any purpose" EKU +- Principal: Authenticated Users Do Not Find: - - Name: ESC1Filtered - - Config: Typical ESC1 - - Principal: Administrators + +- Name: ESC1Filtered +- Config: Typical ESC1 +- Principal: Administrators ## ESC2 + Find: - - Name: ESC2 - - Config: Typical ESC2 - - Principal: Authenticated Users - - Name: ESC1and2 - - Config: "Any purpose" EKU - - Principal: Authenticated Users +- Name: ESC2 +- Config: Typical ESC2 +- Principal: Authenticated Users + +- Name: ESC1and2 +- Config: "Any purpose" EKU +- Principal: Authenticated Users Do Not Find: - - Name: ESC2Filtered - - Config: Typical ESC2 - - Principal: Administrators + +- Name: ESC2Filtered +- Config: Typical ESC2 +- Principal: Administrators ## ESC3 - Not Complete + Find: - - Name: ESC3Condition1 + +- Name: ESC3Condition1 Find: - - Name: ESC3Condition2 + +- Name: ESC3Condition2 ## ESC4 + Find: - - Name: ESC4GenericAll - - Config: GenericAll - - Principal: Authenticated Users - - Name: ESC4UnsafeOwner - - Config: UnsafeOwner - - Principal: Authenticated Users +- Name: ESC4GenericAll +- Config: GenericAll +- Principal: Authenticated Users + +- Name: ESC4UnsafeOwner +- Config: UnsafeOwner +- Principal: Authenticated Users - - Name: ESC4WriteProperty - - Config: WriteProperty on All Objects - - Principal: Authenticated Users +- Name: ESC4WriteProperty +- Config: WriteProperty on All Objects +- Principal: Authenticated Users - - Name: ESC4WriteOwner - - Config: WriteOwner - - Principal: Authenticated Users +- Name: ESC4WriteOwner +- Config: WriteOwner +- Principal: Authenticated Users Do Not Find: - - Name: ESC4FilteredEnroll - - Config WriteProperty, ExtendedRight on Enroll - - Principal: Domain Users - - Name: ESC4FilteredAutoEnroll - - Config: WriteProperty, ExtendedRight on AutoEnroll - - Principal: Domain Users +- Name: ESC4FilteredEnroll +- Config WriteProperty, ExtendedRight on Enroll +- Principal: Domain Users + +- Name: ESC4FilteredAutoEnroll +- Config: WriteProperty, ExtendedRight on AutoEnroll +- Principal: Domain Users - - Name: ESC4FilteredOwner - - Config: Owner - - Principal: Administrators +- Name: ESC4FilteredOwner +- Config: Owner +- Principal: Administrators - - Name: ESC4FilteredSafeUsers - - Config: GenericAll - - Principal: Administrators +- Name: ESC4FilteredSafeUsers +- Config: GenericAll +- Principal: Administrators ## ESC5 + Find: - - Name: ESC5GenericAll - - Config: GenericAll - - Principal: Authenticated Users - - Name: ESC5UnsafeOwner - - Config: UnsafeOwner - - Principal: Authenticated Users +- Name: ESC5GenericAll +- Config: GenericAll +- Principal: Authenticated Users + +- Name: ESC5UnsafeOwner +- Config: UnsafeOwner +- Principal: Authenticated Users - - Name: ESC5WriteProperty - - Config: WriteProperty on All Objects - - Principal: Authenticated Users +- Name: ESC5WriteProperty +- Config: WriteProperty on All Objects +- Principal: Authenticated Users - - Name: ESC5WriteOwner - - Config: WriteOwner - - Principal: Authenticated Users +- Name: ESC5WriteOwner +- Config: WriteOwner +- Principal: Authenticated Users Do Not Find: - - Name: ESC5FilteredEnroll - - Config WriteProperty, ExtendedRight on Enroll - - Principal: Authenticated Users - - Name: ESC5FilteredAutoEnroll - - Config: WriteProperty, ExtendedRight on AutoEnroll - - Principal: Authenticated Users +- Name: ESC5FilteredEnroll +- Config WriteProperty, ExtendedRight on Enroll +- Principal: Authenticated Users + +- Name: ESC5FilteredAutoEnroll +- Config: WriteProperty, ExtendedRight on AutoEnroll +- Principal: Authenticated Users - - Name: ESC5FilteredOwner - - Config: Owner - - Principal: Administrators +- Name: ESC5FilteredOwner +- Config: Owner +- Principal: Administrators - - Name: ESC5FilteredSafeUsers - - Config: GenericAll - - Principal: Administrators +- Name: ESC5FilteredSafeUsers +- Config: GenericAll +- Principal: Administrators ## ESC6 + Check if dangerous flag exists: - - Yes: leave it - - No: set it + +- Yes: leave it +- No: set it ## ESC8 - Not Complete + Find: - - HTTP Enrollment Endpoint + +- HTTP Enrollment Endpoint Find: - - HTTPS Enrollment Endpoint + +- HTTPS Enrollment Endpoint diff --git a/mkdocs.yml b/mkdocs.yml index 53df775f..b2ba493e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,10 +11,10 @@ site_name: "Locksmith" site_url: "https://jakehildreth.github.io/Locksmith" repo_url: "https://github.com/jakehildreth/Locksmith" -repo_name: "TrimarcJake/Locksmith" +repo_name: "JakeHildreth/Locksmith" # edit_uri: edit/main/docs/ # edit_uri_template: -site_description: "A small tool to find and fix common misconfigurations in Active Directory Certificate Services." # meta tag to the generated HTML heade +site_description: "A small tool to find and fix common misconfigurations in Active Directory Certificate Services." # meta tag to the generated HTML header site_author: "Jake Hildreth" # meta tag to the generated HTML header copyright: "(c) 2024 Jake Hildreth." # remote_branch: gh-deploy @@ -83,10 +83,3 @@ theme: - navigation.top - toc.follow - toc.integrate - - # favicon: - # icon: - # repo: - # font: - # text: Work Sans - # logo: