Skip to content

Commit

Permalink
2024.10 Release
Browse files Browse the repository at this point in the history
2024.10 Release
  • Loading branch information
TrimarcJake authored Oct 5, 2024
2 parents 5f493e4 + d68bb2c commit 252b050
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 102 deletions.
16 changes: 14 additions & 2 deletions Build/Build-Module.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
if (Get-Module -Name 'PSPublishModule' -ListAvailable) {
param (
# A CalVer string if you need to manually override the default yyyy.M version string.
[string]$CalVer
)

if (Get-Module -Name 'PSPublishModule' -ListAvailable) {
Write-Information 'PSPublishModule is installed.'
} else {
Write-Information 'PSPublishModule is not installed. Attempting installation.'
Expand All @@ -17,7 +22,7 @@ Import-Module -Name PSPublishModule -Force
Build-Module -ModuleName 'Locksmith' {
# Usual defaults as per standard module
$Manifest = [ordered] @{
ModuleVersion = (Get-Date -Format yyyy.M)
ModuleVersion = if ($Calver) {$CalVer} else {(Get-Date -Format yyyy.M)}
CompatiblePSEditions = @('Desktop', 'Core')
GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a'
Author = 'Jake Hildreth'
Expand All @@ -30,6 +35,13 @@ Build-Module -ModuleName 'Locksmith' {
}
New-ConfigurationManifest @Manifest

# See [PR26](https://github.com/EvotecIT/PSPublishModule/pull/26) for notes about using placeholders and
# built-in placeholders for common module metadata.
# New-ConfigurationPlaceHolder -CustomReplacement @(
# @{ Find = '{CustomName}'; Replace = 'SpecialCase' }
# @{ Find = '{CustomVersion}'; Replace = '1.0.0' }
# )

# Add standard module dependencies (directly, but can be used with loop as well)
#New-ConfigurationModule -Type RequiredModule -Name 'PSSharedGoods' -Guid 'Auto' -Version 'Latest'

Expand Down
194 changes: 144 additions & 50 deletions Invoke-Locksmith.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -535,12 +534,14 @@ function Find-ESC4 {
[int]$Mode
)
$ADCSObjects | ForEach-Object {
$Principal = New-Object System.Security.Principal.NTAccount($_.nTSecurityDescriptor.Owner)
if ($Principal -match '^(S-1|O:)') {
$SID = $Principal
}
else {
$SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value
if ($_.Name -ne '' -and $null -ne $_.Name) {
$Principal = [System.Security.Principal.NTAccount]::New($_.nTSecurityDescriptor.Owner)
if ($Principal -match '^(S-1|O:)') {
$SID = $Principal
}
else {
$SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value
}
}

if ( ($_.objectClass -eq 'pKICertificateTemplate') -and ($SID -notmatch $SafeOwners) ) {
Expand Down Expand Up @@ -685,12 +686,14 @@ function Find-ESC5 {
$SafeObjectTypes
)
$ADCSObjects | ForEach-Object {
$Principal = New-Object System.Security.Principal.NTAccount($_.nTSecurityDescriptor.Owner)
if ($Principal -match '^(S-1|O:)') {
$SID = $Principal
}
else {
$SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value
if ($_.Name -ne '' -and $null -ne $_.Name) {
$Principal = New-Object System.Security.Principal.NTAccount($_.nTSecurityDescriptor.Owner)
if ($Principal -match '^(S-1|O:)') {
$SID = $Principal
}
else {
$SID = ($Principal.Translate([System.Security.Principal.SecurityIdentifier])).Value
}
}

if ( ($_.objectClass -ne 'pKICertificateTemplate') -and ($SID -notmatch $SafeOwners) ) {
Expand Down Expand Up @@ -854,20 +857,31 @@ function Find-ESC8 {
$ADCSObjects | Where-Object {
$_.CAEnrollmentEndpoint
} | ForEach-Object {
$Issue = [pscustomobject]@{
Forest = $_.CanonicalName.split('/')[0]
Name = $_.Name
DistinguishedName = $_.DistinguishedName
CAEnrollmentEndpoint = $_.CAEnrollmentEndpoint
Issue = 'HTTP enrollment is enabled.'
Fix = '[TODO]'
Revert = '[TODO]'
Technique = 'ESC8'
}
if ($_.CAEnrollmentEndpoint -match '^https') {
$Issue.Issue = 'HTTPS enrollment is enabled.'
foreach ($endpoint in $_.CAEnrollmentEndpoint) {
$Issue = [pscustomobject]@{
Forest = $_.CanonicalName.split('/')[0]
Name = $_.Name
DistinguishedName = $_.DistinguishedName
CAEnrollmentEndpoint = $endpoint.URL
AuthType = $endpoint.Auth
Issue = 'An HTTP enrollment endpoint is available.'
Fix = @'
Disable HTTP access and enforce HTTPS.
Enable EPA.
Disable NTLM authentication (if possible.)
'@
Revert = '[TODO]'
Technique = 'ESC8'
}
if ($endpoint.URL -match '^https:') {
$Issue.Issue = 'An HTTPS enrollment endpoint is available.'
$Issue.Fix = @'
Ensure EPA is enabled.
Disable NTLM authentication (if possible.)
'@
}
$Issue
}
$Issue
}
}
}
Expand Down Expand Up @@ -1038,7 +1052,7 @@ function Format-Result {
}
1 {
if ($Issue.Technique -eq 'ESC8') {
$Issue | Format-List Technique, Name, DistinguishedName, CAEnrollmentEndpoint, Issue, Fix
$Issue | Format-List Technique, Name, DistinguishedName, CAEnrollmentEndpoint, AuthType, Issue, Fix
}
else {
$Issue | Format-List Technique, Name, DistinguishedName, Issue, Fix
Expand Down Expand Up @@ -1614,7 +1628,7 @@ function Invoke-Scans {
.EXAMPLE
# Perform all scans
Invoke-Scans
Invoke-Scans
.EXAMPLE
# Perform only the 'Auditing' and 'ESC1' scans
Expand All @@ -1626,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()]
Expand Down Expand Up @@ -1957,12 +1973,72 @@ function Set-AdditionalCAProperty {
$ForestGC
)

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;
}
}
"@
Add-Type -TypeDefinition $code -Language CSharp
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
}

process {
$ADCSObjects | Where-Object objectClass -Match 'pKIEnrollmentService' | ForEach-Object {
[string]$CAEnrollmentEndpoint = $_.'msPKI-Enrollment-Servers' | Select-String 'http.*' | ForEach-Object { $_.Matches[0].Value }
#[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/")) {
$URL = "://$($_.dNSHostName)/$directory"
try {
$Auth = 'NTLM'
$FullURL = "http$URL"
$Request = [System.Net.WebRequest]::Create($FullURL)
$Cache = [System.Net.CredentialCache]::New()
$Cache.Add([System.Uri]::new($FullURL), $Auth, [System.Net.CredentialCache]::DefaultNetworkCredentials)
$Request.Credentials = $Cache
$Request.Timeout = 3000
$Request.GetResponse() | Out-Null
$CAEnrollmentEndpoint += @{
'URL' = $FullURL
'Auth' = $Auth
}
}
catch {
try {
$FullURL = "https$URL"
$Request = [System.Net.WebRequest]::Create($FullURL)

$Request.GetResponse() | Out-Null
$CAEnrollmentEndpoint += @{
'URL' = $FullURL
'Auth' = $Auth
}
}
catch {
try {
$Auth = 'Negotiate'
$FullURL = "https$URL"
$Request = [System.Net.WebRequest]::Create($FullURL)
$Cache = [System.Net.CredentialCache]::New()
$Cache.Add([System.Uri]::new($FullURL), 'Negotiate', [System.Net.CredentialCache]::DefaultNetworkCredentials)
$Request.Credentials = $Cache
$Request.GetResponse() | Out-Null
$CAEnrollmentEndpoint += @{
'URL' = $FullURL
'Auth' = $Auth
}
}
catch {
}
}
}
}
[string]$CAFullName = "$($_.dNSHostName)\$($_.Name)"
$CAHostname = $_.dNSHostName.split('.')[0]
# $CAName = $_.Name
if ($Credential) {
$CAHostDistinguishedName = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Server $ForestGC -Credential $Credential).DistinguishedName
$CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC -Credential $Credential).DnsHostname
Expand All @@ -1972,7 +2048,7 @@ function Set-AdditionalCAProperty {
$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
Expand Down Expand Up @@ -2035,6 +2111,7 @@ function Set-AdditionalCAProperty {
}

function Set-Severity {
[OutputType([string])]
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
Expand Down Expand Up @@ -2162,9 +2239,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
Expand Down Expand Up @@ -2243,12 +2322,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)]
Expand Down Expand Up @@ -2493,7 +2569,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
Expand Down Expand Up @@ -2555,17 +2631,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.10'
$LogoPart1 = @"
_ _____ _______ _ _ _______ _______ _____ _______ _ _
| | | | |____/ |______ | | | | | |_____|
Expand Down Expand Up @@ -2595,10 +2684,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'

Expand Down Expand Up @@ -2648,18 +2740,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
Expand Down Expand Up @@ -2762,7 +2856,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
Expand All @@ -2773,7 +2867,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
Expand Down
2 changes: 1 addition & 1 deletion Locksmith.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -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.10'
PowerShellVersion = '5.1'
PrivateData = @{
PSData = @{
Expand Down
Loading

0 comments on commit 252b050

Please sign in to comment.