In this section
Multi-Environment Attack Triage: Building the Unified Timeline
The chain will not stay in your head
The pivots from the last sub worked: you carried the attacker IP, the account, the host across the layers and pulled out the connected evidence. Now you are holding three query results, the endpoint logons in one window, the audit events in another, the sign-ins in a third, and you are trying to keep the sequence straight in your head. It does not stay. The moment you ask a simple ordering question, did the endpoint foothold come before or after the app registration, you find you cannot answer it from three separate windows, because the answer depends on putting events from different sources in time order, and they are not in time order, they are in source order. The chain exists in your evidence but not in any form you can read.
The unified timeline is the fix and the central technique of cross-domain triage. It is the single artifact that takes every event your pivots surfaced, from every layer, and places it on one time axis, so the multi-stage attack becomes a sequence you read top to bottom rather than a set of fragments you hold in working memory. Once the timeline exists, the questions that were impossible from three windows become trivial: what happened first, what each event led to, where the gaps are. The pivot finds the connected evidence; the timeline makes it legible. Almost everything in the rest of this module, reading the sequence, scoping the blast radius, ordering the containment, is read off a timeline you built here.
One common shape for everything
The obstacle to a unified timeline is that every layer speaks a different language. The endpoint log calls its time field Timestamp and describes a logon; the audit log calls it TimeGenerated and describes an operation; the firewall has its own field and describes an action; the sign-in log has yet another. The schemas do not match, the field names do not match, and you cannot order events you cannot compare. So the first step is normalisation: reduce every event, whatever layer it came from, to a small common shape that lets you line them up. Four fields are enough for triage, when it happened, which layer it came from, which entity it involved, and what the action was. A logon on the seam host, an app registration in the tenant, a service-principal sign-in from the attacker IP, all collapse to the same four columns, and once they share a shape they can share an axis.
That normalisation is mostly mechanical, but two parts of it carry the weight. The timestamp must be normalised to a single reference, almost always UTC, because the whole point is ordering and you cannot order events recorded in different timezones until they are on one clock, a mistake here silently scrambles the sequence. And the action must be reduced to something readable, not the raw event code but a short human phrase, "foothold logon," "app registered," "backdoor sign-in," so the timeline reads as a narrative rather than a dump of event IDs. The other two fields, the layer and the entity, you carry through largely as they are, because they are what let you see which domains the attack crossed and which entity threads them. Four fields, one clock, plain-language actions: that is the shape that makes a timeline.
Disparate schemas reduce to four columns. The two that carry the weight are the UTC timestamp, so events can be ordered, and the plain-language action, so the timeline reads as a story.
The frictions that scramble a timeline
Three practical problems will corrupt a timeline if you do not watch for them, and all three are about time. The first is timezone: logs are recorded in different zones, a cloud audit trail in UTC, an on-prem device in local time, an appliance in whatever it was configured to, and if you place a local-time event next to a UTC event without converting, you put them in the wrong order and read a false sequence. Normalise everything to UTC before ordering, every time, without exception. The second is clock skew: even in one timezone, sources disagree about the current time by seconds or minutes because their clocks drift, so two events that appear ten seconds apart on the timeline may have been simultaneous, or even reversed. You handle skew by not over-reading very small gaps, treating events within the skew margin as effectively concurrent rather than strictly ordered.
The third is precision: sources record time at different granularity, some to the second, some to the minute, some with milliseconds, and an event logged only to the minute cannot be confidently ordered against one logged to the second within that same minute. You handle precision the way you handle skew, by not asserting an order finer than your worst source supports. None of these frictions is exotic, and none is a reason to distrust the timeline, they are reasons to build it carefully and to read its fine-grained ordering with appropriate caution. A timeline built on one clock, with small gaps read as approximate, is reliable for the thing that matters in triage: the sequence of stages across the layers, which plays out over minutes and hours, well above the noise floor of skew and precision.
All three corrupt fine-grained ordering, none corrupts the stage sequence if you convert to UTC and read small gaps as approximate. Build carefully, read fine ordering with caution.
The common mistake
Placing events from different sources on one timeline without normalising their timestamps to a single clock, and then reading the resulting false order as the attack sequence. It is the most damaging error in cross-domain triage because it is silent: the timeline looks authoritative, every event has a time, the order seems definite, and it is wrong, because a local-time endpoint event was laid next to a UTC cloud event and the hours do not mean the same thing.
An analyst then reasons from the false sequence, concludes the cloud backdoor preceded the endpoint foothold, and builds an entirely inverted picture of the attack, sometimes deciding the two are unrelated because the order makes no sense. The mirror mistake is over-reading precision the timeline cannot support, asserting that event A at 14:31:04 caused event B at 14:31:06 when the two sources have ten seconds of clock skew between them and may have been simultaneous.
The fix for both is discipline about time: convert every timestamp to UTC before ordering anything, and read gaps smaller than your sources' skew and precision as approximate rather than definite. The sequence of stages, foothold then pivot then backdoor, is what triage needs, and it holds up; the second-by-second ordering within a stage does not, and asserting it invents precision the evidence does not have.
Seeing it in the evidence
Three layers, one axis: the hybrid attack's evidence assembled into the ordered sequence that was invisible in three separate windows.
Where to find it
The timeline is assembled from each layer's own query, bounded to the incident window: the endpoint foothold from DeviceLogonEvents on the seam host, the pivot and backdoor registration from AuditLogs, the standing access from AADServicePrincipalSignInLogs. Each query returns that layer's events with their native timestamp; the unifying work is yours, converting each to UTC, reducing each to the common shape, and merging them in time order. Some SIEMs let you union normalised tables into one ordered result; the principle is the same whether the merge is a query or done by hand, every event on one clock, in one shape, in one sequence.
SIEM Console
Pull the seam-host logon, the tenant audit operations, and the service-principal sign-ins, each bounded to the window. Project each down to when, layer, entity, action, with the time in UTC. Merged and sorted by time, the three result sets become one sequence: foothold, then the cluster of cloud operations, then the backdoor sign-in. The console returns three tables; the timeline is what you make of them.
azure-aad-audit category="ApplicationManagement"
| table _time, action, user, object
AuditLogs
| where TimeGenerated between (datetime("2026-03-07T14:00:00Z") .. datetime("2026-03-07T15:00:00Z"))
| project TimeGenerated, OperationName, Identity
| order by TimeGenerated asc
# Graph PowerShell: the audit events in the window, time-ordered for the timeline
Get-MgAuditLogDirectoryAudit -Filter "activityDateTime ge 2026-03-07T14:00:00Z" |
Sort-Object activityDateTime | Select-Object activityDateTime, activityDisplayName
Read the output
Predict before running. This panel returns one layer of the timeline, the tenant audit operations in the incident window, in time order: the application added, the role assignment granted, the service principal created, the credentials updated, a tight cluster of changes over about two minutes. On its own this is the cloud middle of the attack.
Now place it between the other two layers you pulled separately. Before it, in the endpoint log, the foothold logon on the seam host at 14:14. After it, in the sign-in log, the backdoor service principal authenticating at 14:31. Assembled on one UTC axis, the full sequence reads top to bottom: foothold on the on-prem server, then the cluster of cloud operations that built the backdoor, then the backdoor signing in, seventeen minutes end to end across three layers.
That ordering was impossible to see in three separate windows and obvious on one timeline. Notice the cluster of audit events spans about two minutes, well above any clock-skew concern, so their order is trustworthy; you would not, by contrast, assert a strict order between two events a few seconds apart from different sources. Run the panel and read this layer as the middle of a sequence whose ends are in other tables.
Building a unified timeline
Reduce every layer's evidence to one shape on one clock, merge in time order, and read the sequence top to bottom. Build it carefully; read fine ordering with caution.
1. Normalise to a common shape
Reduce every event, from any layer, to four fields: when, layer, entity, action. The differing schemas and field names collapse to the same columns, and only then can events from different sources be compared and ordered.
2. One clock, plain-language actions
Convert every timestamp to UTC before ordering, no exceptions, or the sequence scrambles silently. Reduce each action to a short human phrase so the timeline reads as a narrative, not a dump of event codes.
3. Merge in time order, read the stages
Sorted on one axis, the layers become one sequence. The stage ordering, foothold then pivot then backdoor, plays out over minutes and hours and holds up; that is what triage needs from the timeline.
Do not over-read gaps below your skew and precision
Sources drift and record at different granularity. Treat events within the skew margin as effectively concurrent; do not assert that A caused B from a few seconds' difference between two clocks. The stage sequence is trustworthy; second-by-second ordering across sources is not.
The artifact the whole module is built to produce. Foothold, then the cloud cluster that built the backdoor, then the backdoor signing in, read top to bottom as one attack.
Your turn
You build a timeline merging endpoint events and cloud audit events for an incident. On it, a cloud app registration appears to happen forty minutes before the endpoint logon that supposedly led to it, which makes the attack sequence impossible. A colleague concludes the two are unrelated. What is the most likely explanation, and how do you check it?
Reveal
The most likely explanation is a timezone normalisation error, not an unrelated pair of events. An impossible ordering, an effect appearing before its cause, is the classic signature of timestamps that were not converted to a single clock before being placed on the timeline. If the endpoint device logs in local time and the cloud audit trail logs in UTC, and you laid them on one axis without converting, the hours do not mean the same thing, and an event that truly happened after another can appear before it by exactly the offset between the zones, forty minutes is suspiciously close to common timezone or half-hour-offset differences, which is a strong hint. The colleague's conclusion, that the two are unrelated because the order makes no sense, is the trap the anti-pattern warned about: reasoning from a false sequence to a false conclusion. The order making no sense is not evidence the events are unrelated; it is evidence the timeline is wrong. How you check it: confirm the timezone of each source. Pull the raw timestamps and verify what zone each is recorded in, the cloud audit trail is almost certainly UTC, the endpoint may be local; convert both to UTC and rebuild the timeline. Very often the impossible ordering resolves into a sensible one the moment both events are on the same clock, the endpoint logon now correctly precedes the app registration by the few minutes you would expect. You would also sanity-check for clock skew if the gap were small, but forty minutes is far too large for skew and points squarely at a timezone offset. Only if the ordering remains impossible after correct UTC normalisation would you start to doubt the connection, and even then you would suspect a different error before concluding two entity-linked, window-clustered events are coincidental. The lesson is the sub's core: convert every timestamp to one clock before ordering anything, and read an impossible sequence as a broken timeline, not an unrelated pair.
Where this leads: you have the timeline, every layer's evidence on one axis in the right order. The next sub reads it: interpreting the sequence for cause and effect across the layers, which event fed which, and the path the attack took through the environments it crossed.