LOGON GRAPH
EVENT 4624
FIRST-SEEN EDGE
FAN-OUT
DetectionBlue TeamSOCOctober 12, 2026•9 min read

Lateral Movement Graphing: Logon Graphs and First-Time Admin Pairs

Individual tools come and go. A graph of who logs on where does not change, and a new edge is a better signal than any single event.

Scroll

Every lateral movement technique ends in an authentication that connects a source to a destination as some account. Collected consistently, those events form a graph. Detection then asks graph questions: is this edge new, is this account new to this host, is this source suddenly reaching many nodes, and how far is the new node from tier 0?

Signals at a Glance

SignalWhereATT&CK
Edge (source host, destination host, account) never seen in the baseline windowEvent 4624 type 3, 10T1021
Privileged account logging on to a host outside its normal set (first-time admin pair)Event 4624 + 4672T1078.002
Source with unusual out-degree: many distinct destinations in one hourEvent 4624, Kerberos 4769T1021
New edge that moves into a higher tier (workstation to server to domain controller)Edge table + asset tiersT1021
Chain of new edges within a short time, each starting from the previous destinationEdge tableT1021

Building the Edge Table

Start with one normalised table: time, src_host, dst_host, account, logon_type, protocol. Source it from 4624 types 3 and 10 (the source address and workstation name), supplement with Kerberos 4769 on domain controllers, 5140 and 5145 for SMB, Sysmon 3 for process-level connections and, where you have it, Zeek conn and kerberos logs. Resolve IP addresses to hostnames through DHCP and your CMDB, because addresses churn. Drop computer accounts and anonymous logons into a separate edge type rather than discarding them.

// Sentinel (KQL) - normalised edge table for network and remote-interactive logons
let edges = SecurityEvent
| where EventID == 4624 and LogonType in (3, 10)
| where TargetUserName !endswith "$" and TargetUserName !in~ ("ANONYMOUS LOGON", "-")
| extend Src = replace_string(tostring(IpAddress), "::ffff:", "")
| where isnotempty(Src) and Src !in ("-", "::1", "127.0.0.1")
| project TimeGenerated, Src, Dst=Computer, Account=tolower(TargetUserName), LogonType;
edges
| summarize count=count(), first=min(TimeGenerated), last=max(TimeGenerated)
          by Src, Dst, Account, LogonType
// tune/validate: map Src to hostnames using a DHCP/CMDB join before baselining.

Baselining and Scoring Edges

With 14 to 30 days of edges as the baseline, the simplest high-value detection is the anti-join: a recent edge absent from the baseline. Rank new edges by account privilege and destination tier so the list stays short:

// Sentinel (KQL) - first-time (source, destination, account) pairs for privileged accounts
let baseline = SecurityEvent
| where TimeGenerated between (ago(30d) .. ago(1d))
| where EventID == 4624 and LogonType in (3, 10)
| extend Src = replace_string(tostring(IpAddress), "::ffff:", "")
| distinct Src, Dst=Computer, Account=tolower(TargetUserName);
let privileged = SecurityEvent
| where TimeGenerated > ago(30d) and EventID == 4672
| distinct Account=tolower(SubjectUserName);
SecurityEvent
| where TimeGenerated > ago(1d) and EventID == 4624 and LogonType in (3, 10)
| extend Src = replace_string(tostring(IpAddress), "::ffff:", ""), Account = tolower(TargetUserName)
| where Account in (privileged)
| join kind=leftanti baseline on Src, $left.Computer == $right.Dst, Account
| summarize newEdges=count(), dests=dcount(Computer) by Src, Account
| order by newEdges desc
// tune/validate: add a join to a tier list so edges into tier 0 rank first.
index=wineventlog EventCode=4624 (Logon_Type=3 OR Logon_Type=10)
| bin _time span=1h
| stats dc(ComputerName) as out_degree values(ComputerName) as dests by Source_Network_Address, Account_Name, _time
| eventstats avg(out_degree) as mu stdev(out_degree) as sigma by Account_Name
| eval z=if(sigma>0, (out_degree-mu)/sigma, 0)
| where out_degree >= 5 AND z >= 3
| sort - out_degree
# tune/validate: per-account statistics need history; exclude scanners and jump hosts from the baseline.
Vetted sources

Use Microsoft and vendor UEBA documentation, MITRE CAR analytics for lateral movement, Elastic and Splunk ESCU first-time-access content and Zeek conn and kerberos logs as inputs. Atomic Red Team lets you create known-bad edges to test the pipeline. Baseline your own environment before alerting — edge statistics are only as good as the quality of your host and account mapping.

Build Detections That Fire

Mjolnir Security helps blue teams turn threat intelligence into high-fidelity detections across endpoint, network, identity and cloud telemetry.

Detection EngineeringManaged Detection & ResponseThreat HuntingIncident ResponsePurple Teaming
  • Detection Engineering We build and tune detections mapped to ATT&CK from your own telemetry.
  • Threat Hunting as a Service Hypothesis-driven hunts for the behaviours on this page.
  • 24/7 Incident Response Containment when a detection fires. Call +1 833 403 5875.
Written by: Mjolnir Threat Research (MTAC)  |  Published: October 12, 2026  |  Last reviewed: October 12, 2026