TrustedSec - A LAPS(e) in Judgement
As security practitioners, we live in a time where there is an abundance of tools and solutions to help us secure our homes, organizations, and critical data. We know the dangers of unpatched applications and devices as well as the virtues of things like password managers and encrypted databases to protect our passwords and other sensitive information.
However, we often forget that these tools, while helpful and no doubt beneficial, are subject to the same attacks as other applications. As practitioners, we should ensure we are building out both engineering protections and security detections that protect those tools in the same way we do for the rest of our environment.
1.1 Overview
This blog will focus on one such tool—Microsoft LAPS—and how we can build Splunk SPL queries for detecting the attacks described using Windows Security Log events from a domain controller.
1.2 What is LAPS?
Microsoft LAPS (Local Administrator Password Solution) is a Windows native tool used to manage local Administrator accounts within an AD domain.
To keep things simple, instead of requiring an administrator to manually set, rotate, and store the local Administrator passwords, LAPS will do this automatically while providing an easy interface for authorized users to access passwords for recovery and/or other admin-related tasks.
The benefits to an organization here are obvious—automate and protect a well-known critical security challenge and free up IT resources on your team.
However, the fact that LAPS has so much information means that it provides a potential avenue of attack for malicious actors looking to further compromise an environment. If LAPS isn’t properly locked down, an organization can inadvertently allow anybody to grab local Admin powers on a given machine. That alone makes logging the telemetry worthwhile.
1.3 Building your LAPS Lab
One of the major advantages of LAPS is that it is quick and easy (and free!) to install and can automatically be deployed to workstations and servers on a domain through GPO.
This guide does a great job at walking you through the install process and getting some initial configuration done.
1.4 Enhancing Logging
There are a couple of things that must be done to generate the logs you need for the detections we will build.
First, ensure Event ID 4662 is logging ‘Success’ and ‘Fail’:
Group Policy Editor > Policies > Windows Settings > Security Settings > Advanced Audit Policy Configuration > Audit Policies > DS Access
The second thing we need to do is enable logging for the objects that we will look for and use in our LAPS detection. There are a few ways to do this, but if you are running a lab environment, I would recommend enabling auditing for ALL objects:
If you are in a production environment where adding logging is a challenge due to EPS or licensing limitations, then you can also enable logging for just the ms-Mcs-AdmPwd attribute:
1.5 Running our First Attacks Against LAPS
The two tools we will be using to enumerate local Admin passwords are CrackMapExec and LAPSDumper. For now though, we will be focusing on LAPSDumper. So, if you haven’t yet, now’s a good time to install it:
git clone https://github.com/n00py/LAPSDumper.git
The command syntax is very simple:
./laps.py -u <username of account with delegated rights> -p <password username of account with delegated rights> -l <your domain ldap server> -d <your domain>
In this first walkthrough, I have specified a username that I know has delegated rights to generate some preliminary logging data. We’ll complicate this more as we move forward, but we want to start with a baseline for what we know works.
Now, if we’ve configured our logging properly, we should have the logs we need to start forming the base template of our query for testing. As we move forward, we’ll run attacks with different tools to ensure our detection is not catered to one specific toolset, as well as try to make our detection as evasion-proof as possible.
1.6 Detecting a LAPS Attack
Let’s recap a little bit. So far we’ve installed LAPS to our workstations, enabled/enhanced existing logging to our SIEM of choice, and verified that we are getting (at least for now) a minimum of Microsoft Windows Event ID 4662 ingested. We’ve also run our first attacks with LAPSDumper.
You might think it’s time to hit that SIEM for those sweet logs.
And you’d be wrong.
Here’s the thing. Like most Microsoft native toolsets, it can sometimes be a challenge to interpret the data that’s getting thrown at us. And while Event ID 4662 is a useful log for analysts, it requires some additional work to understand what exactly it is that we’re looking at.
You can take a peek at your own logs if you wish, or you can just look at my screengrab:
Now, if you’re new to labbing, or a new cyber practitioner, this isn’t quite as complicated as it looks.
To keep things simple, LAPS stores the local Administrator password it sets on designated machines in two known AD OBJECTS called:
ms-Mcs-AdmPwd This is just the Admin password.
ms-Mcs-AdmPwdExpirationTime And this is the expiration time for the above value.
The properties contained in Event ID 4662, among other event IDs, are just numerical representations of AD OBJECTS.
In this case, the properties within the log are what we will focus on to identify which GUID translates to the ms-Mcs-AdmPwd object. Because we cannot recognize what the readable name of the object is from the string presented, we will need to translate them ourselves.
As a quick note, I realize that like many SIEMs, Splunk includes the ability to translate AD OBJECTS by AD/LDAP integrations. I am intentionally running through manual translations for those who use SIEMs or other log parsing tools without this ability, and for those who want to better understand the structure of the logs we will be focusing on. If you are using an integration to translate your objects, you can easily replace the object GUID with the readable name of the object, and that will work just as well.
As for translating GUIDs, Microsoft has some handy resources on how to do this.
That said…because this is a bit of a pain, I’m also going to walk through it.
1.7 Translating GUIDs
1.7.1 Grab that GUID
First, pull up one of your domain controllers and log in as your Domain Admin/Administrator account.
In the start menu, type in LDP.exe:
Hit Connection and then Connect — accept default settings. If there aren’t default settings, your server should be the IP address of your domain, and the port should be 389:
Next, hit Connection again and then Bind. If you’re logged in as a local Admin or Domain Admin, you should be able to accept the default ‘as logged in user’ option that appears in the pop-up window:
Because we just ran the attack with LAPSDumper – and unless you’re in a high-volume logging environment with lots of event 4662’s- we should only have to decode one or two GUIDs to find which will translate into our ms-Mcs-AdmPwd object.
Quick note: If you see the following GUID {bf967a86-0de6-11d0-a285-00aa003049e2}, you can disregard it. That’s a standard property for a computer object.
Next, click through the drop-down menu to search:
In the next pop-up window, input the following:
BASE DN: DN: CN=Schema,CN=Configuration,DC=,<yourdomainhere>,DC=local Filter: Filter: (&(objectClass=*)(schemaIDGUID=<this will be whatever your GUID is>)) Scope: Subtree Attributes: schemaIDGUID
Before we can search for our GUID, we need to perform the manual modifications below.
Take your GUID:
{cbf43478-21e0-40e7-9e55-12d529cf4be6}
Now take the first three groups (designated by the dashes):
cbf43478-21e0-40e7
Grab the group of numbers before the first ‘-‘:
cbf43478
Add spaces every two characters:
cb f4 34 78
Reverse the sequence of the numbers:
78 34 f4 cb
Remove the spaces:
7834f4cb
Now repeat those steps for the next two groups, and you should wind up with something like this:
7834f4cb e021 e740
The final two groups do not need modification and can simply be dropped in like so:
7834f4cb e021 e740 9e55 12d529cf4be6
Remove all spaces:
7834f4cbe021e7409e5512d529cf4be6
Add backslashes:
\78\34\f4\cb\e0\21\e7\40\9e\55\12\d5\29\cf\4b\e6
And now you can drop this into your search! At least one should turn up as our coveted object:
Now that we have that information, we can hit the SIEM.
*As a side note, you are also able to view the schemaIDGUID through ASDI edit by connecting to Schema and finding the ms-Mcs-AdmPwd object in the properties list. However, knowing how to translate the properties within Event ID 4662 still has value:
1.8 Building the Query
There’s a reason we spent a lot of time translating the GUIDs in our 4662 events to find our ms-Mcs-AdmPwd object, and that’s primarily because it is going to be the key value we’re going to need to detect a read of the object. For the purposes of this blog, we won’t be dealing with its sibling attribute ms-Mcs-AdmPwdExpirationTime.
My lab is currently set up with Splunk, so the remainder of this section will focus on building the query out within the Splunk query language, or SPL for short.
1.8.1 Building a Simple LAPS Query
A quick summary for those who aren’t familiar with SPL, this is just where the logs we are looking for are stored:
index=main
And this is the event ID we’re searching for:
EventCode=4662
The final value in the above screenshot should be familiar. It’s our UNTRANSLATED GUID. Make sure you don’t accidently drop in the GUID that you translated to run our LDP.exe search.
Next, we build in the intelligence by adding in some filtering parameters to filter by access masks 0x100 – Extended Rights Attribute and/or 0x10 ‘Read Object’. I also built in a table to make viewing our queries’ output a little bit easier to read.
In this case, the object name listed in the table is not our ms-Mcs-AdmPwd but instead the GUIDs for the names of the computers that were queried.
1.8.2 LAPS Query With Thresholding
Now that we have a solid base query, we need to make this query tell us more than just the LAPS ms-Mcs-AdmPwd object was read by a user.
Currently, LAPSDumper is requesting all LAPS passwords, which means we are generating multiple events per one attack run, and that, in addition to all the events generated by 4662 events in general, makes for 23 events to sort through and aggregate.
In other words, there’s still a lot of data and only one of us. We need to make this detection work for us by filtering out extraneous data.
The easiest way to do this is to build in thresholding. Thankfully, Splunk does this beautifully:
Let’s break down the lines of queries independently.
This portion of the query is our filter, which simply makes sure we are only viewing the events we want to see per the parsed properties that our Windows events have generated within Splunk:
index=main EventCode=4662 Access_Mask="0x100" OR "0x10" "cbf43478-21e0-40e7-9e55-12d529cf4be6"
This line makes Splunk aggregate the count of events by Account Name, giving us the nice table view with just the usernames that have requested the object we’ve been hunting and the count of events per username:
|stats count by Account_Name
Rename the ‘count’ field created by stats to Event_count. This stores the count data in another named field so we can still build a table to view all the columns we need in one view.
If you don’t rename the variable, table values other than ‘count’ or Account_Name will not populate:
|eval Event_count=count
This line isn’t so important if you’re running this query in a lab. Obviously, 23 events in total is nothing compared to the number of events typically generated by this event ID.
|search Event_count > 1
However, in a real-life production environment, ‘count’ becomes our key. This line allows us to create a thresholding filter for what we will be viewing, thus tuning out activity from users or service accounts with duties that require them to view LAPS’ passwords.
Lastly, the table simply makes everything nice and easy to read!
|table Account_Name, Logon_ID, Event_count
This query has two main advantages:
- You can gain a baseline of WHO is accessing LAPS passwords over time and thus gain a better understanding of your organization’s environment.
- The threshold allows you to tailor this to YOUR organization. Perhaps you have some IT administrator accounts that generate 25 events. By setting your threshold number at 25, you’ll no longer be inundated with events that aren’t worth the analyst’s time.
1.8.3 Building Complexity
We have a good query, but there’s some critical data that we’re missing, primarily an idea of where our attacks are coming from. That’s not a failing of our query. That telemetry is just not included within Event ID 4662.
What we need to do here is pull data from another common event ID that tracks logins, 4624, and join that into our existing query based on a common data point to identify where our ‘LAPS attacks’ are coming from.
In this case, the data point we want to grab from our 4662 events is the Logon ID:
Now, let’s build a new search for Event ID 4624 and throw some important fields into a table for quick searching:
You can maybe see the value here. If we pull our Logon ID from our 4662 event and add that into our filter, we can see where exactly our data is originating from:
Great! But how do we join this together? With a lot of tinkering and some fancy query language, I arrived at this:
index=main ((EventCode=4662 AND"cbf43478-21e0-40e7-9e55-12d529cf4be6" AND (Access_Mask="0x100" OR Access_Mask="0x10")) OR (EventCode=4624 AND Account_Name!="*$" AND Account_Name!="ANONYMOUS LOGON" AND Account_Name!="SYSTEM")) | eval Logon_ID=if(EventCode==4624,mvindex(Logon_ID,-1), mvindex(Logon_ID,-1)) | eval user=if(EventCode==4624,mvindex( Account_Name,-1), mvindex( Account_Name,-1)) | join type=outer Logon_ID [ search EventCode=4662 AND"cbf43478-21e0-40e7-9e55-12d529cf4be6" AND (Access_Mask="0x100" OR Access_Mask="0x10") | stats count by Account_Name, Logon_ID | eval Event_count=count | search Event_count > 0 |table Account_Name,Logon_ID,Event_count] | table user, Logon_ID Source_Network_Address,Event_count |stats values by user, Logon_ID, Source_Network_Address,Event_count |table user, Logon_ID, Source_Network_Address, Event_count
This is relatively complicated syntax and might not be portable into other SIEMs. However, the queries we designed earlier will still work great and can simply be run separately.
1.9 Attacking with CrackMapExec
With a high-fidelity (and complex) query built, it’s a good idea to test our detection with a different tool. Enter CrackMapExec…
/etc/poetry/venv/bin/poetry run ./crackmapexec.py ldap <domain controller> -u 'username' -p '<password>' -d <your domain>-M laps
The first run is with a domain user account that cannot access LAPS passwords. The second run is with an account that can read the LAPS passwords. As you can see below, our query is picking up the data generated by CrackMapExec, just as it did with LAPSDumper.
However, one thing to note is that the CrackMapExec data generates more individual login data with one run than LAPSDumper does, and you may want to factor that in when creating your thresholds. This is precisely why it’s a good idea to run multiple tools and ensure your detections are tool agnostic.
1.9.1 Other Methods of Telemetry
Another method of telemetry that we can use to detect attempts to query LAPS lies within domain LDAP logging.
1.9.2 Enabling LDAP Logging
To enable LDAP logging, first open regedit and navigate to:
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics
Edit the following keys:
Next, navigate to:
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters
And set the Search Time Threshold (msecs) key to 1:
Leveraging Event ID 1644 from the Directory Services log, we can now see our LAPS Attack logs in Splunk:
You can re-add thresholding to make the query actionable for a production network:
This query is great and has the added benefit of not only showing the LDAP query used by our attack tools to pull the ms-Mcs-AdmPwd object but also the source IP address of the attack without having to join two event IDs.
That said, LDAP logging, in my experience, is not implemented within most organizations. When it is, it’s often not configured to audit the level of detail that we need to make this query functional and valuable.
1.9.3 Other Avenues of Detection
This blog only examines a subset of the Windows Event logging data source, and not all of the possible telemetry data points within this data set were analyzed. From my experimentation, I was able to determine that Event ID 4662, in conjunction with Event ID 4624, is likely to be both the easiest and most accessible method of detecting potential LAPS abuse. LDAP-based detections are another great option for organizations with high-capacity logging capabilities. In addition to the aforementioned log sources, both local system logs and RPC data were also examined. During the timeframe of the research and labbing exercise, I was unable to identify any tangible detection potential. However, Microsoft has indicated that there may be some expanded LAPS logging on local machines with the Windows 11 Insider Preview Build 25145 and later operating system versions: https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-management-event-log
1.10 Conclusion
LAPS is a great tool to control and mitigate issues with local Admin password reuse. However, like most security enhancing tools, it can be abused by attackers performing AD reconnaissance or an internal employee with excessive privileges. Having detections geared towards reviewing abnormal LAPS requests could help identify malicious behavior earlier and provide additional baseline information of who may be querying LAPS within an organization.
Lastly, I couldn’t have done this blog without help from the following individuals:
Andrew Schwartz (@4ndr3w6S)
Esteban Rodriguez (@n00py1)
2 References
Microsoft LAPS:
https://docs.microsoft.com/en-us/defender-for-identity/security-assessment-laps
Enabling Extended Logging:
Enabling LDAP Logging:
https://learn.microsoft.com/en-us/defender-for-identity/configure-windows-event-collection
Installing LAPS:
https://4sysops.com/archives/how-to-install-and-configure-microsoft-laps/
Attacking LAPS:
https://www.hackingarticles.in/credential-dumpinglaps/
https://www.n00py.io/2020/12/dumping-laps-passwords-from-linux/
https://github.com/n00py/LAPSDumper.git
https://github.com/Porchetta-Industries/CrackMapExec
The post A LAPS(e) in Judgement appeared first on TrustedSec.
from TrustedSec https://www.trustedsec.com/blog/a-lapse-in-judgement/
Comments
Post a Comment