LAPS - Part 1

I suspect the majority of folk are familiar with the “Local Administrator Password Solution” (LAPS) from Microsoft. If not, the tl;dr is that it:

More detailed information can be found here, here and here.

The purpose of this post, is to put together a more complete end-to-end process for mapping out the LAPS configuration in a domain.

Identifying LAPS

After landing a foothold within a target network, there are a couple of easy ways to work out if LAPS is present.


Any host that has LAPS installed is going to have AdmPwd.dll present on disk - the default location for which is C:\Program Files\LAPS\CSE\.


Since LAPS configurations are most often defined in GPOs, this is the best source of information. We can search for any GPO that has the word LAPS in its display name.

PS H:\> Get-DomainGPO -Identity "*LAPS*"

usncreated               : 13354
displayname              : LAPS
gpcmachineextensionnames : [{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}][{C6DC5466-785A-11D2-84D0-00C04FB169F7}{942A8E4F-A261-11D1-A760-00C04FB9603F}][{D76B9641-3288-4F75-942D-087DE603E3EA}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]
whenchanged              : 10/03/2018 14:09:30
objectclass              : {top, container, groupPolicyContainer}
gpcfunctionalityversion  : 2
showinadvancedviewonly   : True
usnchanged               : 13484
dscorepropagationdata    : 01/01/1601 00:00:00
name                     : {C3801BA8-56D9-4F54-B2BD-FE3BF1A71BAA}
flags                    : 0
cn                       : {C3801BA8-56D9-4F54-B2BD-FE3BF1A71BAA}
gpcfilesyspath           : \\testlab.local\SysVol\testlab.local\Policies\{C3801BA8-56D9-4F54-B2BD-FE3BF1A71BAA}
distinguishedname        : CN={C3801BA8-56D9-4F54-B2BD-FE3BF1A71BAA},CN=Policies,CN=System,DC=testlab,DC=local
whencreated              : 10/03/2018 12:49:43
versionnumber            : 9
instancetype             : 4
objectguid               : 8b911989-7207-4e1d-8447-b3e55a538d8f
objectcategory           : CN=Group-Policy-Container,CN=Schema,CN=Configuration,DC=testlab,DC=local

LAPS Configuration

The specific configuration for the LAPS policy can be found in Registry.pol under the gpcfilesyspath. The Parse-PolFile cmdlet from the GPRegistryPolicy repo can be used to decode the data.

Props to @grouppolicyguy for the tip on that one.

PS H:\> Parse-PolFile "\\testlab.local\SysVol\testlab.local\Policies\{C3801BA8-56D9-4F54-B2BD-FE3BF1A71BAA}\Machine\Registry.pol"

KeyName     : Software\Policies\Microsoft Services\AdmPwd
ValueName   : PasswordComplexity
ValueType   : REG_DWORD
ValueLength : 4
ValueData   : 4

KeyName     : Software\Policies\Microsoft Services\AdmPwd
ValueName   : PasswordLength
ValueType   : REG_DWORD
ValueLength : 4
ValueData   : 8

KeyName     : Software\Policies\Microsoft Services\AdmPwd
ValueName   : PasswordAgeDays
ValueType   : REG_DWORD
ValueLength : 4
ValueData   : 30

KeyName     : Software\Policies\Microsoft Services\AdmPwd
ValueName   : AdminAccountName
ValueType   : REG_SZ
ValueLength : 26
ValueData   : localbossman

KeyName     : Software\Policies\Microsoft Services\AdmPwd
ValueName   : AdmPwdEnabled
ValueType   : REG_DWORD
ValueLength : 4
ValueData   : 1

From this, we can see:

OUs and Computers

Next, we want to know which computers this GPO is applied to.

It’s straight forward to find which GPOs are applied to a specific computer:

PS H:\> Get-DomainGPO -ComputerIdentity fs01 -Properties displayname

Server Admins
Default Domain Policy

But, to find all computers we have to:

The Get-DomainOU cmdlet has a -GPLink filter, which only returns OUs with the specified GUID in their gplink property. Exactly what we want.

PS H:\> Get-DomainOU -GPLink "C3801BA8-56D9-4F54-B2BD-FE3BF1A71BAA" -Properties distinguishedname


Now we can use Get-DomainComputer with the SearchBase parameter to get a computer listing for those OUs.

PS H:\> Get-DomainComputer -SearchBase "LDAP://OU=Workstations,DC=testlab,DC=local" -Properties distinguishedname


PS H:\> Get-DomainComputer -SearchBase "LDAP://OU=Servers,DC=testlab,DC=local" -Properties distinguishedname


Delegated Permissions

Now that we know that LAPS is in the environment, its specific configuration and which computers the policy is applied to, we probably want to know who has delegated rights to the extended attribute for the passwords.


If you happen to land on a computer that has the LAPS PowerShell module installed, this couldn’t be easier. To find out, check for the presence of C:\Windows\System32\WindowsPowerShell\v1.0\Modules\AdmPwd.PS or use Get-Command.

PS H:\> Get-Command *AdmPwd*

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Find-AdmPwdExtendedRights                    AdmPwd.PS
Cmdlet          Get-AdmPwdPassword                           AdmPwd.PS
Cmdlet          Reset-AdmPwdPassword                         AdmPwd.PS
Cmdlet          Set-AdmPwdAuditing                           AdmPwd.PS
Cmdlet          Set-AdmPwdComputerSelfPermission             AdmPwd.PS
Cmdlet          Set-AdmPwdReadPasswordPermission             AdmPwd.PS
Cmdlet          Set-AdmPwdResetPasswordPermission            AdmPwd.PS
Cmdlet          Update-AdmPwdADSchema                        AdmPwd.PS
PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" | fl

ObjectDN             : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\Workstation Admins}

PS H:\> Find-AdmPwdExtendedRights -Identity "Servers" | fl

ObjectDN             : OU=Servers,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\Server Admins}

This shows us that LAB\Workstation Admins and LAB\Server Admins have extended rights over the computers in the Workstations and Servers OU respectively.


If you don’t have access to those cmdlets, we can use PowerView instead. We must:

PS H:\> Get-DomainObjectAcl -SearchBase "LDAP://OU=Workstations,DC=testlab,DC=local" -ResolveGUIDs | Where-Object { $_.ObjectAceType -eq "ms-Mcs-AdmPwd" -and $_.A
ctiveDirectoryRights -like "*ReadProperty*" } | Select-Object ObjectDN, SecurityIdentifier

ObjectDN                                       SecurityIdentifier
--------                                       ------------------
OU=Workstations,DC=testlab,DC=local            S-1-5-21-2656122261-1395146812-1023204418-1109
CN=WKSTN02,OU=Workstations,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1109
CN=WKSTN01,OU=Workstations,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1109
CN=WKSTN03,OU=Workstations,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1109
CN=WKSTN04,OU=Workstations,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1109

PS H:\> Get-DomainObjectAcl -SearchBase "LDAP://OU=Servers,DC=testlab,DC=local" -ResolveGUIDs | Where-Object { $_.ObjectAceType -eq "ms-Mcs-AdmPwd" -and $_.Activ
DirectoryRights -like "*ReadProperty*" } | Select-Object ObjectDN, SecurityIdentifier

ObjectDN                               SecurityIdentifier
--------                               ------------------
OU=Servers,DC=testlab,DC=local         S-1-5-21-2656122261-1395146812-1023204418-1111
CN=FS01,OU=Servers,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1111

We can convert these SIDs manually:

PS H:\> Convert-SidToName S-1-5-21-2656122261-1395146812-1023204418-1109
LAB\Workstation Admins

PS H:\> Convert-SidToName S-1-5-21-2656122261-1395146812-1023204418-1111
LAB\Server Admins

Or if there are too many different ones, we can do some magic to dynamically resolve these and add them to the table.

PS H:\> Get-DomainObjectAcl -SearchBase "LDAP://OU=Workstations,DC=testlab,DC=local" -ResolveGUIDs | Where-Object { $_.ObjectAceType -eq "ms-Mcs-AdmPwd" -and $_.A
ctiveDirectoryRights -like "*ReadProperty*" } | Select-Object ObjectDN, SecurityIdentifier | % { $_ | Add-Member NoteProperty 'ResolvedName' $(Convert-SidToName $
_.SecurityIdentifier); $_ }

ObjectDN                                       SecurityIdentifier                             ResolvedName
--------                                       ------------------                             ------------
OU=Workstations,DC=testlab,DC=local            S-1-5-21-2656122261-1395146812-1023204418-1109 LAB\Workstation Admins
CN=WKSTN02,OU=Workstations,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1109 LAB\Workstation Admins
CN=WKSTN01,OU=Workstations,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1109 LAB\Workstation Admins
CN=WKSTN03,OU=Workstations,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1109 LAB\Workstation Admins
CN=WKSTN04,OU=Workstations,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1109 LAB\Workstation Admins

PS H:\> Get-DomainObjectAcl -SearchBase "LDAP://OU=Servers,DC=testlab,DC=local" -ResolveGUIDs | Where-Object { $_.ObjectAceType -eq "ms-Mcs-AdmPwd" -and $_.Active
DirectoryRights -like "*ReadProperty*" } | Select-Object ObjectDN, SecurityIdentifier | % { $_ | Add-Member NoteProperty 'ResolvedName' $(Convert-SidToName $_.Sec
urityIdentifier); $_ }

ObjectDN                               SecurityIdentifier                             ResolvedName
--------                               ------------------                             ------------
OU=Servers,DC=testlab,DC=local         S-1-5-21-2656122261-1395146812-1023204418-1111 LAB\Server Admins
CN=FS01,OU=Servers,DC=testlab,DC=local S-1-5-21-2656122261-1395146812-1023204418-1111 LAB\Server Admins

To be Continued…

In Part 2, we’ll explore abusing LAPS for privilege escalation and persistence.