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
31 changes: 16 additions & 15 deletions Private/Data/ESCDefinitions.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -713,14 +713,14 @@ $script:ESCDefinitions = data {
}

ESC15 = @{
# ESC15: Schema v1 template with auth EKU — bypasses strong certificate mapping
# ESC15: Enabled schema v1 template with auth EKU — bypasses strong certificate mapping
# (szOID_NTDS_CA_SECURITY_EXT is absent in schema v1 certificates)
Technique = 'ESC15'

Conditions = @(
@{ Property = 'TemplateSchemaVersion'; Value = 1 }
@{ Property = 'AuthenticationEKUExist'; Value = $true }
@{ Property = 'ManagerApprovalNotRequired'; Value = $true }
@{ Property = 'Enabled'; Value = $true }
@{ Property = 'AuthorizedSignatureNotRequired'; Value = $true }
)

Expand All @@ -730,30 +730,28 @@ $script:ESCDefinitions = data {
)

IssueTemplate = @(
"`$(IdentityReference) can enroll in the `$(TemplateName) template, which uses a schema version 1 "
"`$(IdentityReference) can enroll in the `$(TemplateName) template, which uses schema version 1 "
"and a Client Authentication EKU.`n`n"
"Schema v1 templates do not include the CA security extension (szOID_NTDS_CA_SECURITY_EXT) "
"introduced by KB5014754. This means certificates issued from this template are not subject to "
"strong certificate-to-account mapping enforcement, allowing an attacker to authenticate as any "
"principal whose UPN or DNS name they can include in the Subject or SAN of the certificate.`n`n"
"Until the template is upgraded to schema v2+, enabling Manager Approval is the recommended "
"short-term mitigation to prevent unapproved enrollment.`n`n"
"Schema v1 templates cannot be modified in place. Supersede this template with a schema v2+ "
"equivalent.`n`n"
"More info:`n"
" - https://support.microsoft.com/help/5014754"
)

FixTemplate = @(
"# Quick mitigation: Enable Manager Approval to require approval before certificate issuance"
"`$Object = '`$(DistinguishedName)'"
"Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 2}"
"# Long-term fix: supersede this template with a schema v2+ equivalent"
"# See: https://www.gradenegger.eu/en/basics-replace-superseding-of-certificate-templates/"
'# Schema v1 templates cannot be upgraded in-place.'
'# Supersede this template by creating a new schema v2 (or later) template with equivalent settings,'
'# then configure the old template to be superseded by the new one.'
'# See: https://www.gradenegger.eu/en/basics-replace-superseding-of-certificate-templates/'
)

RevertTemplate = @(
"# Disable Manager Approval"
"`$Object = '`$(DistinguishedName)'"
"Get-ADObject `$Object | Set-ADObject -Replace @{'msPKI-Enrollment-Flag' = 0}"
'# No automated revert. Template schema version cannot be changed via script.'
'# If you superseded this template, re-enable the old template and remove the superseding relationship.'
)
}

Expand Down Expand Up @@ -790,12 +788,13 @@ $script:ESCDefinitions = data {
}

SchemaV1 = @{
# SchemaV1: Any enabled schema v1 template — informational hygiene finding
# SchemaV1: Enabled schema v1 template without client auth EKU — informational hygiene finding
Technique = 'SchemaV1'

Conditions = @(
@{ Property = 'TemplateSchemaVersion'; Value = 1 }
@{ Property = 'Enabled'; Value = $true }
@{ Property = 'AuthenticationEKUExist'; Value = $false }
)

IssueTemplate = @(
Expand All @@ -804,7 +803,9 @@ $script:ESCDefinitions = data {
"available in later schema versions. Certificates issued from schema v1 templates do not "
"include the CA security extension (szOID_NTDS_CA_SECURITY_EXT), reducing their compatibility "
"with strong certificate mapping requirements.`n`n"
"Consider superseding this template with a schema v2+ equivalent."
"Because this template does not include a Client Authentication EKU, it is not directly "
"exploitable for account takeover via ESC15. However, it should still be superseded with a "
"schema v2+ equivalent to ensure compatibility with modern security features."
)

FixTemplate = @(
Expand Down
108 changes: 71 additions & 37 deletions Tests/Public/Find-LS2VulnerableTemplate.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ InModuleScope 'Locksmith2' {
SchemaClassName = 'pKICertificateTemplate'
SANAllowed = $true
AuthenticationEKUExist = $true
ManagerApprovalNotRequired = $true
AuthorizedSignatureNotRequired = $true
DangerousEnrollee = @('S-1-1-0')
distinguishedName = 'CN=VulnTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Expand Down Expand Up @@ -112,9 +111,9 @@ InModuleScope 'Locksmith2' {

It 'should skip a template where any ESC1 condition is not met' {
$safeTemplate = New-MockLS2AdcsObject -Properties @{
SANAllowed = $false
distinguishedName = 'CN=SafeTemplate,CN=Certificate Templates,...'
Name = 'SafeTemplate'
SANAllowed = $false
distinguishedName = 'CN=SafeTemplate,CN=Certificate Templates,...'
Name = 'SafeTemplate'
}
$script:AdcsObjectStore = @{ $safeTemplate.distinguishedName = $safeTemplate }
$result = @(Find-LS2VulnerableTemplate -Technique 'ESC1')
Expand Down Expand Up @@ -173,16 +172,16 @@ InModuleScope 'Locksmith2' {
BeforeAll {
function script:New-ESC13VulnerableTemplate {
$t = New-MockLS2AdcsObject -Properties @{
objectClass = @('top', 'pKICertificateTemplate')
SchemaClassName = 'pKICertificateTemplate'
AuthenticationEKUExist = $true
HasLinkedGroupOIDPolicy = $true
LinkedGroupOIDPolicies = @('CN=PrivilegedGroup,CN=Users,DC=contoso,DC=com')
DangerousEnrollee = @('S-1-1-0')
distinguishedName = 'CN=ESC13Template,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'ESC13Template'
Enabled = $true
EnabledOn = @('CONTOSO-CA\CA01')
objectClass = @('top', 'pKICertificateTemplate')
SchemaClassName = 'pKICertificateTemplate'
AuthenticationEKUExist = $true
HasLinkedGroupOIDPolicy = $true
LinkedGroupOIDPolicies = @('CN=PrivilegedGroup,CN=Users,DC=contoso,DC=com')
DangerousEnrollee = @('S-1-1-0')
distinguishedName = 'CN=ESC13Template,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'ESC13Template'
Enabled = $true
EnabledOn = @('CONTOSO-CA\CA01')
}
$security = New-Object System.DirectoryServices.ActiveDirectorySecurity
$sid = [System.Security.Principal.SecurityIdentifier]::new('S-1-1-0')
Expand Down Expand Up @@ -267,14 +266,14 @@ InModuleScope 'Locksmith2' {

It 'should not return an issue when DangerousEnrollee is empty' {
$safeTemplate = New-MockLS2AdcsObject -Properties @{
SchemaClassName = 'pKICertificateTemplate'
AuthenticationEKUExist = $true
HasLinkedGroupOIDPolicy = $true
LinkedGroupOIDPolicies = @('CN=PrivilegedGroup,CN=Users,DC=contoso,DC=com')
DangerousEnrollee = @()
LowPrivilegeEnrollee = @()
distinguishedName = 'CN=SafeTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SafeTemplate'
SchemaClassName = 'pKICertificateTemplate'
AuthenticationEKUExist = $true
HasLinkedGroupOIDPolicy = $true
LinkedGroupOIDPolicies = @('CN=PrivilegedGroup,CN=Users,DC=contoso,DC=com')
DangerousEnrollee = @()
LowPrivilegeEnrollee = @()
distinguishedName = 'CN=SafeTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SafeTemplate'
}
$script:AdcsObjectStore = @{ $safeTemplate.distinguishedName = $safeTemplate }

Expand All @@ -301,7 +300,6 @@ InModuleScope 'Locksmith2' {
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 1
AuthenticationEKUExist = $true
ManagerApprovalNotRequired = $true
AuthorizedSignatureNotRequired = $true
DangerousEnrollee = @('S-1-1-0')
distinguishedName = 'CN=ESC15Template,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Expand Down Expand Up @@ -330,7 +328,7 @@ InModuleScope 'Locksmith2' {
Mock 'Test-IssueExists' { $false }
}

It 'should return an LS2Issue when template has TemplateSchemaVersion=1, auth EKU, no manager approval, and DangerousEnrollee' {
It 'should return an LS2Issue when template has TemplateSchemaVersion=1, auth EKU, is Enabled, AuthorizedSignatureNotRequired, and DangerousEnrollee' {
$vulnTemplate = New-ESC15VulnerableTemplate
$script:AdcsObjectStore = @{ $vulnTemplate.distinguishedName = $vulnTemplate }

Expand All @@ -354,9 +352,9 @@ InModuleScope 'Locksmith2' {
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 2
AuthenticationEKUExist = $true
ManagerApprovalNotRequired = $true
AuthorizedSignatureNotRequired = $true
DangerousEnrollee = @('S-1-1-0')
Enabled = $true
distinguishedName = 'CN=SafeTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SafeTemplate'
}
Expand All @@ -372,9 +370,27 @@ InModuleScope 'Locksmith2' {
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 1
AuthenticationEKUExist = $false
ManagerApprovalNotRequired = $true
AuthorizedSignatureNotRequired = $true
DangerousEnrollee = @('S-1-1-0')
Enabled = $true
distinguishedName = 'CN=SafeTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SafeTemplate'
}
$script:AdcsObjectStore = @{ $safeTemplate.distinguishedName = $safeTemplate }

$result = @(Find-LS2VulnerableTemplate -Technique 'ESC15')

$result.Count | Should -Be 0
}

It 'should not return an issue when Enabled is false' {
$safeTemplate = New-MockLS2AdcsObject -Properties @{
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 1
AuthenticationEKUExist = $true
AuthorizedSignatureNotRequired = $true
DangerousEnrollee = @('S-1-1-0')
Enabled = $false
distinguishedName = 'CN=SafeTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SafeTemplate'
}
Expand All @@ -391,13 +407,14 @@ InModuleScope 'Locksmith2' {
Mock 'Test-IssueExists' { $false }
}

It 'should return an LS2Issue for any enabled schema v1 template' {
It 'should return an LS2Issue for any enabled schema v1 template without client auth EKU' {
$template = New-MockLS2AdcsObject -Properties @{
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 1
Enabled = $true
distinguishedName = 'CN=SchemaV1Template,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SchemaV1Template'
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 1
Enabled = $true
AuthenticationEKUExist = $false
distinguishedName = 'CN=SchemaV1Template,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SchemaV1Template'
}
$script:AdcsObjectStore = @{ $template.distinguishedName = $template }

Expand All @@ -409,11 +426,12 @@ InModuleScope 'Locksmith2' {

It 'should return an issue with Technique SchemaV1' {
$template = New-MockLS2AdcsObject -Properties @{
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 1
Enabled = $true
distinguishedName = 'CN=SchemaV1Template,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SchemaV1Template'
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 1
Enabled = $true
AuthenticationEKUExist = $false
distinguishedName = 'CN=SchemaV1Template,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'SchemaV1Template'
}
$script:AdcsObjectStore = @{ $template.distinguishedName = $template }

Expand All @@ -422,6 +440,22 @@ InModuleScope 'Locksmith2' {
$result[0].Technique | Should -Be 'SchemaV1'
}

It 'should not return an issue when AuthenticationEKUExist is true' {
$safeTemplate = New-MockLS2AdcsObject -Properties @{
SchemaClassName = 'pKICertificateTemplate'
TemplateSchemaVersion = 1
Enabled = $true
AuthenticationEKUExist = $true
distinguishedName = 'CN=AuthEKUTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com'
Name = 'AuthEKUTemplate'
}
$script:AdcsObjectStore = @{ $safeTemplate.distinguishedName = $safeTemplate }

$result = @(Find-LS2VulnerableTemplate -Technique 'SchemaV1')

$result.Count | Should -Be 0
}

It 'should not return an issue when TemplateSchemaVersion is 2' {
$safeTemplate = New-MockLS2AdcsObject -Properties @{
SchemaClassName = 'pKICertificateTemplate'
Expand Down
Loading