Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parse sub command to perform stealthy offline AD CS enumeration based on local registry data #247

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

martanne
Copy link

@martanne martanne commented Jan 21, 2025

This adds a new certipy parse sub command which can be used to offline parse the content of the local registry certificate template cache as captured by either TrustedSec's req_query BOF or a .reg export as produced by the native regedit.exe utility.

It is based on The Registry Rundown research by Outflank's Cedric Van Bockhaven and Max Grim as presented at Troopers 24.

The implementation tries to minimize code duplication by implementing the existing LDAP interfaces using the data from the registry. As such modification to the generic vulnerability detection logic in find.py is mostly avoided.

The changes have been tested against Ludus' AD CS role.

First a regular LDAP enumeration was performed with:

$ certipy find -u [email protected] -p password -dc-ip 10.5.10.11

This was then compared to the data obtained by parsing the output of:

beacon> reg_query_recursive HKU .DEFAULT\Software\Microsoft\Cryptography\CertificateTemplateCache

With a command like:

$ certipy parse -format bof -domain ludus.domain -ca ludus-CA -published "ESC13, ESC9, ESC7_CERTMGR, ESC4, ESC3_CRA, ESC3, ESC2, ESC1, DirectoryEmailReplication, DomainControllerAuthentication, KerberosAuthentication, EFSRecovery, EFS, DomainController, WebServer, Machine, User, SubCA, Administrator" -sids "S-1-5-21-3291837554-245906837-2404182060-513,S-1-5-21-3291837554-245906837-2404182060-1104" beacon.log

Where the published certificate templates were looked up with:

beacon> ldapsearch "(objectclass=pKIEnrollmentService)" --attributes certificateTemplates --dn "CN=Configuration,DC=ludus,DC=domain" --ldaps

Similarly, the corresponding registry branch was exported from a client machine using the native regedit.exe utility and subsequently parsed with:

$ certipy parse -format reg -domain ludus.domain -ca ludus-CA -published "ESC13, ESC9, ESC7_CERTMGR, ESC4, ESC3_CRA, ESC3, ESC2, ESC1, DirectoryEmailReplication, DomainControllerAuthentication, KerberosAuthentication, EFSRecovery, EFS, DomainController, WebServer, Machine, User, SubCA, Administrator" -sids "S-1-5-21-3291837554-245906837-2404182060-513,S-1-5-21-3291837554-245906837-2404182060-1104" adcs.reg

Results were compared with the data obtained via LDAP after normalizing the JSON output with:

$ jq '[ ."Certificate Templates"|values[] ] | sort_by(."Template Name")' < bof.json  > bof.json.sorted

The only observed differences concern some template metadata fields (creation/modification date) and a few settings related to issuance policies.

@zimedev the changes should also be compatible with your certipy-merged repository.

This is in preparation for usecases such as offline registry parsing
where the CA certificate might not be available.
This is based on The Registry Rundown research by Outflank's
Cedric Van Bockhaven and Max Grim as presented at Troopers 24.

Given the output of TrustedSec's reg_query BOF of the certificate
template cache as found in the registry, parse template properties
and analyze them for possible vulnerabilities.

 reg_query_recursive HKU .DEFAULT\Software\Microsoft\Cryptography\CertificateTemplateCache

The introduced parse command works completely offline and does not
connect to any remote system. Therefore, CA information is not available
and the reported data might be outdated.

As a result, it is unknown whether a given template is actually
enabled/published. Manually query the `certificateTemplates` attribute
of objects with class `pKIEnrollmentService` over LDAP and supply the
comma separated list to the `-published` command line option.

 ldapsearch "(objectclass=pKIEnrollmentService)" --attributes certificateTemplates --dn "CN=Configuration,DC=testlab,DC=local"

Similarly, the `-vulnerable` option requires a list of SIDs you control.
These can be specified as a comma separated list to the `-sids` option.

For testing purposes, the sorted JSON output of the regular `certipy find`
and newly introduced `certipy parse` can be compared using the following
jq(1) one-liner:

 jq '[ ."Certificate Templates"|values[] ] | sort_by(."Template Name")'
This allows to parse .reg files as for example produced by regedit.exe
via the File > Export functionality.
This is solely for output purposes, to make result comparison with
LDAP easier.
@zimedev
Copy link

zimedev commented Jan 27, 2025

thank you very much

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.

2 participants