Advanced join Techniques in KQL.
One table showed the process.
Another showed the connection.
Separately, neither proved enough.
Together, they revealed the attack.
Lesson overview
Learn how defenders use advanced join techniques in KQL to correlate Microsoft Defender XDR telemetry across process, network, file, logon and email tables.
Why advanced joins matter
The advanced join workflow
Step 1 — Join process activity to network activity
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let SuspiciousProcesses =
DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName in~ ("powershell.exe", "cmd.exe", "rundll32.exe", "mshta.exe")
| project ProcessTime=Timestamp, DeviceId, DeviceName, AccountUpn, FileName, ProcessCommandLine;
SuspiciousProcesses
| join kind=inner (
DeviceNetworkEvents
| where Timestamp > ago(24h)
| project NetworkTime=Timestamp, DeviceId, DeviceName, AccountUpn, RemoteUrl, RemoteIP, InitiatingProcessFileName
) on DeviceId, AccountUpn
| where NetworkTime between (ProcessTime .. ProcessTime + 10m)
| project ProcessTime, NetworkTime, DeviceName, AccountUpn, FileName, ProcessCommandLine, RemoteUrl, RemoteIP
| order by ProcessTime asc
Step 2 — Use leftouter to keep unmatched evidence
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName =~ "powershell.exe"
| project ProcessTime=Timestamp, DeviceId, DeviceName, AccountUpn, ProcessCommandLine
| join kind=leftouter (
DeviceNetworkEvents
| where Timestamp > ago(24h)
| project NetworkTime=Timestamp, DeviceId, RemoteUrl, RemoteIP
) on DeviceId
| where isnull(NetworkTime) or NetworkTime between (ProcessTime .. ProcessTime + 5m)
| project ProcessTime, NetworkTime, DeviceName, AccountUpn, ProcessCommandLine, RemoteUrl, RemoteIP
| order by ProcessTime asc
Step 3 — Use anti join to find missing evidence
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
let DevicesWithPowerShell =
DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName =~ "powershell.exe"
| distinct DeviceId, DeviceName;
DevicesWithPowerShell
| join kind=leftanti (
DeviceNetworkEvents
| where Timestamp > ago(7d)
| distinct DeviceId
) on DeviceId
| project DeviceName, DeviceId
Step 4 — Join file hashes across tables
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
DeviceFileEvents
| where Timestamp > ago(7d)
| where ActionType in ("FileCreated", "FileModified")
| where isnotempty(SHA256)
| project FileTime=Timestamp, DeviceId, DeviceName, FileName, FolderPath, SHA256
| join kind=inner (
DeviceProcessEvents
| where Timestamp > ago(7d)
| where isnotempty(SHA256)
| project ProcessTime=Timestamp, DeviceId, DeviceName, ProcessName=FileName, SHA256, ProcessCommandLine
) on DeviceId, SHA256
| where ProcessTime >= FileTime
| project FileTime, ProcessTime, DeviceName, FileName, FolderPath, ProcessName, SHA256, ProcessCommandLine
| order by ProcessTime asc
Step 5 — Join email delivery to URL clicks
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
EmailEvents
| where Timestamp > ago(7d)
| project EmailTime=Timestamp, NetworkMessageId, SenderFromAddress, RecipientEmailAddress, Subject, DeliveryAction
| join kind=inner (
UrlClickEvents
| where Timestamp > ago(7d)
| project ClickTime=Timestamp, NetworkMessageId, AccountUpn, Url, ActionType, IsClickedThrough
) on NetworkMessageId
| project EmailTime, ClickTime, SenderFromAddress, RecipientEmailAddress, Subject, Url, ActionType, IsClickedThrough
| order by ClickTime asc
Step 6 — Join identity activity to device activity
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
IdentityLogonEvents
| where Timestamp > ago(24h)
| where ActionType == "LogonSuccess"
| project LogonTime=Timestamp, AccountUpn, IPAddress, Application, DeviceName
| join kind=inner (
DeviceProcessEvents
| where Timestamp > ago(24h)
| project ProcessTime=Timestamp, AccountUpn, DeviceName, FileName, ProcessCommandLine
) on AccountUpn
| where ProcessTime between (LogonTime .. LogonTime + 30m)
| project LogonTime, ProcessTime, AccountUpn, DeviceName, IPAddress, Application, FileName, ProcessCommandLine
| order by LogonTime asc
How to choose the right join type
Real-world investigation
Investigation checklist
Related Agent Foskett Academy lessons
Advanced KQL milestone
Coming next
Final thought
Advanced join Techniques in KQL
Agent Foskett Academy Lesson 91 teaches defenders how to use advanced join techniques in KQL for Microsoft Defender XDR investigations.
KQL joins for Microsoft Defender XDR hunting
This lesson explains how inner joins, leftouter joins, anti joins, join keys, timestamp windows, DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents, EmailEvents, UrlClickEvents and IdentityLogonEvents help defenders correlate evidence during threat hunting and incident response.
