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
1 change: 1 addition & 0 deletions ansible/roles/vulns_adcs_esc10_case1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ ADCS ESC10 Case 1 - Disable strong certificate binding enforcement
### main.yml

- **Set StrongCertificateBindingEnforcement to 0** (ansible.windows.win_regedit)
- **Restart the KDC so the new binding mode takes effect** (ansible.windows.win_service) - Conditional

## Example Playbook

Expand Down
22 changes: 22 additions & 0 deletions ansible/roles/vulns_adcs_esc10_case1/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
# Pin the KDC binding mode explicitly rather than inheriting the Windows
# default. The default moved to Full Enforcement in the February 2025 hardening
# rollout (KB5014754), which silently closes every certificate route that relies
# on a weak (UPN/SAN) mapping: ESC6, ESC9 and ESC10 case 1 all die at once, on a
# lab whose CA-side probes still read green.
- name: Set StrongCertificateBindingEnforcement to 0
ansible.windows.win_regedit:
path: HKLM:\SYSTEM\CurrentControlSet\Services\Kdc
name: StrongCertificateBindingEnforcement
data: 0x0
type: dword
register: _scbe_pin
vars:
ansible_become: true
ansible_become_method: runas
domain_name: "{{ domain }}"
ansible_become_user: "{{ domain_username }}"
ansible_become_password: "{{ domain_password }}"

# The KDC reads this value when the service starts, so the write alone leaves
# the running KDC on its old mode. Nothing later in the vulns run reliably
# reboots this host, and that gap is invisible to any registry-reading check:
# validate would report the weak binding while authentication still refused it.
- name: Restart the KDC so the new binding mode takes effect
ansible.windows.win_service:
name: kdc
state: restarted
when: _scbe_pin is changed
vars:
ansible_become: true
ansible_become_method: runas
Expand Down
85 changes: 46 additions & 39 deletions ansible/roles/vulns_adcs_esc13/files/esc13.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -59,57 +59,55 @@ $ConfigNC = $ADRootDSE.configurationNamingContext
$IssuanceName = "IssuancePolicyESC13"
$ESC13Template = "CN=$esc13templateName,CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigNC"

# Generate a new unique OID
$OID = New-TemplateOID -ConfigNC $ConfigNC

# Define the path to the OID
$TemplateOIDPath = "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC"

# Create a new AD object with the generated OID
$oa = @{
'DisplayName' = $IssuanceName
'Name' = $IssuanceName
'flags' = [System.Int32]'2'
'msPKI-Cert-Template-OID' = $OID.TemplateOID
}
$theresults = New-ADObject -Path $TemplateOIDPath -OtherAttributes $oa -Name $OID.TemplateName -Type 'msPKI-Enterprise-Oid'

# Get the new OID object
$OIDContainer = "CN=OID,CN=Public Key Services,CN=Services,"+$ConfigNC
$OIDs = Get-ADObject -Filter * -SearchBase $OIDContainer -Properties DisplayName,Name,msPKI-Cert-Template-OID,msDS-OIDToGroupLink
$newOIDObj = ($OIDS | where {$_.DisplayName -eq $IssuanceName })
$newOIDValue = $newOIDObj | select -ExpandProperty msPKI-Cert-Template-OID

# Get the ESC13 template object for updating
$adObject = Get-ADObject $ESC13Template -Properties msPKI-Certificate-Policy

# Get the current policies
$policies = $adObject.'msPKI-Certificate-Policy'
# Reuse the issuance policy OID if this already ran. Creating one unconditionally
# leaves a second, unlinked OID object behind on every re-run (lab reset), points
# the template at both, and links only one of them.
# @() keeps a single match from unrolling to a bare string, whose [0] would be the
# character "C" rather than a distinguished name.
$existing = @(Get-ADObject -SearchBase $TemplateOIDPath -Filter { DisplayName -eq $IssuanceName } -Properties DisplayName, 'msPKI-Cert-Template-OID')

if ($existing.Count -gt 1) {
# Converge the duplicate state an earlier run may have left behind.
Write-Output "Removing $($existing.Count - 1) duplicate $IssuanceName OID object(s)"
$existing | Select-Object -Skip 1 | ForEach-Object {
Remove-ADObject -Identity $_.DistinguishedName -Confirm:$false
}
$existing = @($existing | Select-Object -First 1)
}

# Add new OID to the policies
$newPolicy = $newOIDValue # replace with your new OID
$policies = $newPolicy
if ($existing.Count -eq 0) {
$OID = New-TemplateOID -ConfigNC $ConfigNC
$oa = @{
'DisplayName' = $IssuanceName
'Name' = $IssuanceName
'flags' = [System.Int32]'2'
'msPKI-Cert-Template-OID' = $OID.TemplateOID
}
New-ADObject -Path $TemplateOIDPath -OtherAttributes $oa -Name $OID.TemplateName -Type 'msPKI-Enterprise-Oid'
$existing = @(Get-ADObject -SearchBase $TemplateOIDPath -Filter { DisplayName -eq $IssuanceName } -Properties DisplayName, 'msPKI-Cert-Template-OID')
}

# Convert policies to an array of strings
$policies = $policies | ForEach-Object { $_.ToString() }
$newOIDObj = $existing | Select-Object -First 1
$newOIDValue = $newOIDObj.'msPKI-Cert-Template-OID'
$esc13OID_dn = $newOIDObj.DistinguishedName
if (-not $esc13OID_dn) {
throw "Could not resolve the $IssuanceName OID object under $TemplateOIDPath"
}
$esc13OID_dn

# Update the ESC13 template AD object
Set-ADObject -Identity $adObject.DistinguishedName -Replace @{ 'msPKI-Certificate-Policy' = $policies }
# Point the ESC13 template at exactly this issuance policy
$adObject = Get-ADObject $ESC13Template -Properties msPKI-Certificate-Policy
Set-ADObject -Identity $adObject.DistinguishedName -Replace @{ 'msPKI-Certificate-Policy' = $newOIDValue.ToString() }

# Get DN of the ESC13 Group
$ludus_esc13_group_dn = (Get-ADGroup $esc13group).DistinguishedName
$ludus_esc13_group_dn

# Get Distinguished Name of the ESC13 OID Issuance Policy we created
# Thanks to Jonas (https://twitter.com/Jonas_B_K) for helping with this!
$ADRootDSE = Get-ADRootDSE
$ConfigurationNC = $ADRootDSE.configurationNamingContext
$OIDContainer = "CN=OID,CN=Public Key Services,CN=Services,"+$ConfigurationNC
$OIDs = Get-ADObject -Filter * -SearchBase $OIDContainer -Properties DisplayName,Name,msPKI-Cert-Template-OID,msDS-OIDToGroupLink
$esc13OID_dn = ($OIDS | where {$_.DisplayName -eq $IssuanceName }).DistinguishedName[0]
$esc13OID_dn

# Create a DirectoryEntry object for the Issuance Policy OID
# Thanks to Jonas (https://twitter.com/Jonas_B_K) for helping with this!
$object = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$esc13OID_dn")

# Set the msDS-OIDToGroupLink property to the DN of the ESC13 group
Expand All @@ -118,3 +116,12 @@ $object.Properties["msDS-OIDToGroupLink"].Value = $Toset
$object.CommitChanges()
$object.RefreshCache()
$object | select msDS-OIDToGroupLink

# The group link is what makes ESC13 exploitable, and a silent no-op here looks
# identical to success, so confirm it landed in the directory.
$link = Get-ADObject -Identity $esc13OID_dn -Properties 'msDS-OIDToGroupLink' |
Select-Object -ExpandProperty 'msDS-OIDToGroupLink'
if ($link -ne $ludus_esc13_group_dn) {
throw "msDS-OIDToGroupLink on $esc13OID_dn is '$link', expected '$ludus_esc13_group_dn'"
}
Write-Output "ESC13 linked: $esc13OID_dn -> $link"
14 changes: 11 additions & 3 deletions ansible/roles/vulns_adcs_esc7/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@
ansible.windows.win_powershell:
script: |
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Install-Module PSPKI -Force -ErrorAction Stop
if (-not (Get-Module -ListAvailable -Name PSPKI)) {
throw "PSPKI module installation failed"
# Guard the install the same way the NuGet task above does: -Force means a
# re-install would not error, but it still reaches out to PSGallery, so on
# a lab that already has PSPKI any egress hiccup becomes a fatal.
if (Get-Module -ListAvailable -Name PSPKI) {
$Ansible.Changed = $false
} else {
Install-Module PSPKI -Force -ErrorAction Stop
if (-not (Get-Module -ListAvailable -Name PSPKI)) {
throw "PSPKI module installation failed"
}
$Ansible.Changed = $true
}
error_action: stop

Expand Down
Loading
Loading