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:

  • periodically changes the local admin account password
  • stores the password in a extended attribute of the computer object in AD
  • allows password read & reset permissions to be delegated to AD users/groups

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.

AdmPwd.dll

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

GPOs

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:

  • Password complexity is set to 4 - which means Upper, lower, specials and digits
  • Password length is set to 8
  • Password expires every 30 days
  • The policy is applying to a local account called localbossman, rather than the built-in administrator account

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

displayname
-----------
LAPS
Server Admins
Defender
Firewalls
Default Domain Policy

But, to find all computers we have to:

  • Find the LAPS GPO (which we’ve done) and grab its GUID name (not its actual object GUID)
  • Find which OUs that GPO is being applied to
  • Get a list of all computers in those OUs

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

distinguishedname
-----------------
OU=Workstations,DC=testlab,DC=local
OU=Servers,DC=testlab,DC=local

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

distinguishedname
-----------------
CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
CN=WKSTN04,OU=Workstations,DC=testlab,DC=local

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

distinguishedname
-----------------
CN=FS01,OU=Servers,DC=testlab,DC=local

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.

AdmPwd.PS

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                          5.0.0.0    AdmPwd.PS
Cmdlet          Get-AdmPwdPassword                                 5.0.0.0    AdmPwd.PS
Cmdlet          Reset-AdmPwdPassword                               5.0.0.0    AdmPwd.PS
Cmdlet          Set-AdmPwdAuditing                                 5.0.0.0    AdmPwd.PS
Cmdlet          Set-AdmPwdComputerSelfPermission                   5.0.0.0    AdmPwd.PS
Cmdlet          Set-AdmPwdReadPasswordPermission                   5.0.0.0    AdmPwd.PS
Cmdlet          Set-AdmPwdResetPasswordPermission                  5.0.0.0    AdmPwd.PS
Cmdlet          Update-AdmPwdADSchema                              5.0.0.0    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.

PowerView

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

  • Get a list of all Object ACLs for the OUs of interest (and resolve GUIDs to their display names)
  • Filter on ActiveDirectoryRights for ReadProperty
  • Filter on ObjectAceType for ms-Mcs-AdmPwd
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.