PassTheCert. Bypassing the lack of a PKINIT mechanism in Active Directory

Introduction

Study SpecterOps “Certified Pre-Owned”about Certificate Services Abuse Active Directory (ADCS)has made it even easier for white hat hackers to gain domain administrator privileges during internal pentests.

Here’s what typically happens when we run an internal penetration test on an environment that has not been protected from ADCS attacks:

  1. Get a domain account (for example, via Responder or mitm6)

  2. Find the AD CS Registration Web Service (for example, using a valid account and Certify/Certify; or in a “black box” using manual search or option –dump-adcs in ntlmrelayx.py)

  3. Force the domain controller to connect to our workstation (for example, using printerbug.py or PetitPotam).

  4. Pass this authentication to the AD CS registration web service using ntlmrelayx.py (attack ESC8described in section “Certified Pre-Owned”)to obtain a certificate for the target DC.

  5. By using PKINITtools obtain the TGT for the target DC (or recover its NT hash), which will allow you to take over the domain.

However, I recently encountered an Active Directory environment where the last step
(PKINIT) didn’t work.

Sometimes domain controllers do not support PKINIT. This may be because their certificates do not contain the smart card login EKU. However, some protocols, including LDAP, support Schannel, which allows authentication over TLS. We have created a Proof-of-Concept, PassTheCert, which allows you to authenticate to an LDAP/S server to perform various attack actions

No PKINIT?

PKINIT is a Kerberos mechanism that allows the use of X.509 certificates as a pre-authentication method. It can be used to request the TGT and even the NT hash of an account. Many articles have already been written on this topic, see:

Usually when PKI deployed in an Active Directory environment, PKINIT supported. However, during the evaluation process, I encountered the following error message when trying to use the domain controller certificate:

$ python3 ~/tools/PKINITtools/gettgtpkinit.py -cert-pfx AD2_auth.pfx 'contoso.com/AD2$' AD2.ccache
2022-04-07 12:49:00,854 minikerberos INFO     Loading certificate and key from file
2022-04-07 12:49:00,958 minikerberos INFO     Requesting TGT
Traceback (most recent call last):
  File "/home/yme/tools/PKINITtools/gettgtpkinit.py", line 349, in <module>
    main()
  File "/home/yme/tools/PKINITtools/gettgtpkinit.py", line 345, in main
    amain(args)
  File "/home/yme/tools/PKINITtools/gettgtpkinit.py", line 315, in amain
    res = sock.sendrecv(req)
  File "/home/yme/venv/PKINITtools/lib/python3.8/site-packages/minikerberos/network/clientsocket.py", line 87, in sendrecv
    raise KerberosError(krb_message)
minikerberos.protocol.errors.KerberosError:  Error Code: 16 Reason: KDC has no support for PADATA type (pre-authentication data)

This is what the error message looks like in Wireshark:

Back to basics

Having found no clear way to use the stolen certificate, I returned
To “Certified Pre-Owned“, deciding that there should be a section explaining how you can authenticate with a certificate without relying on Kerberos.

Interesting information is contained in the section “Active Directory Authentication with Certificates” (emphasis mine):

During our research, we also discovered that some protocols use Schannel, a security suite that supports SSL/TLS, to authenticate domain users. LDAPS is one of the commonly used options. For example, the following screenshot shows the PowerShell script Get-LdapCurrentUser authenticating to LDAPS using a certificate for authentication and running LDAP whoami to see which account is authenticated.

Indeed, you can use SSL/TLS to authenticate to a domain controller. Here is the relevant Microsoft documentation:

Active Directory allows two ways to establish a secure SSL/TLS connection to a DC. The first method is to connect to the DC via a secure LDAPS port (TCP ports 636 and 3269 in AD DS and a configuration-specific port in AD LDS). The second is connecting to the DC via a regular LDAP port (TCP ports 389 or 3268 in AD DS, as well as a port specific to the AD LDS configuration) and then sending an extended operation LDAP_SERVER_START_TLS_OID [RFC2830]. In both cases, the DC will request (but not require) a client certificate as part of the SSL/TLS handshake [RFC2246]. If the client presents the DC with a valid certificate at this time, then it can be used by the DC to authenticate (bind) the connection as the credentials represented by the certificate.

When you search for an error message, you can quickly find Microsoft documentation about this question:

KDC_ERR_PADATA_TYPE_NOSUPP

When you try to log in using a smart card, the required certificate cannot be found. This issue may occur because the wrong certificate authority (CA) is requested or the appropriate CA cannot be contacted to obtain Domain Controller or Domain Controller Authentication certificates for the domain controller.

This can also happen if the domain controller does not have a smart card certificate installed (Domain Controller or Domain Controller Authentication templates).

A certificate can have multiple extended keys (Extended Key Usages, EKU). If the KDC is to support smart card logins, its certificate must have EKU Smart Card Logon. Unsuccessful use PKINITmay indicate that the target KDCs do not have certificates with the required EKUs.

In this case, you will not be able to use your certificate to obtain the TGT or NT hash. So what can you do with your certificate?

Executing a command Get-LdapCurrentUser Lee Christensen gives the following result:

PS C:\> Get-LdapCurrentUser -Certificate Z:\AD2.pfx -Server AD1.contoso.com:636 -UseSSL u:CONTOSO\AD2$

Great, everything works! This means that we can authenticate to the LDAP service. Now we have the ability to use our malicious DC certificate. However, there appear to be no offensive tools that support authentication using TLS certificates.

So I created PassTheCert is a simple C# tool that can authenticate to an LDAP server using a client certificate and perform actions that are interesting to an attacker. Unlike most other attack tools, this tool has the added bonus of working in environments where LDAP Channel Binding is enabled, since Schannel authentication is, by design, not affected by Channel Binding.

Since we are connecting to LDAP, our methods for escalating privileges are limited. At the moment, only four attack vectors have been implemented:

  • Granting the user DCSync rights. This is useful if you have managed to obtain/generate a certificate for a privileged account, such as an Exchange server, that (still) has access WriteDacl to the Domain object.

  • Attribute modification msDS-AllowedToActOnBehalfOfOtherIdentity domain machine to perform an attack Resource Based Constrained Delegation (RBCD). The good thing about this attack is that the machine can update its own attribute.

  • Adding a computer to a domain, which is useful for performing RBCD attacks. This method is also good because by default, authenticated users can add machines to the domain, and it combines well with the attack described above.

  • Reset your account password. This requires the right User-Force-Change-Password over the target account.

The code can be found Hereas well as a Python version implemented @lowercase_drm. Note for those who like to google: this tool expands the concept Pass the Certificatewhich he named @_nwodtuhs in his Twitter thread dedicated to AD CS and PKINIT.

So let’s say you find yourself in a situation similar to mine: you have a domain controller certificate, but no PKINIT. You can start by adding a new computer to the domain:

PS C:\> .\PassTheCert.exe --server ad1.contoso.com --cert-path Z:\ad2.pfx --add-computer --computer-name DESKTOP-1337$
No password given, generating random one.
Generated password: Q2cpNOMhwlU2yZQBPAbJ1YY9M9XJIfBc
Success

Now that you have a managed computer defined by SPN etc, you can add its SID to the attribute msDS-AllowedToActOnBehalfOfOtherIdentity domain controller:

PS C:\> .\PassTheCert.exe --server ad1.contoso.com --cert-path Z:\ad2.pfx --rbcd --target "CN=AD2,OU=Domain Controllers,DC=contoso,DC=com" --sid "S-1-5-21-863927164-4106933278-53377030-3122"
msDS-AllowedToActOnBehalfOfOtherIdentity attribute is empty
You can clear it using arguments:
        --target "CN=AD2,OU=Domain Controllers,DC=contoso,DC=com" --restore clear
Success

Now that everything is ready, you can return to Impacket to perform an RBCD attack and impersonate a domain administrator on a DC:

$ getST.py -spn 'cifs/ad2.contoso.com' -impersonate Administrateur 'contoso.com/desktop-1337$:Q2cpNOMhwlU2yZQBPAbJ1YY9M9XJIfBc'
Impacket v0.9.25.dev1+20220218.140931.6042675a - Copyright 2021 SecureAuth Corporation

[*] Getting TGT for user
[*] Impersonating Administrateur
[*]         Requesting S4U2self
[*]         Requesting S4U2Proxy
[*] Saving ticket in Administrateur.ccache
$ export KRB5CCNAME=Administrateur.ccache
$ wmiexec.py -k -no-pass contoso.com/Administrateur@ad2.contoso.com
Impacket v0.9.25.dev1+20220218.140931.6042675a - Copyright 2021 SecureAuth Corporation

[*] SMBv3.0 dialect used
[!] Launching semi-interactive shell - Careful what you execute
[!] Press help for extra shell commands
C:\>whoami
contoso\administrateur

Here’s what an attack looks like using the Python version of the tool:

Schannel authentication generates two events with IDs 4648 and 4624, just like any other authentication mechanism. It can be seen that the login process is Schannel. Authentication is set to Kerberos, which seems to confirm the white paper’s reference to “Certified Pre-Owned” (page 32):

Schannel first attempts to map the certificate to the user account using the S4U2Self function of the Kerberos protocol. If this fails, an attempt is made to match the certificate to the user account using the certificate’s SAN extension, a combination of the subject and issuer fields, or issuer only.[.]

Future work

PassTheCert is open source, so feel free to extend it. At the moment it is a very simple tool, functioning mainly as a Proof-of-Concept. It only supports LDAP/S protocols, and only implements the bare minimum required. As recalled in white paper for “Certified Pre-Owned” (again, page 32), other protocols support Schannel authentication, but don’t seem to do it out of the box. However, it would be great to be able to use a certificate obtained through ESC8for authentication via RDP.

We are also considering the possibility of implementing certificate authentication in pywerviewwhich would allow more actions to be performed on the LDAP server (for example, reading gMSA passwords).

However, implementing certificate authentication in Impacket, while not straightforward, would be a great addition to our security toolkit.

Finally, if you read the Microsoft documentation excerpt on SSL/TLS DC connections, you may have noticed that TLS authentication can be done in two different ways:

  1. Directly on the LDAPS port (TCP/636)

  2. On the LDAP port (TCP/389) via StartTLS.

PassTheCert supports both methods, allowing it to be used on TCP/389 if TCP/636 is closed.

This became the starting point for @lowercase_drm’s research on bypass LDAP channel binding via StartTLSonce again showing that digging into a subset of problems can lead to unexpected findings.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *