Overview
Timelapse is an Easy Windows machine that acts as a Domain Controller. A guest session on SMB lets us list every domain user and download a share that holds a password protected backup archive. Cracking the archive, and then the PFX certificate inside it, gives us a certificate that we can use to log in over WinRM as a low privileged user. The PowerShell history of that user leaks the password of a service account, and that account is a member of the LAPS_Readers group. Reading the LAPS attribute of the Domain Controller gives us the local Administrator password, and with it full control of the domain.
Enumeration
We started our enumeration with an Nmap scan to identify the open ports and services on the target machine.
Nmap Scan
We used the following command, which runs the default scripts (-sC), detects service versions (-sV) and skips host discovery (-Pn).
Timelapse ➜ nmap -sC -sV -Pn 10.129.91.116

Nmap Scan
The scan reveals the following open ports and services:
| Port | Service | Description |
|---|---|---|
| 53 | DNS | Simple DNS Plus, resolves names for the domain |
| 88 | Kerberos | Microsoft Windows Kerberos, used for domain authentication |
| 135 | MSRPC | Microsoft Windows RPC endpoint mapper |
| 139 | NetBIOS-SSN | Microsoft Windows NetBIOS session service |
| 389 | LDAP | Active Directory LDAP (Domain: timelapse.htb) |
| 445 | SMB | Microsoft-DS, SMB file sharing |
| 464 | kpasswd5 | Kerberos password change service |
| 593 | RPC over HTTP | Microsoft Windows RPC over HTTP 1.0 |
| 636 | LDAPS | LDAP over SSL (tcpwrapped) |
| 3268 | LDAP (GC) | Active Directory Global Catalog LDAP |
| 3269 | LDAPS (GC) | Global Catalog LDAP over SSL (tcpwrapped) |
| 5986 | WinRM (HTTPS) | Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP), WinRM over SSL. This is the port we use later with the certificate |
The combination of DNS, Kerberos, LDAP and SMB confirms that this machine is a Windows Active Directory Domain Controller (DC01, domain timelapse.htb).
A few details in the scan output are worth noting:
- WinRM only over SSL: port 5985 is not open. Only 5986 is, and its certificate has the common name
dc01.timelapse.htb. This already tells us that any WinRM access has to go through HTTPS. - Expired certificate: the certificate on port 5986 was only valid from 2021-10-25 to 2022-10-25. Evil-WinRM does not validate it, so this does not block us.
- Clock skew: the scan reports a clock skew of roughly +8 hours between the target and our machine. This does not matter for the SMB and WinRM steps in this writeup, but Kerberos based attacks would fail until the time is synchronised. (
sudo ntpdate 10.129.91.116)
User Enumeration
The SMB service accepts authentication as the guest user with an empty password. This misconfiguration allows us to perform a RID brute force.
Every security object in a domain (users, groups, computers) has a RID (Relative Identifier), which is the last part of its SID. By asking the server to resolve RID after RID, we can build a complete list of the domain accounts, even without valid credentials.
Timelapse ➜ nxc smb timelapse.htb -u 'guest' -p '' --rid-brute

Performed RID Brute
We saved the discovered usernames in users.list, because we need this list again later for password spraying.
Share Enumeration
With the guest session still available, we continued by listing the SMB shares and our permissions on each of them.
Timelapse ➜ nxc smb timelapse.htb -u 'guest' -p '' --shares

Performed Share Enumeration
The non-default Shares share is readable by guest, so this is where we look next.
Retrieve Files on the Share
We used smbclient to recursively download everything that is present on the Shares share. Turning prompt off prevents mget from asking for confirmation on every single file.
Timelapse ➜ smbclient //timelapse.htb/Shares -U "Guest"
smb: \> recurse on
smb: \> prompt off
smb: \> mget *

Retrieved Files
The share has two folders. HelpDesk contains installers and documentation for LAPS (Local Administrator Password Solution), which is a hint we will need in the post-exploitation phase. Dev contains winrm_backup.zip, which is the interesting file for now.
Cracking the Zip Password
winrm_backup.zip is password protected, so we cannot extract the .pfx file inside it yet.
We used zip2john to convert the archive into a hash format that John the Ripper understands, and then cracked it with the rockyou wordlist.
Timelapse ➜ zip2john winrm_backup.zip > hash_winrm_zipfile
Timelapse ➜ john hash_winrm_zipfile --wordlist=/opt/Tools/rockyou.txt

Cracked Zip Password
Unzipping
With the cracked password we can extract the archive, which gives us legacyy_dev_auth.pfx.
Timelapse ➜ unzip winrm_backup.zip

Unzipped Successfully
Cracking the PFX Password
A .pfx file is a password protected container that bundles the certificate chain and the private key together. If we crack this password, we can use the certificate to authenticate to the machine.
We followed the same approach as with the zip file: convert with pfx2john, then crack with John.
Timelapse ➜ pfx2john Dev/legacyy_dev_auth.pfx > hash_pfxfile

Cracked Password
Exploitation
We now have a client certificate for the user legacyy, together with the password that protects it. The next step is to use it to log in to the target.
Connecting to Target
Extract Private Key
Evil-WinRM needs the private key and the certificate as separate PEM files. We used openssl to extract the private key first. The -nodes flag stores the key without encrypting it again, and openssl asks for the import password we just cracked.
Timelapse ➜ openssl pkcs12 -in legacyy_dev_auth.pfx -nocerts -out key.pem -nodes
Extract the Certificate
Then we extracted the public certificate from the same .pfx file.
Timelapse ➜ openssl pkcs12 -in legacyy_dev_auth.pfx -clcerts -nokeys -out cert.pem
Connect via WinRM
During our enumeration we identified that port 5986 (WinRM over HTTPS) is open. Certificate authentication only works over SSL, so we connect with evil-winrm using -S (SSL), -c (certificate) and -k (private key).
Timelapse ➜ evil-winrm -i 10.129.91.116 -S -c cert.pem -k key.pem

Successfully Connected
Post-Enumeration
We now have a foothold as legacyy, a low-privileged user. From here we looked for a way to reach administrative privileges.
PowerShell History File
PowerShell’s PSReadLine module saves every command typed in a console session to a history file. We read it with the following command.
*Evil-WinRM* PS C:\temp> Get-Content $env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt

Show PowerShell History
The history contains a command with a plain text password: E3R$Q62^12p7PLlC%KWaxuaV
Perform Password Spraying
To find out which account the password belongs to, we sprayed it against the user list we built from the RID brute force earlier. Because we try one password against many users, this is a password spray and not a brute force, which keeps the risk of account lockouts low.
Timelapse ➜ nxc smb timelapse.htb -u users.list -p 'E3R$Q62^12p7PLlC%KWaxuaV'

Successful Authentication
The password is valid for the following account: svc_deploy:E3R$Q62^12p7PLlC%KWaxuaV
BloodHound Enumeration
Next we collected Active Directory data with SharpHound.exe, which we ran from the machine as legacyy. The collected data can be imported into BloodHound to visualise relationships and attack paths.
*Evil-WinRM* PS C:\temp> .\SharpHound --CollectionMethods All

SharpHound Enumeration

SVC_Deploy ReadLaps
BloodHound shows that svc_deploy is a member of the LAPS_Readers group, which is allowed to read the LAPS password of the DC01 computer account. This also explains the LAPS files we saw on the HelpDesk share.
Post-Exploitation
LAPS stores the randomised local Administrator password of a computer in the ms-Mcs-AdmPwd attribute of its computer object in Active Directory. Since svc_deploy can read that attribute for DC01, we can retrieve the local Administrator password of the Domain Controller and take over the entire domain.
Read LAPS
We used bloodyAD to query LDAP for every object that has a LAPS expiration time set, and to return the password together with its expiration time.
Timelapse ➜ bloodyAD --host 10.129.91.116 -d timelapse.htb -u svc_deploy -p 'E3R$Q62^12p7PLlC%KWaxuaV' get search --filter '(ms-mcs-admpwdexpirationtime=*)' --attr ms-mcs-admpwd,ms-mcs-admpwdexpirationtime
| Parameter | Explanation |
|---|---|
--host 10.129.91.116 |
The Domain Controller we send the LDAP query to |
-d timelapse.htb |
The domain we authenticate against |
-u svc_deploy -p '...' |
The credentials we found in the PowerShell history |
get search |
Performs an LDAP search |
--filter '(ms-mcs-admpwdexpirationtime=*)' |
Only returns objects that have a LAPS expiration time, which means LAPS is managing them |
--attr ms-mcs-admpwd,ms-mcs-admpwdexpirationtime |
The attributes we want to see: the plain text password and its expiration time |

Read Password
Verify Password
We verified the LAPS password with NetExec. Because we pass the whole user list, NetExec also shows which account the password belongs to, which is the local Administrator.
Timelapse ➜ nxc smb timelapse.htb -u users.list -p "69C]wR]7S()dh)+0ipFN4aw2"

Password Verified
Gaining Access
Finally we used wmiexec.py from Impacket to log in to the Domain Controller as administrator.
Timelapse ➜ wmiexec.py timelapse.htb/administrator:"69C]wR]7S()dh)+0ipFN4aw2"@10.129.91.116

Successfully Logged In

