The BloodHound Slack challenge
Coincidentally, as I was drafting my post on the ADWS blind spot (originally scoped to be part 6, but now Part 5A), I observed an interesting conversation take place in the BloodHound Slack #red-team channel. The conversation that followed set the stage for this research, and ended up leading me to decide to split Part 6 into two separate posts.
The original exchange
It started when User 1 asked about ADWS detection:
"Hi guys, what's your preferred way of opsec safe AD enum? ADSI vs ADWS? And what's your experience with detection of adws on the dc side? Eg when using soapy? ADSi has been very reliable for us, also in very mature environments. I would love to hear more about adws enum."
User 2 responded with his preference for ADWS and explained the detection challenges:
"My preferred way is using ADWS for AD collection but I'm a bit biased lol 😅
But the name of the game when performing stealthy collection of any type is all about using constrained and unique queries. Specifically doing collection through ADWS has a large number of opsec benefits which depending on how detections are written can completely bypass some LDAP oriented detections.
As for the best way of detecting ADWS, probably SACL canaries."
User 2 then explained the core attribution problem:
"The main thing differentiating the two protocols is with common LDAP diagnostic logging the host/device the collection appears to be coming from in the logs is always the DC the ADWS query is executed on due to the ADWS services interaction with the LDAP service on loopback, and detailed logging for the ADWS service is very lacking and usually not enabled."
Charlie Clark asked if correlation was possible with more logs:
"absolutely, but it's still possible to determine for adws requests, right? it's just it requires correlating more logs?"
User 2's response:
"Nope, not really. I've not found a way to correlate the originating host whatsoever. The only way to derive the originating host is through netsession enumeration, but its even harder with SOAPy because you can proxy in the traffic from a non-windows session."
Charlie pushed back:
"surely you could with a network event? 5156?"
User 2's full response outlined the challenges:
"Yeah you could theoretically identify the originating host with 5156 but thats just a basic network connection and usually tuned down due to the large amount of those events generated. Big identity solutions like MDI usually don't alert for collection on those types of events to my understanding due to the amount of normal traffic.
You could detect it with traffic to 9389 (ADWS) but there's an even bigger problem there, all of RSAT uses ADWS. So sysadmins doing normal operations would trigger false positives.
So you could figure it out during triage but it would be tough but alerting is very difficult"
Why Event 5156 wasn't a random suggestion
When I saw Charlie suggest Event 5156, I knew immediately that he was onto something. This wasn't a shot in the dark; Charlie, Jonathan Johnson, and I had collaborated on Event 5156 correlation before.
Prior work:
-
Jonathan Johnson's RPC Research - Jonny's SpecterOps 2022 paper, "RPC for Detection Engineers", had demonstrated the power of Event 5156 for correlating network connections with service activity.
-
Our Collaborative Post - Charlie, Jonny, and I had published "The Client Server Relationship - A Match Made in Heaven", where we showed exactly how to use Event 5156 for client-server correlation and attribution.
So when User 2 said 5156 correlation was “theoretically” possible but impractical, I knew the technique worked. The question was whether it would work specifically for ADWS.
Challenge accepted.
The hypothesis
User 2 raised valid concerns:
-
Event 5156 generates high volume
-
RSAT tools create false positives
-
Correlation is difficult
But I hypothesized that:
-
Event 5156 is already being collected by most enterprise SIEMs
-
Pattern detection in 1644 (like [all_with_list], SDflags:0x7, (!(FALSE))) solves the false positive problem
-
Timestamp correlation with a tight window could work
Phase 1: Understanding the event flow
In Part 5A we showed how ADWS hides the attacker’s IP from network sensors and Event 1644—the network sees only an encrypted blob on port 9389, and the LDAP log shows [::1] as the client. Here’s the full event sequence that makes attribution possible anyway.
Event 5156 is the missing piece. It comes from the Windows Filtering Platform (WFP) and hits the Security event log every time a network connection is permitted. It’s become one of my favorite events for correlation work because it records the application that owns the connection, not just the port. So instead of seeing generic svchost.exe, you see microsoft.activedirectory.webservices.exe. It’s on by default under the Filtering Platform Connection audit subcategory, so no extra configuration is needed.
When a remote host connects to ADWS on port 9389, Event 5156 captures the inbound connection with the real source IP. That happens before ADWS translates the request into an internal LDAP call, which is the step that swaps the real IP for localhost.
Figure 1: Event 5156 (Security Log): The Windows Filtering Platform captures the inbound ADWS connection with the real source IP before the request is translated internally.
Figure 2: Event 1138 (AD Web Services Log): ADWS logs the new connection with the client's real IP and a unique InstanceId that ties the session to subsequent events.
Figure 3: Event 1644 (Directory Service Log): The internal LDAP query shows localhost as the client—the real source IP is gone, but the user context and query details are preserved.
This is the external connection that provides source IP attribution.
Phase 2: Port-based correlation
Event 1644 shows the client as [::1]:60983 (localhost with ephemeral port). If we could find an internal Event 5156 showing ADWS connecting to LDAP on that same port, we could chain:
External 5156 → Internal 5156 (matching port) → 1644.
Running SOAPy from the Linux box (10.1.1.13) and checking the logs:
Event 1644:
Client: [::1]:60983
User: MARVEL\loki
Filter: ( & (objectClass=user) ... )
Internal 5156 events found:
03:54:46.406 | ::1:61532
03:53:46.393 | ::1:61522
03:52:46.380 | ::1:61514
The ports don’t match. Event 1644 shows port 60983, but the internal 5156 events show 61532, 61522, and so on. Port 60983 is a persistent LDAP connection ADWS established long before the test. The original 5156 for that connection had already rolled out of the Security log. New queries reuse this existing connection — no new 5156 is created for each query.
Port-based correlation only works in a SIEM where the original 5156 (when the persistent connection was first established) is still retained. On a live DC query, it may have rolled out.
Phase 3: Timestamp-based correlation
Port-based correlation failed, but the events still happen in sequence with predictable timing. Even if the persistent connection’s original 5156 has rolled out of the log, the external 5156 that fired for the current query is still there — timestamped within milliseconds of Event 1644.
The hypothesis: correlate by time window.
External 5156 → [ADWS processing] → Event 1644
~60–80ms
Same SOAPy attack, checking timestamps:
External 5156:
Time: 03:53:28.407
Source IP: 10.1.1.13
Dest Port: 9389
Application: microsoft.activedirectory.webservices.exe
Event 1644:
Time: 03:53:28.469
Client: [::1]:60983
User: MARVEL\loki
Filter: ( & (objectClass=user) (objectCategory=CN=Person,CN=Schema,CN=Configuration,DC=marvel,DC=local) )
Attributes: [all_with_list]nTSecurityDescriptor
Controls: SDflags:0x7;
Delta: 62ms
Repeated testing across three runs:
|
Test Run |
External 5156 |
Event 1644 |
Delta |
|
Run 1 |
03:52:22.256 |
03:52:22.339 |
83.2ms |
|
Run 2 |
03:53:28.407 |
03:53:28.469 |
61.2ms |
|
Run 3 |
03:55:25.275 |
03:55:25.341 |
65.8ms |
Consistent ~60-80ms window across all three runs. The processing delay between external connection and LDAP query execution is stable enough to correlate events.
Phase 4: Building the detection script
With timestamp correlation confirmed as reliable, the next step was building a script that could do this automatically — collecting the relevant events, attempting port-based correlation first, then falling back to the timing window.
Running Get-ADWSAttribution.ps1 during active SOAPy enumeration:
Figure 4: Get-ADWSAttribution.ps1 correlating Event 5156 → 1644 to attribute LDAP queries back to their source IP, catching MARVEL\loki mid-BloodHound enumeration.
Source IP 10.1.1.13 correctly attributed to all three queries. The real-time monitor mode catches it as it happens:
Figure 5: Three events. One attacker. Event 5156 tells you who connected. Event 1138 tells you ADWS proxied it. Event 1644 tells you what they were after.
Phase 5: The 5156 reality check
The first objection to any 5156-based detection is volume, the concern that it’s tuned down or disabled in most environments. Let's check:
auditpol /get /subcategory: "Filtering Platform Connection"
Filtering Platform Connection Success
Event 5156 is on by default. It’s part of Windows Filtering Platform auditing that ships enabled out of the box. The volume concern is real, but it’s about retention, not availability. Every network connection generates a 5156, so on a busy DC, the Security log fills fast, and old events get overwritten.
The fix is SIEM. Most enterprise environments already forward Security logs to a SIEM, which means the data is retained for days or weeks.
|
Environment |
5156 Availability |
Correlation Method |
|
Live DC query |
May have rolled out |
TIMESTAMP (probabilistic) |
|
SIEM (Splunk/Sentinel) |
Retained for days/weeks |
PORT (deterministic) or TIMESTAMP |
|
Forensic EVTX backup |
If captured in time |
Either method |
Phase 6: Addressing the false positive problem
User 2's concern: "All of RSAT uses ADWS."
True. Sysadmins using PowerShell AD cmdlets generate identical 5156 traffic to port 9389. The solution is pattern detection in Event 1644. You don’t alert on every ADWS connection—only on connections that correlate with suspicious LDAP patterns.
|
Pattern |
Meaning |
Legitimate Use? |
|
[all_with_list] |
PowerShell -Properties * |
Rare for admins |
|
SDflags:0x7 |
Full security descriptor request |
Very rare |
|
(!(FALSE)) |
SOAPHound signature |
Never |
|
High volume localhost queries |
Bulk enumeration |
Suspicious |
|
nTSecurityDescriptor in attributes |
ACL enumeration |
BloodHound pattern |
The detection flow
Figure 6: The Detection Flow: spot the suspicious pattern in Event 1644, correlate it to a matching Event 5156 within ~100ms, walk away with a confirmed source IP, authenticated user, query details, and tool signature—all from logs that were already there.
Where this leaves the debate
User 2 was right about several things: LDAP events show localhost, 5156 generates high volume, RSAT creates false positives, and real-time DC-local detection is harder when the original 5156 has already rolled out of the Security log.
But his conclusion that the originating host “cannot be determined” doesn’t hold for SIEM-based detection. The data has been there all along in most enterprise environments. Timestamp correlation works even when port-based correlation doesn’t. And pattern detection in Event 1644 handles the false positive problem he identified.
The gap was never technical. It was that nobody had published the correlation method.
Summary: The complete attribution chain
Figure 7: Complete attribution chain: Event 5156 captures the real source IP at the network boundary; Event 1138 ties the ADWS session to the authenticated user; Event 1644 reveals the query—correlate all three, and the attacker has nowhere to hide.
Conclusion
"Can you identify who's querying AD via ADWS?"
Yes. Event 5156 correlation with a ~60–80ms timing window gives you the source IP that Event 1644 hides behind localhost. Port-based correlation works when your SIEM retains the original connection event. Timestamp-based correlation works even when it doesn't.
The data was always there. Nobody was correlating it.
Acknowledgments
User 1 - For asking the original question in #red-team
User 2 - For explaining the ADWS/LDAP loopback problem and the challenges
Charlie Clark - For suggesting Event 5156 correlation as a potential solution
BloodHound Slack #red-team - Where the conversation happened
Thanks to Jonathan Johnson, Anton Ovrutsky, Matt Anderson, Tyler Bohlmann, Michael Haag, Nasreddine Bencherchali, and Adam Bienvenu for their help in reviewing this post.