CertificateAutoenrollment

Summary

FSProtect ACL Alias

CertificateAutoEnrollment

Affected Object Types

Certificate Templates

Exploitation Certainty

Certain

AD Permission Guid

a05b8cc2-17bc-4802-a710-e7c15ab866a2

Description

The CertificateAutoEnrollment permission allows accounts in an Active Directory Enterprise PKI to automatically request digital certificates from an Enterprise CA. Certificates are important for secure authentication and encrypted communications. They confirm identities, secure email, enable VPN access, and allow code signing. With automatic certificate enrollment, devices and users can obtain certificates that meet security requirements without manual intervention.

However, if certificate template permissions are misconfigured or are granted to untrusted accounts, serious security risks may result. Unauthorized users could obtain certificates without proper checks and use them to impersonate trusted services, intercept communications, sign malicious code, or decrypt sensitive data. This can enable attackers to bypass security controls, escalate privileges, and maintain persistence in the network.

How to trigger

The Certificate Auto-Enrollment mechanism is triggered by several events. Common triggers include the following:

You can configure this via: Computer/User Configuration > Policies > Windows Settings > Security Settings > Public Key Policies > Certificate Services Client – Auto-Enrollment

1. User logon

When a user logs on, the system checks for required certificates and automatically enrolls or renews them if necessary.

2. Computer startup

At startup, a domain-joined computer verifies its machine certificate status and initiates autoenrollment for any missing or expiring certificates.

3. Group Policy refresh

During a routine Group Policy refresh (approximately every 90 minutes), the system reviews certificate statuses and triggers autoenrollment as needed.

4. Certificate renewal period

When a certificate reaches its renewal threshold (for example, 80% of its validity period), autoenrollment renews it before expiration.

5. Certificate template configuration changes

Updates to certificate template settings cause clients to re-evaluate and, if necessary, enroll for certificates based on the new configuration.

6. Manual Group Policy update (gpupdate /force)

Running a forced Group Policy update immediately triggers the autoenrollment process to process any pending certificate enrollments or renewals.

Identification

Active Directory PowerShell

You can enumerate CertificateAutoEnrollment entries using the ActiveDirectory PowerShell module.

1. Find-CertificateAutoenrollment function

function Find-CertificateAutoenrollment {
    [CmdletBinding()]
    param ([string]$Target = $null,[string]$SearchBase = $null,[string]$OutputPath = "CertificateAutoenrollment.csv", [switch]$ExcludeAdmins = $false)
    Import-Module ActiveDirectory -ErrorAction Stop
    Write-Host "Gathering Active Directory Certificate Templates and inspecting ACLs for explicit 'Certificate Autoenrollment' permissions..."
    $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow;
    $ActiveDirectoryRights = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight;
    $CertificateAutoenrollmentGuid = "a05b8cc2-17bc-4802-a710-e7c15ab866a2";
    $ExcludedSIDs = @()
    if ($ExcludeAdmins) {
        Write-Host "Excluding default administrative groups and built-in accounts."
        $ExcludedSIDs += (New-Object System.Security.Principal.NTAccount "NT AUTHORITY\SYSTEM").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "NT AUTHORITY\SELF").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "BUILTIN\Account Operators").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "BUILTIN\Administrators").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS").Translate([System.Security.Principal.SecurityIdentifier])
        try {
            $ExcludedSIDs += (Get-ADGroup -Identity "Domain Admins").SID ;$ExcludedSIDs += (Get-ADGroup -Identity "Enterprise Admins").SID
            $ExcludedSIDs += (Get-ADGroup -Identity "Schema Admins").SID ;$ExcludedSIDs += (Get-ADGroup -Identity "Cert Publishers").SID
            $ExcludedSIDs += (Get-ADGroup -Identity "Group Policy Creator Owners").SID ;$ExcludedSIDs += (Get-ADGroup -Identity "Domain Controllers").SID
            $ExcludedSIDs += (Get-ADGroup -Identity "Key Admins").SID ;$ExcludedSIDs += (Get-ADGroup -Identity "Enterprise Key Admins").SID
            $ExcludedSIDs += (Get-ADGroup -Identity "Enterprise Read-only Domain Controllers").SID ;$ExcludedSIDs += (Get-ADGroup -Identity "RAS and IAS Servers").SID
        }
        catch {
            Write-Warning "Could not resolve one or more default domain admin groups (e.g., Domain Admins, Enterprise Admins, Schema Admins). They might not be filtered from results."
        }
    }
    $foundAcls = @()
    $objectsToScan = @()
    try {
        if ($Target) {
            Write-Host "Searching for permissions on specific object: '$Target'."
            $specificObject = Get-ADObject -Identity $Target -Properties nTSecurityDescriptor -ErrorAction Stop
            if ($specificObject) {
                $objectsToScan += $specificObject
            } else {
                Write-Output "Object '$Target' not found."
                return
            }
        } else {
            $configNC = (Get-ADRootDSE).ConfigurationNamingContext
            $adObjectParams = @{
                Filter    = "ObjectClass -eq 'pKICertificateTemplate'"
                Properties = "nTSecurityDescriptor"
                ErrorAction = "Stop"
            }
            if ($SearchBase) {
                $adObjectParams.Add("SearchBase", $SearchBase)
                Write-Host "Searching for certificate templates within '$SearchBase'."
            } else {
                $adObjectParams.Add("SearchBase", $configNC)
                Write-Host "Searching for all certificate templates in the Configuration Naming Context."
            }
            $objectsToScan = Get-ADObject @adObjectParams
        }
        if (-not $objectsToScan) {
            Write-Output "No Active Directory Certificate Templates found matching the criteria."
            return
        }
        foreach ($obj in $objectsToScan) {
            $ObjectDistinguishedName = $obj.DistinguishedName;
            try {
                $acl = Get-Acl -Path "AD:$ObjectDistinguishedName"
                foreach ($ace in $acl.Access) {
                    $isExcluded = $false
                    if ($ExcludeAdmins) {
                        try {
                            if ($ExcludedSIDs -contains $ace.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])) {
                                $isExcluded = $true
                            }
                        }
                        catch {
                            Write-Warning "Could not translate SID for exclusion check: $($ace.IdentityReference.Value). Error: $($_.Exception.Message)"
                        }
                    }
                    if ($ace.AccessControlType -eq $AccessControlType -and
                        ($ace.ActiveDirectoryRights -band $ActiveDirectoryRights) -and # Use -band for bitwise comparison
                        ($ace.ObjectType -eq $CertificateAutoenrollmentGuid) -and
                        -not $ace.IsInherited -and
                        -not $isExcluded) { # Apply exclusion filter
                        $foundAcls += [PSCustomObject]@{
                            'Vulnerable Template' = $ObjectDistinguishedName
                            'Internal Threat'     = $ace.IdentityReference.Value # Get the string representation
                        }
                    }
                }
            }
            catch {
                Write-Warning "Could not retrieve ACL for '$ObjectDistinguishedName': $($_.Exception.Message)"
            }
        }
    }
    catch {
        Write-Error "Failed to retrieve Active Directory Certificate Templates: $($_.Exception.Message)"
        return
    }
    if ($foundAcls.Count -gt 0) {
        $exclusionMessage = if ($ExcludeAdmins) { " (excluding default admin groups and built-in accounts)" } else { "" }
        Write-Host "Found $($foundAcls.Count) Active Directory certificate template(s) with explicit 'Certificate Autoenrollment' permissions$exclusionMessage."
        try {
            $foundAcls | Sort-Object -Unique 'Vulnerable Template', 'Internal Threat' | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
            Write-Output "Results exported successfully to '$OutputPath'"
        }
        catch {
            Write-Error "Failed to export results to CSV file '$OutputPath': $($_.Exception.Message)"
        }
    } else {
        $exclusionMessage = if ($ExcludeAdmins) { " (excluding default admin groups and built-in accounts)" } else { "" }
        Write-Output "No Active Directory certificate template(s) found with explicit 'Certificate Autoenrollment' permissions$exclusionMessage."
    }
}

2. Scan all templates in the domain

Find-CertificateAutoenrollment

3. Scan a specific template

Find-CertificateAutoenrollment -Target "CN=DirectoryEmailReplication,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=forestall,DC=labs"

4. To exclude default admin ACLs for better visibility

Find-CertificateAutoenrollment -ExcludeAdmins

.NET Directory Services

By using PowerShell’s built-in .NET System.DirectoryServices APIs, you can enumerate CertificateAutoEnrollment entries without relying on external modules or additional dependencies.

1. Find-CertificateAutoenrollmentSimple function

function Find-CertificateAutoenrollmentSimple {
    [CmdletBinding()]
    param ([string]$Target = $null,[string]$OutputPath = "CertificateAutoenrollment.csv")
    $CertificateAutoenrollmentGuid = [Guid]"a05b8cc2-17bc-4802-a710-e7c15ab866a2"
    $results = @()
    try {
        if ($Target) {
            Write-Verbose "Binding directly to specified DN: $Target"
            $templates = @(New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target"))
        } else {
            Write-Verbose "Enumerating all certificate templates from ConfigurationNamingContext..."
            $rootDSE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
            $configNC = $rootDSE.Properties["configurationNamingContext"][0]
            $searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$configNC")
            $searcher.Filter = "(objectClass=pKICertificateTemplate)"
            $searcher.PageSize = 1000
            $searcher.PropertiesToLoad.Add("distinguishedName") | Out-Null
            $hits = $searcher.FindAll()
            $templates = foreach ($hit in $hits) {
                try { $hit.GetDirectoryEntry() } catch { Write-Warning "Error binding: $_"; continue }
            }
        }
        foreach ($entry in $templates) {
            $dn = $entry.distinguishedName
            Write-Verbose "Checking ACLs on: $dn"
            try {
                $acl  = $entry.ObjectSecurity
                $aces = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])
            } catch {
                Write-Warning "Failed to retrieve ACL for $dn : $_"
                continue
            }
            foreach ($ace in $aces) {
                if (
                    $ace.AccessControlType -eq "Allow" -and
                    ($ace.ActiveDirectoryRights -band [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight) -and
                    $ace.ObjectType -eq $CertificateAutoenrollmentGuid -and
                    -not $ace.IsInherited
                ) {
                    $principal = try {
                        $ace.IdentityReference.Translate([System.Security.Principal.NTAccount]).Value
                    } catch {
                        $ace.IdentityReference.Value
                    }
                    $results += [PSCustomObject]@{
                        Template   = $dn
                        Principal  = $principal
                        Right      = "Certificate Autoenrollment"
                    }
                }
            }
        }
        if ($results.Count -gt 0) {
            $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
            Write-Host "Exported $($results.Count) entr$(if($results.Count -eq 1){'y'}else{'ies'}) to $OutputPath"
        } else {
            Write-Host "No certificate autoenrollment ACLs found."
        }
    } catch {
        Write-Error "Unhandled error : $_"
    }
}

2. Scan all certificate templates in the domain

Find-CertificateAutoenrollmentSimple

3. Scan a specific template object

Find-CertificateAutoenrollmentSimple -Target "CN=KerberosAuthentication,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=forestall,DC=labs"

Certification Authority

1. Open Certification Authority (certsrv) on your Windows server.

2. Expand the CA name and right-click Certificate Templates.

3. Select Manage from the context menu.

4. In the Certificate Templates Console window, double-click the vulnerable Certificate Template.

5. In the Properties window, navigate to the Security tab.

6. In the Security Settings window, locate and select the relevant Access Control Entry (ACE) for the user or group you wish to configure.

7. In the permissions list, ensure the Autoenroll option is set appropriately (enable for trusted accounts, remove for untrusted accounts).

8. Click OK to save your changes and close the dialogs.

Exploitation

The CertificateAutoEnrollment permission is not a standalone vulnerability and cannot be exploited on its own. However, when combined with other attack vectors (for example, ESC1), it can be an enabler in a larger exploit chain by facilitating lateral movement or privilege escalation.

Mitigation

1. Open Certification Authority (certsrv) on your Windows server.

2. Expand the CA name and right-click Certificate Templates.

3. Select Manage from the context menu.

4. In the Certificate Templates Console window, double-click the vulnerable Certificate Template.

5. In the Properties window, navigate to the Security tab.

6. In the Security Settings window, locate and remove the Autoenroll permission from any untrusted or unnecessary user or group.

7. Click OK to save your changes and close the dialogs.

Detection

Adding new Access Control Entries to Active Directory objects changes the ntSecurityDescriptor attribute of those objects. These changes can be detected with Event IDs 5136 and 4662 to identify potentially risky or unauthorized modifications. Event ID 4886 indicates that a certificate request has been received by the Certification Authority, while Event ID 4887 confirms that the request has been approved and the certificate issued. These logs are important for monitoring certificate operations and maintaining security.

Event ID
Description
Fields/Attributes
References

5136

A directory service object was modified.

ntSecurityDescriptor

https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-5136

4662

An operation was performed on an object.

AccessList, AccessMask

https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662

4886

Certificate Services received a certificate request.

CertificateTemplate, Requester

https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn319076(v=ws.11)

4887

Certificate Services approved a certificate request and issued a certificate.

CertificateTemplate, Requester

https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn319076(v=ws.11)

References

Last updated

Was this helpful?