Active Directory Enumeration for Red Teams
The Directory Service is the heart and soul of many organisations, and whether its Active Directory, OpenLDAP or something more exotic, as a source of much knowledge it often acts as a conduit for internal reconnaissance and other attacks during red team operations.
With this in mind, it is common to see blue teams invest heavily in securing and monitoring access to the directory, whether this is through honey tokens, analysis of LDAP queries, anomalies within telemetry, or other similar defensive strategies.
Therefore, this creates a challenge for red teams looking to tap in to this knowledge base for reconnaissance or privilege escalation attacks. Simply running automated tools such as BloodHound, StandIn, SharpView and equivalent without intimate knowledge of what they’re doing under the hood might leave you blindly navigating a minefield of potential detection points.
In this post, we will explore how defenders can monitor for suspicious LDAP activity, as well as operational security approaches for red teams conducting LDAP reconnaissance.
Monitoring for LDAP Activity
Before we look at the part LDAP tradecraft can play in an operation, we need to understand how defenders can collect telemetry about our activities, and the telemetry that our tools might generate. This allows us to determine potential detection points and theorise strategies to circumvent them. There are typically two areas this telemetry is collected, the source (i.e. the endpoint) and the destination (i.e. the Directory server or domain controller); in some cases this may be both.
Endpoint Telemetry
Endpoint LDAP telemetry is typically collected through one of two approaches; API level hooking (often targeted at wldap32.dll
) and Event Tracing for Windows (ETW) using the Microsoft-Windows-LDAP-Client
provider.
With respect to telemetry obtained through API level hooking, the content is of course dependent on the product making use of this approach and can almost always be trivially circumvented through unhooking of the relevant user mode DLLs. As such, we will not go in to any further details on this telemetry.
In terms of the Microsoft-Windows-LDAP-Client
ETW provider, we can subscribe to this telemetry to gain further insight on what the EDR might see using tools such as SilkETW:
SilkETW.exe -t user -pn Microsoft-Windows-LDAP-Client -ot eventlog
With SilkETW running, let’s perform a basic custom LDAP query with the filter for SPNs, such as (serviceprincipalname=*)
using ADSearch.exe
:
As we note above, peeking in to the logs obtained from SilkETW for the Microsoft-Windows-LDAP-Client
provider, we can see we now have a rich data set that details the search query filters, affected process, and more.
With your red tinted glasses on, you might begin to think how you can use such telemetry to your advantage, particularly when trying to blend your Active Directory tradecraft with typical process behaviour. For example, even monitoring an endpoint for a short time, we can note processes like Excel.exe
:
and taskhostw.exe
:
These processes are frequently making LDAP queries and may offer potential candidates for surrogates in which to perform our LDAP post-exploitation. Making such informed decisions can assist red teamers in blending in with the noise. Equally, defenders can leverage such intelligence to baseline processes that are typically interacting with LDAP and use this to spot outliers in their estate.
SilkETW, of course, supports Yara, meaning that defenders can build detection logic around LDAP filters that might be used for potential offensive tradecraft, such as privileged user enumeration or scanning of service principals. Using our previous example of (serviceprincipalname=*)
we can build a simple Yara rule to flag any processes making this LDAP query:
rule spnscan {
strings:
$s1 = /serviceprincipalname=\\*/
condition:
any of ($s*)
}
Rerunning SilkETW with the Yara rules option enabled, we now get hits for our spnscan
rule:
The primary downside to this telemetry is, that it is very easy to circumvent through user mode ETW patching and any command-and-control framework that supports this feature is able to disable it for any post-exploitation tools that are executed in process. As an example, repeating the exact same LDAP query using Nighthawk, we can see there are now no events generated in our SilkETW capture:
The same approach can be applied to your EDR which typically will typically consume the same ETW telemetry. Using Microsoft Defender for Endpoint (MDE) as an example, we can identify any LDAP searches performed in the last hour from a specific endpoint using a hunt query of the DeviceEvents
table similar to the following:
let window = 1h;
DeviceEvents
| where ingestion_time() >= ago(window)
| where ActionType =~ "LdapSearch"
| where DeviceName =~ "WS1.zone1.bossbank.local"
| extend AdditionalFields=parse_json(AdditionalFields)
| extend SearchFilter=tostring(AdditionalFields.SearchFilter)
| project-reorder DeviceName, InitiatingProcessCommandLine, SearchFilter
To understand if MDE is capable of capturing this telemetry, we can repeat the same LDAP search from both a beacon process (where an ETW hook is applied) and the command line:
Running the hunt query, we note that we only receive telemetry for the process where the ETW hooks are not applied, confirming our understanding that MDE is using the Microsoft-Windows-LDAP-Client
ETW provider to capture its LDAP telemetry:
If we want to extend our hunts to search for suspicious activity, similar to how we did with the SilkETW Yara rules, we can modify the query to include detection of the serviceprincipalname=*
filter, as follows:
let window = 1h;
let dodgyfilters=dynamic([
"serviceprincipalname=*"
]);
DeviceEvents
| where ingestion_time() >= ago(window)
| where ActionType =~ "LdapSearch"
| extend AdditionalFields=parse_json(AdditionalFields)
| extend SearchFilter=tostring(AdditionalFields.SearchFilter)
| where SearchFilter has_any(dodgyfilters)
| project-reorder DeviceName, InitiatingProcessCommandLine, SearchFilter
As we can see, this quickly identifies our LDAP query when executed without the ETW hook:
Directory Server Telemetry
By default, Windows does not record LDAP events due to the performance impacts this might have in production environments. There are, of course, a number of third-party identity options to achieve this, but instead let’s first focus on how we can achieve this natively.
To enable LDAP logging, there’s a number of registry settings we should alter on the domain controller. The first (HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\NTDS\Diagnostics\15 Field Engineering
) increases the logging level to level 5, which will enable the capture of Directory Service events (ID 1644):
Next, we need to modify the logging of expensive and efficient queries such that the criteria for meeting the threshold is sufficiently low that we’re able to capture the log events. To do this, lower the values of the following registry keys so the threshold is effectively removed, by setting the values to 1:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters\Expensive Search Results Threshold
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters\Inefficient Search Results Threshold
From this point onwards, all Directory Service events (ID 1644) will be captured on the Domain Controllers event log. To test this, let’s send a simple LDAP query to retrieve domain computers:
Reviewing the Domain Controllers event log we can now see that ID 1644 events (Directory Service) are now being captured:
This telemetry could of course, be collected on the domain controller side, so cannot trivially be evaded when operating from an endpoint. It does, however, give us sufficient insight in to understanding what defenders might be able to see from our tools, which we can, in turn, leverage to adapt our tradecraft.
As an alternative, and perhaps more practical method of collecting LDAP telemetry from the domain controller, we can also turn to third-party identity solutions such as Microsoft Defender for Identity (MDI).
Microsoft Defender for Identity will capture LDAP telemetry (for the most part) passing through the domain controller in the IdentityQueryEvents
events table. We can use the advanced hunting feature to query this table to view this telemetry data, as follows:
let timeframe = 24h;
IdentityQueryEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "LDAP query"
As we can see, the Query field holds the full query details for our serviceprincipalname=*
filter:
If you delve in to this telemetry, you’ll also note that the source process tree is not populated, with the source information being restricted only to the host (as far as I can tell). More on this later.
Building on this telemetry, we can, of course, create custom detection and custom hunt rules, such as the following, which is taken from the MDI community queries section of the Defender portal:
This specific rule will detect our prior SPN reconnaissance, as it triggers on the serviceprincipalname=*
filter:
// Detect Active Directory LDAP queries that search for Kerberoasting (SPNs) or accounts with Kerberos preauthentication not required from Azure ATP, and try to get the process initiated the LDAP query from MDATP.
// Replace 389 on line 5 with LDAP port in your environment
// Replace true on line 6 to false if you want to include Nt Authority process
// This LDAP query cover Rubeus, Kerberoast, BloodHound tools
// This query was updated from <https://github.com/Azure/Azure-Sentinel/tree/master/Hunting%20Queries/Microsoft%20365%20Defender/Discovery/Roasting.yaml>
let ASREP_ROASTING = "userAccountControl:1.2.840.113556.1.4.803:=4194304";
let ASREP_ROASTING1 = "userAccountControl|4194304";
let ASREP_ROASTING2 = "userAccountControl&4194304";
let KERBEROASTING = "serviceprincipalname=*";
let LDAP_PORT = 389;
let ExcludeNtAuthorityProcess = true;
let AzureAtpLdap = (
IdentityQueryEvents
| where ActionType == "LDAP query"
| parse Query with * "Search Scope: " SearchScope ", Base Object:" BaseObject ", Search Filter: " SearchFilter
| where SearchFilter contains ASREP_ROASTING or
SearchFilter contains ASREP_ROASTING1 or
SearchFilter contains ASREP_ROASTING2 or
SearchFilter contains KERBEROASTING
| extend Time = bin(Timestamp, 1s)
| extend DeviceNameWithoutDomain = tolower(tostring(split(DeviceName, '.')[0])));
let MDAtpNetworkToProcess = (
DeviceNetworkEvents
| extend DeviceNameWithoutDomain = tolower(tostring(split(DeviceName, '.')[0]))
| where RemotePort == LDAP_PORT
| extend Time = bin(Timestamp, 1s)
| extend isExclude = iff( ExcludeNtAuthorityProcess and InitiatingProcessAccountDomain == "nt authority" , true, false));
AzureAtpLdap
| join kind=leftouter (
MDAtpNetworkToProcess ) on DeviceNameWithoutDomain, Time
| where isExclude == false or isnull(isExclude)
This can then, of course, be turned in to a custom detection rule in the Defender portal to run periodically.
Other potential detection points where similar queries might prove effective in detecting offensive tradecraft could include:
- Detection of wildcard queries;
- Enumeration of sensitive principals;
- Signaturing of specific query tradecraft (e.g. filters containing “description=pass”);
- Number or size of LDAP queries made within a specific time window.
These are left as an exercise for the reader, but we recommend consulting the Azure Sentinel community queries for more ideas.
Now that we have sufficient sources of telemetry for our LDAP queries, let’s dive in to how this telemetry might affect red team operations.
Analysis of Red Team Tools
With sufficient details on how we can collect LDAP telemetry data from both the endpoint and domain controller, let’s turn our attention to how this might impact our use of offensive tools that target Active Directory enumeration.
SharpHound
Automated Active Directory enumeration is, of course, going to be noisy; therefore, it should come as no surprise that SharpHound will be detected out of the box by identity solutions, even in its most “stealthy” configuration.
Running SharpHound with a limited collection set, such as the following, will at a minimum lead to a security principal reconnaissance alert in MDI:
SharpHound.exe -c DCOnly -d zone1.bossbank.local --stealth --secureldap
Of course, not everyone will have MDI, so let’s delve in to what SharpHound is doing here. This will not only assist us in building signatures for its behaviour, but also to understand what we might need to alter to evade detection.
Retrieving the LDAP queries conducted for the period we ran SharpHound, we can see the following:
Diving in to the results, we can note a number of queries, including the following:
LDAP Search Scope: "WholeSubtree", Base Object: "DC=zone1,DC=bossbank,DC=local", Search Filter: " ( | (sAMAccountType=805306369) (objectClass=container) (sAMAccountType=805306368) ( | (sAMAccountType=268435456) (sAMAccountType=268435457) (sAMAccountType=536870912) (sAMAccountType=536870913) ) (objectClass=domain) (objectCategory=CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=bossbank,DC=local) ( & (objectCategory=CN=Group-Policy-Container,CN=Schema,CN=Configuration,DC=bossbank,DC=local) (flags=*) ) (primaryGroupID=*) ) "
Tracing this back to the SharpHound source code, you’ll note the SharpHoundCommon/src/CommonLib/LDAPQueries/LDAPFilter.cs
file with components of the file hardcoded:
Now that we know what SharpHound is doing and why, we can think about potential methods to not only evade any signatures that might be reliant on these filters, but also to potentially take alternative approaches that might allow us to retrieve the same information in a more OpSec safe manner. We’ll leave this as an exercise for the reader.
ADExplorer
ADExplorer is an excellent tool for analysing Active Directory both offline and online and has become a useful weapon in the red team arsenal. ADExplorer snapshots are particularly useful to red teams as they can be converted to BloodHound using tools such as ADExplorerSnapshot by c3c.
Running ADExplorer
to capture a snapshot of our domain gives some interesting results in our Defender setup; querying the IdentityQueryEvents
table to understand what MDI would see results in the following:
As you might have noticed, there are no results in the IdentityQueryEvents
table for the LDAP queries that ADExplorer
performed; this might seem a little odd, and and we certainly agree, but these queries do not appear to be stored by MDI.
Delving in to the DeviceEvents
table does, however, give us more information on what’s going on and we can see a series of queries being performed by ADExplorer
:
These queries are all relatively simplistic; however, the (objectGUID=*)
does, at least from our assessment, appear to be somewhat rare in nature; it could therefore provide a useful detection point for identifying the use of ADExplorer
, assuming sufficient ETW telemetry is at hand.
SharpView
With the above in mind, you’re probably thinking more targeted custom queries are a better approach to automated enumeration and, of course, you’re right. A popular tool amongst some red teams that wraps up some of these more targeted queries is SharpView.
As an example, you might leverage the Get-DomainGroupMember
command to recover group members for a given group:
These queries will be logged in MDI’s IdentityQueryEvents
table, but there are no built-in alerts explicitly raised. We can, however, create a high-fidelity alert for the use of SharpView, as my colleague @rbmaslen pointed out, based on the use of the PCRE.net library which drops a DLL to disk in a fixed location inside %TEMP%
.
Building an MDE hunt rule allows us to trivially create a custom detection for SharpView based on the file creation events for ba9ea7344a4a5f591d6e5dc32a13494b.dll
; a hunt may look similar to the following:
let window = 1h;
DeviceFileEvents
| where ingestion_time() >= ago(window)
| where DeviceName contains "WS1"
| where FolderPath contains "ba9ea7344a4a5f591d6e5dc32a13494b"
Tradecraft Considerations for Red Teams
Having looked at various approaches to detect Active Directory enumeration through both the offensive techniques and the tools frequently used, let’s have a look at some approaches that red teams can take to minimise the opportunities for detection.
Blending In
As we briefly touched upon, one potential avenue for detection is through baselining the processes that would typically be performing LDAP interaction. It therefore stands to reason when looking to perform Active Directory post-exploitation, that we should consider the possibility of blending in with a suitable surrogate process.
In order to find potentially suitable surrogates, let’s use the blue team’s tools against them by building a KQL query to find all processes that have recently performed an LDAP search:
DeviceEvents
| where ActionType =~ "LDAPSearch"
| extend AdditionalFields=parse_json(AdditionalFields)
| extend SearchFilter=tostring(AdditionalFields.SearchFilter)
| project InitiatingProcessFolderPath, InitiatingProcessCommandLine, SearchFilter, InitiatingProcessParentFileName
This gives us the following list of processes, alongside their parents, that might make potentially useful candidates for blending our LDAP tradecraft:
InitiatingProcessFolderPath | InitiatingProcessCommandLine | SearchFilter | InitiatingProcessParentFileName |
---|---|---|---|
c:\windows\system32\sppextcomobj.exe | SppExtComObj.exe -Embedding | (objectclass=*) | svchost.exe |
c:\windows\system32\lsass.exe | lsass.exe | (&(DnsDomain=seth\2Elocal\2E)(Host=WD1)(DomainGuid=1\21\C6\0F\2A\5D\3FE\88n\8F\9A\BA\01\BB\0E)(NtVer=\16\00\00\00)(DnsHostName=WD1\2Eseth\2Elocal)) | wininit.exe |
c:\windows\system32\svchost.exe | svchost.exe -k netsvcs -p -s gpsvc | (objectclass=*) | services.exe |
c:\windows\system32\windowspowershell\v1.0\powershell.exe | powershell.exe -ExecutionPolicy AllSigned -NoProfile -NonInteractive -Command “& {$OutputEncoding = [Console]::OutputEncoding =[System.Text.Encoding]::UTF8;$scriptFileStream = [System.IO.File]::Open(‘C:\ProgramData\Microsoft\Windows Defender Advanced Threat Protection\DataCollection\8699.10157887.0.10157887-b7db1ed00ad748852a3572e55ed3350977567f27\9e137068-a631-45e6-81aa-4adda242796e.ps1’, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read);$calculatedHash = Microsoft.PowerShell.Utility\Get-FileHash ‘C:\ProgramData\Microsoft\Windows Defender Advanced Threat Protection\DataCollection\8699.10157887.0.10157887-b7db1ed00ad748852a3572e55ed3350977567f27\9e137068-a631-45e6-81aa-4adda242796e.ps1’ -Algorithm SHA256;if (!($calculatedHash.Hash -eq ‘b10268615d74417598398ce57b5127e1aca9b4c6059d69bfe15200fd68689aee’)) { exit 323;}; . ‘C:\ProgramData\Microsoft\Windows Defender Advanced Threat Protection\DataCollection\8699.10157887.0.10157887-b7db1ed00ad748852a3572e55ed3350977567f27\9e137068-a631-45e6-81aa-4adda242796e.ps1’ }” | (objectclass=*) | SenseIR.exe |
c:\windows\system32\taskhostw.exe | taskhostw.exe SYSTEM | (objectclass=*) | svchost.exe |
c:\windows\system32\backgroundtaskhost.exe | “BackgroundTaskHost.exe” -ServerName:BackgroundTaskHost.WebAccountProvider | (objectclass=*) | svchost.exe |
c:\windows\system32\dns.exe | dns.exe | (&(objectCategory=dnsNode)(uSNChanged>=755011)) | services.exe |
c:\windows\system32\dfsrs.exe | DFSRs.exe | (objectCategory=msDFSR-ContentSet) | services.exe |
c:\windows\adws\microsoft.activedirectory.webservices.exe | Microsoft.ActiveDirectory.WebServices.exe | (objectClass=*) | services.exe |
c:\windows\system32\ismserv.exe | ismserv.exe | (objectClass=interSiteTransport) | services.exe |
c:\windows\system32\spoolsv.exe | spoolsv.exe | (objectclass=*) | services.exe |
c:\windows\system32\services.exe | services.exe | (objectclass=*) | wininit.exe |
c:\windows\system32\certsrv.exe | certsrv.exe | (objectclass=*) | services.exe |
c:\windows\system32\mmc.exe | “mmc.exe” “C:\Windows\system32\dsa.msc” | (objectclass=*) | svchost.exe |
c:\windows\system32\wbem\wmiprvse.exe | wmiprvse.exe -secured -Embedding | (objectclass=*) | svchost.exe |
Accessing the Directory
Hopefully based on the various detection points that we’ve look at so far, it stands to reason that when accessing the directory we should favour custom, targeted queries over automated, overly scoped retrievals of directory data.
When accessing the directory directly, aside from preferring an encrypted transport such as LDAPS (which we assume is a given so will ignore here), there’s two things that we should bear in mind to limit the amount of data we’re extracting, and thus avoid an overly large LDAP data pull: the search base and the search scope.
You can consider LDAP to take a hierarchical structure, much like a tree. This structure is composed of various entries, each representing objects like users, groups, or organisational units. Each entry is identified by a unique Distinguished Name (DN). The search base determines the starting point in the directory from where the search for entries begin.
The search base, in combination with the search scope, determines which entries are considered during the search. The scope can be set to different levels such as:
- Base: Searches only the entry defined as the search base;
- One level: Searches all entries directly under the search base, but not including the base itself;
- Subtree: Searches the base entry and all entries under it, traversing the entire subtree.
Whilst at MDSec we almost exclusively use our own internal LDAP tooling, there are several public tools that allow you to perform custom LDAP queries, and I’m going to briefly consider three of what I think are the most popular: ADSearch by a former @MDSecLabs’er Tom Carver, StandIn by FuzzySec and the ldapsearch BOF from TrustedSec.
Both ADSearch and StandIn are developed using .NET and use the DirectorySearcher
APIs, which uses a DirectoryEntry
object to determine the search base for the directory which by default will be the root of the directory. In both instances, neither of these tools allow the operator to control the search base nor the search scope.
In the case of ADSearch, the distinguished name to use as the search base is derived from the current domain name here:
Whilst in StandIn, this uses the default DirectoryEntry
context of the root domain for its custom queries:
Conversely, the ldapsearch BOF does offer the operator control over both the search base (ultimately passed as distinguishedName
to ExecuteLDAPQuery()
) and the search scope:
Indeed, the ldapsearch
BOF was the only public tool that I could explicitly identify as attempting to support this feature. However, from what I could tell, there is a bug in the tool when parsing arguments if a search scope is passed. This, combined with the absence of the feature in other tools would imply that this might be an area of tradecraft that is overlooked by some. It is also worth noting that this tool does not appear to support LDAPS, so this is something to be aware of from an OpSec perspective.
If we’re looking to bring this functionality to ADSearch and StandIn, it’s a relatively straightforward task. For example, in the case of ADSearch, we can simply add an additional command line argument for whether we want to recurse the directory and if not, apply the relevant scope to the DirectorySearcher
object (i.e. set to OneLevel
):
Further, to add support for an arbitrary search base, we can again simply provide a user supplied argument for the distinguished name (distinguishedName
) to our DirectoryEntry
object:
Custom Query Tradecraft
With ideas on the approach for how and where we should perform our LDAP post-exploitation from, let’s dive deeper in to the tradecraft for querying it.
Hopefully, based on some what we’ve looked at so far, we have a better understanding at the potential detection pitfalls that we might want to avoid; to reiterate some of the common ones may include:
- Overly scoped wildcard queries;
- Access to sensitive principals;
- Signatures of specific queries or filters against techniques or tools;
- Volume or size of data retrieved in a short time window.
The question then becomes: how do we operate with this in mind as much as possible? My approach, at least, is to try and generically tackle this by leveraging small, targeted queries to retrieve particular data sets from the directory that contain the information that I desire, without explicitly requesting that information. Let’s look at this in a little more detail.
I typically start my directory reconnaissance using a technique I call “OU walking”; essentially, this involves slowly mapping out the organisational units in the directory. This hopefully provides sufficient information on identifying where the objects of interest might reside, assuming a tidily structured directory.
Let’s imagine our domain root might look as follows, with a handful of organisational units:
We can extract a list of OUs using a simple query such as “ou>=’’”,
which returns the distinguished name for all objects with a non-empty OU property, from the domain root and without recursion:
If any of the OUs look like they might hold the information that we’re looking for, we can repeat the same thing to find any interesting OUs at the next level. For example, here we are likely to want to expand the Production
OU, as it stands out as being potentially more interesting than the others:
If the purpose of our LDAP exploration was to identify potentially interesting accounts for roasting, we might want to investigate the Accounts
OU, and rinse and repeat this process until there’s an OU we might want to fully explore the objects inside. When we find an OU that we want to retrieve the contents of (in the case of our test AD OU=Database,OU=Service Accounts,OU=Accounts,OU=Production,DC=zone1,DC=bossbank,DC=local
), we can then retrieve all objects in the container using a suitable query that avoids sensitive attributes or wildcards:
We explicitly avoid requesting suspicious attributes such as serviceprincipalname
in our filter and instead retrieve everything, allowing us to parse this information on the client side. This approach does marginally increase the size of the data were retrieving (although we can page), but with the combination of binding to a specific OU and preventing recursion, this should hopefully be limited whilst also restricting the opportunity for signatures.
If we then try to hunt for this activity inside of MDI, we’ll find no events were stored:
This approach, in combination with the other referenced tradecraft has served me well for some time and hopefully provides a platform on which to conduct further LDAP post-exploitation.
Active Directory Web Services (ADWS)
In October 2021, we were performing a product assessment for an Active Directory deception vendor. During the course of the assessment, we realised that the Active Directory modules for RSAT were not affected by the honey data. Investigating this led us down the path of Active Directory Web Services, and we were quick to recognise the potential added benefits that this might bring to red team engagements. After having some success with this, we decided it would not be in our interests to publicly document this technique at the time. However, after FalconForce published their research on SOAPHound, we decided to dive in to our vault and reinvigorate it.
One of the key benefits of using ADWS for LDAP post-exploitation was that it was relatively unknown, and therefore no one was monitoring for its use. ADWS operates an entirely different service to LDAP, available on TCP port 9389 and using a SOAP protocol for its interface.
While investigating ADWS, we noted that because it was a SOAP web service, the actual LDAP queries being executed were being done on the domain controller. This provided a number of interesting side effects which turned out to be advantageous. Firstly, analysing the LDAP queries on the domain controller, you may note that the queries originate from 127.0.0.1
in the logs:
This became the cause of confusion during some operations where LDAP queries were spotted by the blue team, and in many cases led to them being overlooked or disregarded.
A secondary benefit of this is that the activity does not show up in the DeviceEvents
under the LDAPSearch
action type, meaning that very little telemetry is available.
In order to start building a client and gain an understanding of the functionality exposed by the service, we can add a service reference inside Visual Studio:
From the new namespace, you can then expose the various methods that can be called:
Using the methods available on the service, it is then possible to start building ADWS features. For example, to retrieve group membership, we can bind to the relevant group and call the GetADGroupMember
method, as shown below:
NetTcpBinding tcpBind = new NetTcpBinding();
ADWSService.AccountManagementClient acm = new ADWSService.AccountManagementClient(tcpBind, new EndpointAddress("net.tcp://100.64.0.72:9389/ActiveDirectoryWebServices/Windows/AccountManagement"));
acm.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
var principal = acm.GetADGroupMember("ldap:389", "CN=Domain Admins,CN=Users,DC=seth,DC=local","DC=seth,DC=local", true);
foreach (var attr in principal)
{
Console.WriteLine(attr.DistinguishedName);
Console.WriteLine(attr.SamAccountName);
}
Executing this as a domain user will recover the members of the Domain Admins
group:
Building a fully functional ADWS search client is not immediately that trivial; however, the following resource proved useful to us.
While performing ADWS post-exploitation, we discovered there was little telemetry available on the actions being performed stored in MDE or MDI. For example, performing a targeted ADWS query such as the following, resulted in almost nothing being visible in the telemetry:
Indeed, the only way we found possible to positively detect the presence of ADWS post-exploitation was through baselining of network events. The DeviceNetworkEvents
table in MDE provides details around process connectivity; using a hunt such as the following will assist in identifying processes that are connecting to ADWS:
DeviceNetworkEvents
| where DeviceName contains "WS1"
| where RemotePort == 9389
On the whole, we found this to be relatively rare in an environment, so it might provide a high-fidelity point of detection.
Content Link – PUSH