Skip to Main Content
January 10, 2023

A LAPS(e) in Judgement

Written by Megan Nilsen

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

Figure 1 - Enable Event ID 4662 Logging in GPO

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:

Figure 2 - Adding Object Auditing for All Objects - Part 1
Figure 3 - Adding Object Auditing for All Objects - Part 2
Figure 4 - Adding Object Auditing for All Objects - Part 3

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:

<em>git clone </em>https://github.com/n00py/LAPSDumper.git<em></em>

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.

Figure 5 - LAPSDumper Attack Execution

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:

Figure 6 - Event ID 4662 Displayed in Splunk

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.

Figure 7 - Event ID 4662 Properties

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:

Figure 8 - LDP Application

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:

Figure 9 - LDP Connect

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:

Figure 10 - LDP Bind

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:

Figure 11 - LDP 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:

Figure 12 - LDP Search for ms-Mcs-AdmPwd Attribute

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:

Figure 13 - ms-Mcs-AdmPwd Properties View

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

Figure 14 - 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.

Figure 15 - Simple LAPS Query With Table

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:

Figure 16 - LAPS Query With Thresholding

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:

Figure 17 - Identifying Logon_IDs Within Event ID 4662

Now, let's build a new search for Event ID 4624 and throw some important fields into a table for quick searching:

Figure 18 - Quick Event ID 4624 Search

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:

Figure 19 - Searching Event ID 4624 for Logon IDs Corresponding to LAPS Attacks

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
Figure 20 - Complex Laps Query With Thresholding

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
Figure 21 - Attacking LAPS With CrackMapExec

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.

Figure 22 - LAPS Query Detecting Attacks With CrackMapExec

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:

Figure 23 - Edit Registry Key 16 LDAP Interface Events
Figure 24 - Edit Registry Key 15 Field Engineering

Next, navigate to:

Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters

And set the Search Time Threshold (msecs) key to 1:

Figure 25 - Edit Search Time Threshold

Leveraging Event ID 1644 from the Directory Services log, we can now see our LAPS Attack logs in Splunk:

Figure 26 - LDAP Logging LAPS Simple Detection

You can re-add thresholding to make the query actionable for a production network:

Figure 27 - LDAP Logging LAPS Detection With Threshold

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:

https://4sysops.com/archives/part-2-faqs-for-microsoft-local-administrator-password-solution-laps/#:~:text=In%20its%20default%20configuration%2C%20LAPS%20only%20logs%20errors.,the%20Application%20Event%20Log%20by%20the%20AdmPwd%20Source.

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/

https://4sysops.com/archives/set-up-microsoft-laps-local-administrator-password-solution-in-active-directory/

https://techcommunity.microsoft.com/t5/itops-talk-blog/step-by-step-guide-how-to-configure-microsoft-local/ba-p/2806185#:~:text=Installing%20Microsoft%20LAPS%201%201.%20Download%20Microsoft%20LAPS,select%20%E2%80%9C%20Management%20Tools%20%E2%80%9D.%20...%20More%20items

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