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.
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
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:
4
- which means Upper, lower, specials and digits8
30 days
localbossman
, rather than the built-in administrator accountNext, 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:
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
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 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.
If you don’t have access to those cmdlets, we can use PowerView instead. We must:
ActiveDirectoryRights
for ReadProperty
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
In Part 2, we’ll explore abusing LAPS for privilege escalation and persistence.