Skip to content

Conversation

@vilacham
Copy link

@vilacham vilacham commented Sep 6, 2025

Fix ESC1 false negatives in template enumeration

Summary

This PR improves ESC1 detection in PSPKIAudit by addressing false negatives observed in specific template configurations. The change refines the logic that determines whether a template is exploitable for ESC1.

Background

According to Microsoft documentation (MS-CRTD) enrollment permissions for a certificate template are evaluated based on two processing rules:

First rule

  • The requester SID matches the SID associated with the ACE.
  • The ACE type is ACCESS_ALLOWED_OBJECT_ACE.
  • The access mask has the bit 0x00000100 set (Control Access).
  • The ObjectType field corresponds to the Enroll GUID (0e10c968-78fb-11d2-90d4-00c04f79dc55).

Second rule

  • The requester SID matches the SID associated with the ACE.
  • The ACE type is ACCESS_ALLOWED_ACE.
  • The access mask has the bit 0x00000100 set.

These rules define how Active Directory evaluates whether a principal can enroll for a certificate. In addition to Microsoft’s specification, further practical details and lab-based examples can be found in my previous articles The Schrödinger’s ESC1 Vulnerability and The Schrödinger’s ESC1 Vulnerability: Benchmark Update, where these conditions were analyzed across different tool implementations.

Problem

In some scenarios, PSPKIAudit failed to flag templates as ESC1-vulnerable even when they were exploitable. This was reproducible in a controlled lab with eight dedicated templates (four vulnerable, four non-vulnerable).

Root Cause

In PSPKIAudit/Code/Invoke-PKIAudit.ps1, the function Test-CanLowPrivEnrollInTemplate does not implement both enrollment-processing rules defined by Microsoft’s specification (MS-CRTD). The current logic effectively checks only Rule 1 by validating the presence of the Enroll object-specific extended right. Because an ACE with an ObjectType is, by definition, an ACCESS_ALLOWED_OBJECT_ACE, the function implicitly assumes the ACE type and does not verify it explicitly.

As a result, Rule 2 is not considered. This omission leads to false negatives in cases where enrollment is legitimately granted through a standard ACE with Control Access.

Solution

Key changes

  • Implemented MS-CRTD enrollment checks in PSPKIAudit/Code/Invoke-PKIAudit.ps1

    • Rewrote Test-CanLowPrivEnrollInTemplate to read the template’s AD ACL by DN and enforce:
      • ACCESS_ALLOWED_OBJECT_ACE with Control Access bit set and ObjectType = Enroll GUID
      • ACCESS_ALLOWED_ACE with Control Access bit set
    • Updated function docs to require ActiveDirectory module (instead of PSPKI).
  • Replaced low-priv principal regex with non-admin check in PSPKIAudit/Code/Invoke-PKIAudit.ps1

    • Added Get-AdminSids to build an admin SID set (well-known SIDs, Administrator, krbtgt, key admin groups, and all their members).
    • Test-CanLowPrivEnrollInTemplate now skips ACEs assigned to admin SIDs and evaluates only non-admin principals.
  • Improved DACL rendering for templates in PSPKIAudit/Code/Invoke-PKIAudit.ps1

    • Added Get-CertificateTemplateDACLString to build DACL output from AD (nTSecurityDescriptor), listing entries as “DOMAIN\sam (Allow|Deny) - Rights”.
    • Resolved identities to DOMAIN\sam via NTAccount, falling back to SID if translation fails.
    • Updated both Get-AuditCertificateTemplate code paths to use the new function, fixing incomplete ACL displays seen with Get-CertificateTemplateAcl.

What this means

  • Enrollment rights are now determined exactly per MS-CRTD:
    • Rule 1: ACCESS_ALLOWED_OBJECT_ACE + Control Access + Enroll GUID.
    • Rule 2: ACCESS_ALLOWED_ACE (standard) + Control Access bit.
  • Enrollment permissions are now listed for all low-privileged (non-admin) principals instead of only the ones listed in CommonLowprivPrincipals.
  • All the ACEs in the DACL are now printed based on the definitions present in MS-ADTS

This aligns PSPKIAudit’s behavior with the spec and eliminates the observed false negatives for templates that rely on a standard Control Access grant or that grant enrollment permissions to a low-privileged principal not listed in CommonLowprivPrincipals.

Testing

  • Environment: Terraform + GCP lab consisting of a Domain Controller, a Certificate Authority, a Windows Server (for .exe and .ps1 tools), and an Ubuntu machine (for Python tools).

  • Vulnerable templates: Four vulnerable certificate templates, with screenshots of their security descriptors and the corresponding vulnerable ACEs included.

    • TestCase1
      • ACE Type: ACCESS_ALLOWED_OBJECT_ACE
      • Principal: Domain Users group
      • Access Mask: 0x00000130
      • Object GUID: 0e10c968–78fb-11d2–90d4–00c04f79dc55
    • TestCase2
      • ACE Type: ACCESS_ALLOWED_OBJECT_ACE
      • Principal: Specific low-privileged user (alice)
      • Access Mask: 0x00000130
      • Object GUID: 0e10c968–78fb-11d2–90d4–00c04f79dc55
    • TestCase3
      • ACE Type: ACCESS_ALLOWED_ACE
      • Principal: Domain Users group
      • Access Mask: 0x00000100
    • TestCase4
      • ACE Type: ACCESS_ALLOWED_OBJECT_ACE
      • Principal: Specific low-privileged user (alice)
      • Access Mask: 0x00000100
  • Reproduction:

    1. Run Invoke-PKIAudit using the modified version of PSPKIAudit (this branch).
      2025-09-06 16_49_08
      2025-09-06 17_04_00
      2025-09-06 17_04_21
      2025-09-06 17_04_43
      2025-09-06 17_05_06
      2025-09-06 17_05_26

    2. Run the same Invoke-PKIAudit command using the original PSPKIAudit release for comparison.
      2025-09-06 17_09_36
      2025-09-06 17_22_44
      2025-09-06 17_23_11

    3. For the vulnerable cases where the original PSPKIAudit failed to detect ESC1 (false negatives), privilege escalation using certipy req and certipy auth was demonstrated to confirm that the templates were indeed exploitable.
      2025-09-06 17_27_08
      2025-09-06 17_28_11
      2025-09-06 17_28_54

Result

All four vulnerable templates are now correctly reported as ESC1 by the modified PSPKIAudit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant