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


+
## 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: