Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions Private/Convert/Convert-IdentityReferenceToSid.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,9 @@ function Convert-IdentityReferenceToSid {
Supports forest-wide searches using Global Catalog when RootDSE is provided, enabling resolution
of principals from child domains and trusted domains within the forest.

.PARAMETER Principal
.PARAMETER IdentityReference
The IdentityReference object to convert. Typically from ACL IdentityReference properties.

.PARAMETER Credential
PSCredential for authenticating to Active Directory. Required when running from non-domain joined computers.

.PARAMETER RootDSE
A DirectoryEntry object for the RootDSE. Used to determine the domain context for LDAP queries.
If not specified, attempts to derive the domain from the NTAccount name.

.INPUTS
System.Security.Principal.IdentityReference
Accepts IdentityReference objects via the pipeline.
Expand Down
7 changes: 0 additions & 7 deletions Private/Convert/Resolve-Principal.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@ function Resolve-Principal {
.PARAMETER IdentityReference
The IdentityReference object to convert. Can be either NTAccount or SecurityIdentifier.

.PARAMETER Credential
PSCredential for authenticating to Active Directory. Required for LDAP queries.

.PARAMETER RootDSE
A DirectoryEntry object for the RootDSE. Used to determine the domain context for LDAP queries.
If not specified, attempts to query without specific domain context.

.INPUTS
System.Security.Principal.IdentityReference
Accepts IdentityReference objects (NTAccount or SecurityIdentifier) via the pipeline.
Expand Down
Binary file modified Private/Get/Get-RootDSE.ps1
Binary file not shown.
5 changes: 5 additions & 0 deletions Private/Get/Get-WebEnrollmentEndpointStatus.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ function Get-WebEnrollmentEndpointStatus {
PSCustomObject with properties URL, NtlmOffered, EpaNotRequired.
Returns $null when the endpoint is unreachable or times out.

.EXAMPLE
Get-WebEnrollmentEndpointStatus -Url 'http://ca.contoso.com/certsrv/'
Checks whether the certsrv web enrollment endpoint at the given URL requires NTLM
authentication and whether Extended Protection for Authentication (EPA) is enforced.

.NOTES
Intentionally has no unit tests - HttpClient cannot be mocked in Pester.
Use integration tests (Get-WebEnrollmentEndpointStatus.Integration.Tests.ps1)
Expand Down
5 changes: 5 additions & 0 deletions Private/Initialize/Initialize-LS2Scan.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ function Initialize-LS2Scan {

The full vulnerability scan only runs once per session. Subsequent calls to any Find-LS2*
function will use the cached IssueStore data.

.EXAMPLE
Initialize-LS2Scan -Forest 'contoso.com'
Initializes the Locksmith2 scan context for the contoso.com forest using the current user's
credentials. Returns $true on success.
#>
[CmdletBinding()]
[OutputType([bool])]
Expand Down
4 changes: 2 additions & 2 deletions Private/Set/Set-CADisableExtensionList.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ function Set-CADisableExtensionList {
- DisableExtensionList: Array of disabled extension OIDs
- SecurityExtensionDisabled: Boolean indicating if the security extension (1.3.6.1.4.1.311.25.2) is disabled

.PARAMETER InputObject
Pipeline input from previous Set-CA* functions. Must contain DistinguishedName and CAFullName properties.
.PARAMETER AdcsObject
Pipeline input of LS2AdcsObject instances. Must contain DistinguishedName and CAFullName properties.

.OUTPUTS
PSCustomObject with DistinguishedName and CAFullName properties for pipeline continuation.
Expand Down
Binary file modified Private/Set/Set-DangerousEnrollee.ps1
Binary file not shown.
Binary file modified Private/Set/Set-LowPrivilegeEnrollee.ps1
Binary file not shown.
75 changes: 75 additions & 0 deletions Private/UI/Show-Logo.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@ $LowerHalfBlock = [char]0x2584
$UpperHalfBlock = [char]0x2580

function ConvertTo-ConsoleColor {
<#
.SYNOPSIS
Maps an RGB color value to the nearest System.ConsoleColor.

.DESCRIPTION
Finds the closest match to the given RGB values from the 16 standard console colors
using squared Euclidean distance in RGB space.

.PARAMETER R
Red channel value (0-255).

.PARAMETER G
Green channel value (0-255).

.PARAMETER B
Blue channel value (0-255).

.OUTPUTS
System.ConsoleColor
The nearest ConsoleColor to the given RGB values.

.EXAMPLE
ConvertTo-ConsoleColor -R 0 -G 128 -B 255
Returns the ConsoleColor closest to a medium blue.
#>
param([int]$R, [int]$G, [int]$B)
$colorMap = @(
@{ Color = [System.ConsoleColor]::Black; R = 0; G = 0; B = 0 }
Expand Down Expand Up @@ -70,11 +95,61 @@ function ConvertTo-ConsoleColor {
}

function Get-TrueColorFg {
<#
.SYNOPSIS
Returns an ANSI escape sequence for a true-color (24-bit) foreground color.

.DESCRIPTION
Builds the ANSI CSI SGR escape sequence for setting the terminal foreground to the
specified 24-bit RGB color. Requires a terminal with VT/ANSI true-color support.

.PARAMETER R
Red channel value (0-255).

.PARAMETER G
Green channel value (0-255).

.PARAMETER B
Blue channel value (0-255).

.OUTPUTS
System.String
The ANSI escape sequence string for the requested foreground color.

.EXAMPLE
Get-TrueColorFg -R 0 -G 200 -B 255
Returns the ANSI sequence to set the foreground to a cyan-blue.
#>
param([int]$R, [int]$G, [int]$B)
return "$ESC[38;2;${R};${G};${B}m"
}

function Get-TrueColorBg {
<#
.SYNOPSIS
Returns an ANSI escape sequence for a true-color (24-bit) background color.

.DESCRIPTION
Builds the ANSI CSI SGR escape sequence for setting the terminal background to the
specified 24-bit RGB color. Requires a terminal with VT/ANSI true-color support.

.PARAMETER R
Red channel value (0-255).

.PARAMETER G
Green channel value (0-255).

.PARAMETER B
Blue channel value (0-255).

.OUTPUTS
System.String
The ANSI escape sequence string for the requested background color.

.EXAMPLE
Get-TrueColorBg -R 30 -G 30 -B 46
Returns the ANSI sequence to set the background to a dark navy.
#>
param([int]$R, [int]$G, [int]$B)
return "$ESC[48;2;${R};${G};${B}m"
}
Expand Down
14 changes: 14 additions & 0 deletions Public/Find-LS2VulnerableCA.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ function Find-LS2VulnerableCA {
.PARAMETER Technique
ESC technique name to scan for (e.g., 'ESC6', 'ESC7a', 'ESC7m', 'ESC8', 'ESC11', 'ESC16')

.PARAMETER Forest
Fully qualified domain name of the target AD forest. If not specified, uses the value already
set in module scope or auto-detected by Resolve-LS2ConnectionContext.

.PARAMETER Credential
PSCredential for authenticating to Active Directory. If not specified, uses the credential
already set in module scope or the current user's identity.

.PARAMETER ExpandGroups
When specified, expands group principals in discovered issues into individual per-member issues.

.PARAMETER Rescan
Forces a fresh vulnerability scan even if the IssueStore is already populated.

.EXAMPLE
Find-LS2VulnerableCA -Technique ESC6
Checks for CAs with EDITF_ATTRIBUTESUBJECTALTNAME2 enabled.
Expand Down
16 changes: 15 additions & 1 deletion Public/Find-LS2VulnerableObject.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,21 @@ function Find-LS2VulnerableObject {
by focusing on the supporting infrastructure objects.

.PARAMETER Technique
ESC technique name to scan for. Currently supports 'ESC5'.
ESC technique name to scan for. Currently supports 'ESC5a' and 'ESC5o'.

.PARAMETER Forest
Fully qualified domain name of the target AD forest. If not specified, uses the value already
set in module scope or auto-detected by Resolve-LS2ConnectionContext.

.PARAMETER Credential
PSCredential for authenticating to Active Directory. If not specified, uses the credential
already set in module scope or the current user's identity.

.PARAMETER ExpandGroups
When specified, expands group principals in discovered issues into individual per-member issues.

.PARAMETER Rescan
Forces a fresh vulnerability scan even if the IssueStore is already populated.

.EXAMPLE
Find-LS2VulnerableObject -Technique ESC5o
Expand Down
14 changes: 14 additions & 0 deletions Public/Find-LS2VulnerableTemplate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ function Find-LS2VulnerableTemplate {
.PARAMETER Technique
ESC technique name to scan for (e.g., 'ESC1', 'ESC2', 'ESC3c1', 'ESC3c2')

.PARAMETER Forest
Fully qualified domain name of the target AD forest. If not specified, uses the value already
set in module scope or auto-detected by Resolve-LS2ConnectionContext.

.PARAMETER Credential
PSCredential for authenticating to Active Directory. If not specified, uses the credential
already set in module scope or the current user's identity.

.PARAMETER ExpandGroups
When specified, expands group principals in discovered issues into individual per-member issues.

.PARAMETER Rescan
Forces a fresh vulnerability scan even if the IssueStore is already populated.

.EXAMPLE
Find-LS2VulnerableTemplate -Technique ESC1
Scans for templates vulnerable to ESC1 (misconfigured certificate templates).
Expand Down
12 changes: 0 additions & 12 deletions Public/Get-LS2Stores.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,6 @@ function Get-LS2Stores {
These stores are populated during the execution of Invoke-Locksmith2 and persist
for the duration of the PowerShell session.

.PARAMETER Name
Optional. Name of a specific store to retrieve. Valid values:
- PrincipalStore
- AdcsObjectStore
- DomainStore
- IssueStore
- SafePrincipals
- DangerousPrincipals
- StandardOwners

If not specified, returns an object containing all stores.

.INPUTS
None. This function does not accept pipeline input.

Expand Down
116 changes: 116 additions & 0 deletions Tests/Locksmith2.CBHCoverage.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#requires -Version 5.1
BeforeDiscovery {
$ModuleRoot = Split-Path $PSScriptRoot -Parent
$allFunctionCases = [System.Collections.Generic.List[hashtable]]::new()

$sourceFiles = Get-ChildItem -Recurse -Include '*.ps1' -Path @(
(Join-Path $ModuleRoot 'Private'),
(Join-Path $ModuleRoot 'Public')
) | Sort-Object FullName

foreach ($file in $sourceFiles) {
# Handle UTF-16LE (BOM: FF FE)
$rawBytes = [System.IO.File]::ReadAllBytes($file.FullName)
$encoding = if ($rawBytes.Length -ge 2 -and $rawBytes[0] -eq 0xFF -and $rawBytes[1] -eq 0xFE) {
[System.Text.Encoding]::Unicode
} else {
[System.Text.Encoding]::UTF8
}
$content = [System.IO.File]::ReadAllText($file.FullName, $encoding)

$parseErrors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseInput(
$content, $file.FullName, [ref]$null, [ref]$parseErrors
)
if ($parseErrors) { continue }

$functions = $ast.FindAll({
$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]
}, $true)

foreach ($func in $functions) {
$help = $func.GetHelpContent()

# Actual parameter names extracted from the AST param() block
$paramNames = @()
if ($func.Body.ParamBlock -and $func.Body.ParamBlock.Parameters.Count -gt 0) {
$paramNames = @($func.Body.ParamBlock.Parameters | ForEach-Object {
$_.Name.VariablePath.UserPath
})
}

# Parameter names documented in .PARAMETER blocks (lowercased for comparison)
$docParamNamesLower = @()
if ($help -and $help.Parameters -and $help.Parameters.Count -gt 0) {
$docParamNamesLower = @($help.Parameters.Keys | ForEach-Object { $_.ToLower() })
}

$codeParamNamesLower = @($paramNames | ForEach-Object { $_.ToLower() })

# In param() but missing from CBH
$missingParams = @($paramNames | Where-Object { $_.ToLower() -notin $docParamNamesLower })

# In CBH .PARAMETER but not in param() — phantom entries
$phantomParams = @($docParamNamesLower | Where-Object { $_ -notin $codeParamNamesLower })

# [OutputType(...)] attribute declared on the param block
$hasOutputType = $false
if ($func.Body.ParamBlock -and $func.Body.ParamBlock.Attributes) {
$hasOutputType = [bool]($func.Body.ParamBlock.Attributes |
Where-Object { $_.TypeName.Name -eq 'OutputType' })
}

$allFunctionCases.Add(@{
FunctionName = $func.Name
FileName = $file.Name
HasSynopsis = $help -and -not [string]::IsNullOrWhiteSpace($help.Synopsis)
HasDescription = $help -and -not [string]::IsNullOrWhiteSpace($help.Description)
ParamCount = $paramNames.Count
MissingParams = $missingParams
PhantomParams = $phantomParams
HasExample = $help -and $help.Examples -and $help.Examples.Count -gt 0
HasOutputType = $hasOutputType
HasOutputsDoc = $help -and $help.Outputs -and $help.Outputs.Count -gt 0
})
}
}

$casesWithParams = @($allFunctionCases | Where-Object { $_.ParamCount -gt 0 })
$casesWithOutputType = @($allFunctionCases | Where-Object { $_.HasOutputType })
}

Describe 'CBH Synopsis' -Tag 'Unit', 'CBH' {
It '<FunctionName> in <FileName> should have a non-empty .SYNOPSIS' -ForEach $allFunctionCases {
$HasSynopsis | Should -BeTrue
}
}

Describe 'CBH Description' -Tag 'Unit', 'CBH' {
It '<FunctionName> in <FileName> should have a non-empty .DESCRIPTION' -ForEach $allFunctionCases {
$HasDescription | Should -BeTrue
}
}

Describe 'CBH Parameter Coverage' -Tag 'Unit', 'CBH' {
It '<FunctionName> in <FileName> should document all parameters in CBH' -ForEach $casesWithParams {
$MissingParams | Should -BeNullOrEmpty -Because "these parameters lack a .PARAMETER doc block: $($MissingParams -join ', ')"
}
}

Describe 'CBH No Phantom Parameters' -Tag 'Unit', 'CBH' {
It '<FunctionName> in <FileName> should not document non-existent parameters in CBH' -ForEach $allFunctionCases {
$PhantomParams | Should -BeNullOrEmpty -Because "these .PARAMETER entries do not match any param() variable: $($PhantomParams -join ', ')"
}
}

Describe 'CBH Example' -Tag 'Unit', 'CBH' {
It '<FunctionName> in <FileName> should have at least one .EXAMPLE' -ForEach $allFunctionCases {
$HasExample | Should -BeTrue
}
}

Describe 'CBH Outputs Coverage' -Tag 'Unit', 'CBH' {
It '<FunctionName> in <FileName> should document .OUTPUTS when [OutputType] is declared' -ForEach $casesWithOutputType {
$HasOutputsDoc | Should -BeTrue -Because '[OutputType] is declared but .OUTPUTS is missing from CBH'
}
}
Loading