Certify 2.0
Aug 11 2025
By: Valdemar Carøe • 16 min read
TL;DR Due to modern advances in the AD CS attack landscape, an update to Certify was long overdue. Certify 2.0 delivers a suite of new capabilities and refined usability improvements, enhancing both its ease of use and overall effectiveness.
It’s been just about four years since Will Schroeder and Lee Chagolla-Christensen released Certify 1.0 along with the Certified Pre-Owned white-paper on abusing Active Directory Certificate Services (AD CS). With eight new privilege escalation techniques having been released since then, the AD CS exploitation landscape has changed a lot, and an update to accommodate these new techniques has been long overdue.
While developing Certify 2.0, we even discovered what we believed to be a new tradecraft for the existing ESC7 technique. However, we were later made aware by Jonas Bülow Knudsen that the technique had already been described by Carl Sörqvist in this blogpost. Nevertheless, we will be presenting the research at the end of this blogpost, so stay tuned for the juicy details!
What’s new in Certify 2.0?
Certify 1.0 had some pain points we tried to alleviate in Certify 2.0, both by modifying existing logic and introducing new features.
Template Status and Publishing CAs
Certify 1.0 did not provide any ways of enumerating disabled certificate templates, as the find command only printed certificate templates that a certificate authority (CA) published.
In addition to only printing published templates, any certificate template that multiple CAs published would print for each publishing CA, displaying the same information multiple times in the output stream, as shown in the snippet below.
CA Name : SERVER\CA-NAME-1
Template Name : TEMPLATE-NAME
...
CA Name : SERVER\CA-NAME-2
Template Name : TEMPLATE-NAME
...
This has been changed in Certify 2.0, where the enum-templates command by default includes all certificate templates regardless of publication status. A certificate template that has not been published by any CA will display an “Enabled: False” attribute.
Template Name : TEMPLATE-NAME
Enabled : False
Certificate templates that are published by CAs will display an “Enabled: True” attribute and a “Publishing CAs” attribute populated with all the CAs that publish the template.
Template Name : TEMPLATE-NAME
Enabled : True
Publishing CAs : SERVER\CA-NAME-1, SERVER\CA-NAME-2
Unreadable Certificate Templates
Certify 1.0 did not indicate whether or not a certificate template could be read by the executing principal, so while multiple certificate templates showed up in the output stream, there may have been other certificate templates published by the CA that were not displayed, as the user could not read their attributes.
This created a false sense of confidence that a user of the tool had a full overview of the PKI, and has been fixed in Certify 2.0 where the following message is printed for all the certificate templates that are published by a CA but whose attributes cannot be read.
[!] The enterprise certificate authority 'CA-NAME' publishes the following unreadable certificate templates:
TEMPLATE-NAME-1
TEMPLATE-NAME-2
Template Filters
Certify 1.0 supported three certificate template filters with its find command, which could help a user of the tool search for certificate templates with specific configurations.
- The /vulnerable parameter ensured that only certificate templates that Certify marked as vulnerable displayed. However, the filter did not mention which type of vulnerability had been identified, so the user was forced to find out for themselves.
- The /clientauth parameter ensured that only certificate templates that supported client authentication would be displayed. However, the filter did not include the ‘Any Purpose’ EKU nor ‘Subordinate CA’ (no EKUs), both of which permits client authentication.
- The /enrolleeSuppliesSubject parameter ensured that only certificate templates that allowed the enrollee to supply subject details displayed.
This has been changed in Certify 2.0, where the enum-templates command contains even more certificate template filters.
- The –filter-vulnerable (previously /vulnerable) parameter works the same as in Certify 1.0, but certificate templates now have a Vulnerabilities attribute that shows exactly which vulnerability type has been identified for the certificate template.
- The –filter-client-auth (previously /clientauth) parameter works the same as in Certify 1.0, but now also supports the ‘Any Purpose’ EKU and ‘Subordinate CA’ (no EKUs).
- The –filter-supply-subject (previously /enrolleeSuppliesSubject) parameter works the same as in Certify 1.0.
- The –filter-enabled parameter is a filter that displays only certificate templates that are published by a CA (i.e., certificate templates that are enabled).
- The –filter-request-agent parameter is a filter that displays only certificate templates that can be enrolled in “on-behalf-of” other users using an enrollment agent certificate.
- The –filter-manager-approval parameter is a filter that displays only certificate templates that require manager approval before issuance.
Template Vulnerability Attribution
The find command in Certify 1.0 had a /currentuser parameter which allowed you to change vulnerability attribution from built-in low-privilege domain groups to any group that the current user is a member of.
However, from personal experience, I have often wanted to know if a specific principal could abuse any PKI vulnerabilities before I decided whether to authenticate as the principal.
Certify 2.0, while supporting the –current-user (previously /currentuser) parameter, now also supports a –target-user <username> parameter, which sets vulnerability attribution based on the group memberships of a target user without authenticating as that user. This can allow the adversary to evaluate if authentication as the stolen user is worth the risk.
Template Attributes
Certify 1.0 had some rather ambiguous attribute display names when using the find command to enumerate certificate templates. For example, the certificate template overview contained two attributes named Application Policies and mspki-certificate-application-policy, where one describes which application policies are required to enroll in a certificate (“on-behalf-of” another user), and the other describes which application policies are granted by the certificate template.
For the average user, these names were very confusing, so we simplified the display names for all certificate template attributes in Certify 2.0.
Attribute Name in Certify 1.0 | Attribute Name in Certify 2.0 |
msPKI-Certificate-Name-Flag | Certificate Name Flag |
mspki-enrollment-flag | Enrollment Flag |
Application Policies | Required Application Policies |
Issuance Policies | Required Issuance Policies |
mspki-certificate-application-policy | Certificate Application Policies |
pkiextendedkeyusage | Extended Key Usage |
We have also introduced three new certificate template attributes in Certify 2.0.
- The Manager Approval attribute shows if manager approval is required for issuance (if the “PEND_ALL_REQUESTS” flag is included in the Enrollment Flag attribute).
- The Certificate Issuance Policies attribute shows issuance policies granted by the certificate template, which is necessary for identifying ESC13 vulnerabilities.
- The Vulnerabilities attribute shows all vulnerability attributions identified for individual certificate templates.
Certificate Authority Attributes
Certify 1.0 did not parse all the CA configurations required to identify some of the newer escalation techniques such as ESC11 and ESC16. This has been fixed in Certify 2.0, which parses all required settings and introduces a range of new CA attributes.
- The RPC Request Encryption attribute show whether encryption is enforced for the ICertPassage RPC interface (as required for ESC11).
- The RPC Request Restrictions attribute show if there are any restrictions in place for the ICertPassage RPC interface (as required for ESC11).
- The Disabled Extensions attribute shows all certificate extensions that are disabled on the CA (as required for ESC16).
- The Vulnerabilities attribute shows all vulnerability attributions identified for individual certificate authorities.
Parameters
Command parameters in Certify 1.0 was a bit tricky, as it was not clearly defined under which circumstances specific parameters were valid. For example, when using the request command with the /onbehalfof parameter, half of the arguments normally supported by the command would be invalidated, as the request command was actually two different actions baked into a single command.
- Certify.exe request /ca: /template: [/subject:] [/altname:] [/url:] [/sid:] [/machine] [/install]
- Certify.exe request /ca: /template: /onbehalfof: /enrollcert: [/enrollcertpw:] [/machine]
The regular self-service request action accepted subject detail parameters, while the opposite on-behalf-of request action accepted enrollment agent parameters. This was a great cause for confusion, and has been remediated by splitting individual actions into separate commands, each with their own complete parameter list. Every command in Certify 2.0 supports printing its entire parameter pool using the –help parameter.
Certificate Request Output Format
Certificates requested with Certify 1.0 are printed to the console in PEM format and require manual transformation into the PKCS12 format using OpenSSL or other third-party software.
[*] cert.pem :
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAuGXP2Jc/vrASRjkyCaxPEAvkANhRGTtMjlToMKyoJlMJ04oY ...
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIGajCCBVKgAwIBAgITYAAAALjbpQqmG8BKzAAAAAAAuDANBgkqhkiG9w0BAQsF ...
-----END CERTIFICATE-----
[*] Convert with: openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out cert.pfx
This is detrimental to Certify being able to act as a standalone tool for the Windows platform, as OpenSSL is not installed on Windows by default and often requires a separate UNIX system or Windows Subsystem for Linux (WSL). This is especially not ideal for usage in remote customer environments, so in Certify 2.0, we changed the output logic to print the certificates in a base64-encoded PKCS12 format by default.
[*] Certificate (PFX) :
MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqhkiG9w0BBwGggCSABIID...
The printed format is directly usable with Rubeus, using the /certificate parameter of the asktgt command, but the old Certify 1.0 output format is still supported by any of the Certify 2.0 request commands using the –output-pem parameter.
Downloading Issued Certificates
Certificates requested with Certify 1.0 have their private key printed to the console regardless of whether the CA issued the certificate request.
[*] cert.pem :
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAuGXP2Jc/vrASRjkyCaxPEAvkANhRGTtMjlToMKyoJlMJ04oY
...
-----END RSA PRIVATE KEY-----
If the certificate request is issued at a later time (e.g. by manager approval or an ESC7 abuse technique), we could download it using the download command in Certify 1.0.
[*] cert.pem :
-----BEGIN CERTIFICATE-----
MIIGajCCBVKgAwIBAgITYAAAALjbpQqmG8BKzAAAAAAAuDANBgkqhkiG9w0BAQsF
...
-----END CERTIFICATE-----
Once both the private key and certificate has been obtained, we can merge these together in a single PEM file and use OpenSSL to manually transform it into a PKCS12 format.
It can be tedious to assemble these files manually and transform them using OpenSSL, so we have streamlined the entire process in Certify 2.0. Whenever a certificate request fails or the CA does not issue it, the private key is printed in a base64-encoded format.
[*] Private Key (PEM) :
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ0KTUlJRW93SUJBQUtDQVFF...
If the certificate request is issued at a later time, the certificate can be downloaded by using the request-download command in Certify 2.0. By default, the certificate will be printed in the same PEM format from Certify 1.0, but if the private key is supplied through the –private-key <key> parameter, the tool will automatically transform and output the certificate in a base64-encoded PKCS12 format.
[*] Certificate (PFX) :
MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqhkiG9w0BBwGggCSABIID...
The printed format is directly usable with Rubeus, using the /certificate parameter of the asktgt command, but the old PEM output format is still supported by the request-download command in Certify 2.0 using the –output-pem parameter.
Subject Alternative Name (SAN) Types
In Certify 1.0, when requesting certificates for which the enrollee supplies subject details, it was possible to define a Subject Alternative Name (SAN) using the /altname parameter. However, the parameter supported only the UserPrincipalName SAN format.
In Certify 2.0, we added support for multiple SAN formats, such that the tool can be used for abuse scenarios where the SAN details require more fine-grained control.
- The –upn <upn> parameter adds a User Principal Name (UPN) SAN to the certificate request.
Example: administrator or administrator@corp.local. - The –dns <dns> parameter adds a Domain Name System (DNS) SAN to the certificate request.
Example: dc01.corp.local. - The –email <email> parameter adds an RFC822 SAN to the certificate request.
Example: user@corp.com.
Application Policies in Request
Certify 1.0 did not support certificate requests containing application policy extensions and, as such, did not facilitate the necessary functionality to perform the ESC15 abuse scenario.
This is supported by Certify 2.0, where an –application-policy parameter has been added to the following commands:
- The request command, so it is possible to inject application policies when requesting certificates by regular means.
- The request-agent command, so it is possible to inject application policies when requesting certificates “on-behalf-of” other users using an enrollment agent.
- The manage-ca command, so it is possible to inject application policies when managing certificate requests that are pending manager approval.
Certificate Renewal
Certify 1.0 did not support certificate renewal, as described in Account Persistence Technique 3 (PERSIST3). However, this functionality has been added to Certify 2.0, and is simple to use.
The request-renew command allows an adversary to request renewal of an issued certificate. The –cert-pfx parameter accepts the issued certificate in the base64-encoded PKCS12 format and the renewed certificate is also printed in the same format.
Certify.exe request-renew --ca <ca> --cert-pfx <base64-pfx>
_____ _ _ __
/ ____| | | (_)/ _|
| | ___ _ __| |_ _| |_ _ _
| | / _ \ '__| __| | _| | | |
| |___| __/ | | |_| | | | |_| |
\_____\___|_| \__|_|_| \__, |
__/ |
|___./
v2.0.0
[*] Action: Request a certificate renewal
[*] Current user context : DOMAIN\USERNAME
[*] Certificate Authority : SERVER\CA-NAME
[*] CA Response : The certificate has been issued.
[*] Request ID : 1
[*] Certificate (PFX) :
MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqh...
Certificate Forgery
Certify 1.0 was initially released alongside another tool called ForgeCert, which is a tool for creating self-signed certificates using stolen signing certificates from a CA as described in Domain Persistence Technique 1 (DPERSIST1).
This functionality has been merged into Certify 2.0 under the guise of the forge command and extended to accept a range of parameters making it capable of forging self-signed certificates equivalent to what we could have requested using the request command.
Certify.exe forge --ca-cert <file> --ca-pass <pass> --upn <upn> --sid <sid>
_____ _ _ __
/ ____| | | (_)/ _|
| | ___ _ __| |_ _| |_ _ _
| | / _ \ '__| __| | _| | | |
| |___| __/ | | |_| | | | |_| |
\_____\___|_| \__|_|_| \__, |
__/ |
|___./
v2.0.0
[*] Action: Forge a (golden) certificate
CA Certificate Information:
Subject: CN=CORP-CA01-CA, DC=corp, DC=local
Issuer: CN=CORP-CA01-CA, DC=corp, DC=local
Start Date: 21/05/2025 12.13.47
End Date: 21/05/2030 12.23.46
Thumbprint: CFBDC6826AC074EF86BE7774F959A2FF5F322DDC
Serial: 12D3E574A63DE7854A9E5A1F4CD56490
Forged Certificate Information:
Subject: CN=User
SubjectAltName: <upn>
Issuer: CN=CORP-CA01-CA, DC=corp, DC=local
Start Date: 15/07/2025 12.27.54
End Date: 15/07/2026 12.27.54
Thumbprint: EEFD6BFDB467038EC7BE8EF6D07B40DC3CC74E73
Serial: 00F5AD22A765158613CDDF6B2804E401FC
Forged certificate (PFX):
MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqh...
The printed format is directly usable with Rubeus, using the /certificate parameter of the asktgt command.
Certificate Template Management
Certify 1.0 had the capability to show access controls for certificate templates and could even detect ESC4 vulnerabilities, but did not have any functionality for carrying out the attack.
Introducing the new manage-template command in Certify 2.0. This command is dedicated to carrying out certificate template modifications over LDAP for ESC4 abuse scenarios, such as modifying access controls, authorized signature requirements, extended key usages, or toggling manager approval (for ESC7) and/or the security extension (for ESC9).
Certify.exe manage-template --template <template> --client-auth
_____ _ _ __
/ ____| | | (_)/ _|
| | ___ _ __| |_ _| |_ _ _
| | / _ \ '__| __| | _| | | |
| |___| __/ | | |_| | | | |_| |
\_____\___|_| \__|_|_| \__, |
__/ |
|___./
v2.0.0
[*] Action: Manage a certificate template
[*] Using the search base 'CN=Configuration,DC=corp,DC=local'
[*] Attempting to toggle targeted EKUs on the certificate template.
[*] Successfully modified the certificate template.
Certify completed in 00:00:00.0891826
Certificate Authority Management
Certify 1.0 had the capability to show access controls for CAs allowing for the detection of ESC7 vulnerabilities, but did not have any functionality for carrying out the attack.
Introducing the new manage-ca command in Certify 2.0. This command is dedicated to carrying out administrative CA actions over DCOM for ESC7 abuse scenarios, such as modifying access controls (role delegations), publishing/disabling certificate templates, issuing and/or denying certificate requests, revoking issued certificates, and more.
Certify.exe manage-ca --ca <ca> --template <template>
_____ _ _ __
/ ____| | | (_)/ _|
| | ___ _ __| |_ _| |_ _ _
| | / _ \ '__| __| | _| | | |
| |___| __/ | | |_| | | | |_| |
\_____\___|_| \__|_|_| \__, |
__/ |
|___./
v2.0.0
[*] Action: Manage a certificate authority
[*] Attempting to modify published templates on the CA.
[*] Attempting to save a list of published certificate templates on the CA.
...
[+] Successfully set the list of published certificate templates on the CA.
Certify completed in 00:00:00.2817272
Expanding ESC7 Exploitation
The ESC7 escalation technique, as Will Schroeder and Lee Chagolla-Christensen describe it in Certified Pre-Owned, showcases an abuse scenario for the Manage CA role that leads to a guaranteed elevation of privileges, while the only abuse scenario shown for the less privileged Manage Certificates role, is that the holder of this role can issue certificates pending manager approval. There is no guarantee that this will lead to an elevation of privileges.
Since the inception of this technique, many new ways to abuse the Manage CA role have been published by security researchers around the world, but nothing worthwhile has been released for the Manage Certificates role.
Even though we refer to the role as Manage Certificates, in the security tab of the CA, it is actually called Issue and Manage Certificates.
This led me down the rabbit hole of thinking that “if we have currently only considered the Issue part for ESC7 abuse scenarios, what could be possible with the Manage part?”.
While I was implementing the manage-ca command in Certify 2.0, I looked through the documentation for the ICertAdmin (MS-CSRA) interface and noticed that there were two interesting functions called SetAttributes and SetExtension that, according to this page required only the “Officer” (Manage Certificates) role.
The SetAttributes function allows us to set attribute pairs in pending certificate requests, such as the SAN attribute used during ESC6; however, if the CA is not vulnerable to ESC6, the attribute would be ignored when issuing the certificate.
The SetExtension function allows us to set extensions on pending certificate requests, such as the SAN extension; however, when issuing the certificate, the default values from the certificate template overwrite all of our changes.
Effectively, it seemed like it was not possible to do anything worthwhile using any of these two functions…but then I got an idea. What if I append a certificate extension that is not included in the certificate template and thus does not get overwritten by a default value from the template?
The first extension I could think of was an issuance policy extension (as is used in ESC13), since they are pretty rare and not found in every certificate template.
I tried to append an issuance policy with OID “1.1.1.1” to the certificate template, and while I did not expect it to remain when issuing the certificate, against all odds, it did!
This effectively shows that the Manage Certificates role is able to modify certificate template extensions as long as there is no default extension defined for the template in question. While I could only come up with the issuance policy scenario, I am sure that there are more extensions that I’ve overlooked, so there might be even more ways to abuse this.
Conclusion
The AD CS attack landscape has evolved significantly since the initial release of Certify and the Certified Pre-Owned white-paper. With Certify 2.0, we have delivered a suite of new capabilities and refined usability enhancements, ensuring it remains a valuable asset for red teamers and penetration testers navigating modern AD CS challenges.
Try It Today!
Certify 2.0 is available now on GitHub. We have also introduced a brand new wiki with an overview of commands and comprehensive usage examples for a variety of AD CS attack techniques.
Join us in the #certify-chat channel in the BloodHound Slack for any questions or feedback, and feel free to report any issues on GitHub. We hope you’re as excited this new update as we are!