Detection rules › Kusto
Hunt MDE with GSA events
This rule correlates the Microsoft Defender for Endpoint DeviceNetworkEvents table with the Global Secure Access NetworkAccessTraffic table. By doing this, you can enrich the MDE events which contains detailed process information with the GSA events that contains detailed HTTP header information and more.
References
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Sysmon | Event ID 3 | Network connection |
| Security-Auditing | Event ID 5156 | The Windows Filtering Platform has permitted a connection. |
| Defender-DeviceNetworkEvents | any | Network activity (any) |
Rule body yaml
let gsa_events = NetworkAccessTraffic
// Join DeviceInfo to get MDE DeviceID
| join kind=inner (
DeviceInfo
| distinct DeviceId, AadDeviceId
) on $left.DeviceId == $right.AadDeviceId
// Remove Entra Device ID from GSA logs
| project-away DeviceId
// Rename MDE Device ID to DeviceId column
| project-rename DeviceId = DeviceId1;
// Get all MDE network events
DeviceNetworkEvents
// Get HTTP details if HTTP connection is logged
| extend HttpStatus = toint(todynamic(AdditionalFields).status_code),
BytesIn = toint(todynamic(AdditionalFields).response_body_len),
BytesOut = toint(todynamic(AdditionalFields).request_body_len),
HttpMethod = tostring(todynamic(AdditionalFields).method),
UrlHostname = tostring(todynamic(AdditionalFields).host),
UrlPath = tostring(todynamic(AdditionalFields).uri),
UserAgent = tostring(todynamic(AdditionalFields).user_agent),
HttpVersion = tostring(todynamic(AdditionalFields).version)
// Join GSA logs
| join kind=inner gsa_events on
DeviceId,
$left.RemoteUrl == $right.DestinationFqdn,
$left.RemotePort == $right.DestinationPort,
$left.Protocol == $right.TransportProtocol,
$left.InitiatingProcessFileName == $right.InitiatingProcessName
| project-rename TimeGeneratedGsa = TimeGenerated1, TimestampMde = Timestamp
| project-away Type, TenantId, TimeGenerated, TenantId1, Type1, DeviceId1, AadDeviceId
let gsa_events = NetworkAccessTraffic
// Join DeviceInfo to get MDE DeviceID
| join kind=inner (
DeviceInfo
| distinct DeviceId, AadDeviceId
) on $left.DeviceId == $right.AadDeviceId
// Remove Entra Device ID from GSA logs
| project-away DeviceId
// Rename MDE Device ID to DeviceId column
| project-rename DeviceId = DeviceId1;
// Get all MDE network events
DeviceNetworkEvents
// Get HTTP details if HTTP connection is logged
| extend HttpStatus = toint(todynamic(AdditionalFields).status_code),
BytesIn = toint(todynamic(AdditionalFields).response_body_len),
BytesOut = toint(todynamic(AdditionalFields).request_body_len),
HttpMethod = tostring(todynamic(AdditionalFields).method),
UrlHostname = tostring(todynamic(AdditionalFields).host),
UrlPath = tostring(todynamic(AdditionalFields).uri),
UserAgent = tostring(todynamic(AdditionalFields).user_agent),
HttpVersion = tostring(todynamic(AdditionalFields).version)
// Join GSA logs
| join kind=inner gsa_events on
DeviceId,
$left.RemoteUrl == $right.DestinationFqdn,
$left.RemotePort == $right.DestinationPort,
$left.Protocol == $right.TransportProtocol,
$left.InitiatingProcessFileName == $right.InitiatingProcessName
| project-rename TimeGeneratedGsa = TimeGenerated2, TimestampMde = TimeGenerated
| project-away Type, TenantId, TimeGenerated, TenantId1, Type1, DeviceId1, AadDeviceId
Stages and Predicates
Let binding: gsa_events
let gsa_events = NetworkAccessTraffic
| join kind=inner (
DeviceInfo
| distinct DeviceId, AadDeviceId
) on $left.DeviceId == $right.AadDeviceId
| project-away DeviceId
| project-rename DeviceId = DeviceId1;
Stage 1: source
DeviceNetworkEvents
Stage 2: extend
| extend HttpStatus = toint(todynamic(AdditionalFields).status_code),
BytesIn = toint(todynamic(AdditionalFields).response_body_len),
BytesOut = toint(todynamic(AdditionalFields).request_body_len),
HttpMethod = tostring(todynamic(AdditionalFields).method),
UrlHostname = tostring(todynamic(AdditionalFields).host),
UrlPath = tostring(todynamic(AdditionalFields).uri),
UserAgent = tostring(todynamic(AdditionalFields).user_agent),
HttpVersion = tostring(todynamic(AdditionalFields).version)
Stage 3: join
| join kind=inner gsa_events on
DeviceId,
$left.RemoteUrl == $right.DestinationFqdn,
$left.RemotePort == $right.DestinationPort,
$left.Protocol == $right.TransportProtocol,
$left.InitiatingProcessFileName == $right.InitiatingProcessName
Stage 4: project-rename
| project-rename TimeGeneratedGsa = TimeGenerated1, TimestampMde = Timestamp
Stage 5: project-away
| project-away Type, TenantId, TimeGenerated, TenantId1, Type1, DeviceId1, AadDeviceId
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
BytesIn | extend |
BytesOut | extend |
HttpMethod | extend |
HttpStatus | extend |
HttpVersion | extend |
UrlHostname | extend |
UrlPath | extend |
UserAgent | extend |
TimeGeneratedGsa | project-rename |
TimestampMde | project-rename |