Attack Timeline
/api/v1/bec/timelineBuild a unified attack timeline by correlating sign-in logs, inbox rules, OAuth grants, mail access patterns, message deletions, and permission changes. Events are clustered into sessions, mapped to attack phases, and risk-scored to reconstruct the full BEC kill chain.
AuditLog.Read.All
MailboxSettings.Read
bec:read
10 per request
All plans
180 seconds
CLI Usage
# Build a timeline for a single user (last 30 days) dfir-cli bec timeline --user admin@contoso.com # Custom date range with all data sources dfir-cli bec timeline --user admin@contoso.com \ --since 2026-03-01 --until 2026-03-31 \ --include-sign-ins --include-rules --include-oauth \ --include-mail-access --include-deletions \ --include-search --include-permissions # Filter by IP and attack phase dfir-cli bec timeline --user admin@contoso.com \ --filter-ip 185.220.101.42 --filter-phase persistence # Summary-only view grouped by phase dfir-cli bec timeline --user admin@contoso.com \ --summary-only --group-by phase --min-risk 60 # Import from previously exported JSON dfir-cli bec timeline --import timeline-export.json
Flags
| Flag | Type | Description |
|---|---|---|
| --user | string | Target user email address (UPN) to build the timeline for |
| --days | int | Number of days to look back (default: 30) |
| --since | string | Start date for the timeline window (ISO 8601) |
| --until | string | End date for the timeline window (ISO 8601) |
| --include-sign-ins | bool | Include Azure AD sign-in log events |
| --include-rules | bool | Include inbox rule creation/modification events |
| --include-oauth | bool | Include OAuth consent grant events |
| --include-mail-access | bool | Include mailbox access and folder enumeration events |
| --include-deletions | bool | Include message deletion events |
| --include-search | bool | Include mailbox search/eDiscovery events |
| --include-permissions | bool | Include permission and role change events |
| --filter-ip | string | Only include events from this IP address |
| --filter-phase | string | Only include events in this attack phase |
| --min-risk | int | Minimum risk score to include (0-100, default: 0) |
| --group-by | string | Group events by "time" (default), "phase", or "session" |
| --no-correlate | bool | Skip session correlation and phase mapping |
| --summary-only | bool | Return only the summary without individual events |
| --import | string | Import and re-analyze a previously exported timeline JSON file |
Request Body
{
"user": "admin@contoso.com",
"days": 30,
"since": "2026-03-01T00:00:00Z",
"until": "2026-03-31T23:59:59Z",
"include_sign_ins": true,
"include_rules": true,
"include_oauth": true,
"include_mail_access": true,
"include_deletions": true,
"include_search": true,
"include_permissions": true,
"filter_ip": null,
"filter_phase": null,
"min_risk": 0,
"group_by": "time",
"no_correlate": false,
"summary_only": false
}| Field | Type | Required | Description |
|---|---|---|---|
| user | string | Yes | Target user UPN to build the attack timeline for |
| days | integer | No | Number of days to look back. Default: 30. Ignored if since/until are set. |
| since | string | No | Start of the timeline window (ISO 8601). Overrides days. |
| until | string | No | End of the timeline window (ISO 8601). Default: now. |
| include_sign_ins | boolean | No | Include sign-in log events. Default: true. |
| include_rules | boolean | No | Include inbox rule events. Default: true. |
| include_oauth | boolean | No | Include OAuth consent grant events. Default: true. |
| include_mail_access | boolean | No | Include mailbox access events. Default: true. |
| include_deletions | boolean | No | Include message deletion events. Default: true. |
| include_search | boolean | No | Include mailbox search events. Default: false. |
| include_permissions | boolean | No | Include permission/role change events. Default: false. |
| filter_ip | string | No | Only include events originating from this IP address. |
| filter_phase | string | No | Only include events mapped to this attack phase. |
| min_risk | integer | No | Minimum risk score threshold (0-100). Default: 0. |
| group_by | string | No | "time", "phase", or "session". Default: "time". |
| no_correlate | boolean | No | Skip session clustering and phase mapping. Default: false. |
| summary_only | boolean | No | Return only the summary object. Default: false. |
Response
{
"data": {
"events": [
{
"timestamp": "2026-04-08T18:16:58Z",
"source": "oauth_grant",
"operation": "app_consent",
"actor": "Microsoft App Access Panel",
"actor_ip": "",
"actor_agent": "",
"target": "0000000c-0000-0000-c000-000000000000",
"details": "OAuth app consented: Microsoft App Access Panel (AppID: 0000000c-...)",
"risk_level": "high",
"risk_score": 40,
"phase": "persistence",
"session_id": "",
"correlation_id": "9cf92b94-0873-4082-9010-dbd07b4814b0",
"location": "",
"confidence": 50,
"tags": ["oauth", "consent", "third_party_app"]
},
{
"timestamp": "2026-04-10T10:10:14Z",
"source": "sign_in",
"operation": "interactive_sign_in",
"actor": "admin@devopsdfirlab.onmicrosoft.com",
"actor_ip": "31.10.224.253",
"actor_agent": "Chrome 135.0",
"target": "",
"details": "Sign-in to Microsoft 365 via Chrome",
"risk_level": "low",
"risk_score": 0,
"phase": "info",
"session_id": "4a8b1c2d-...",
"correlation_id": "f9e8d7c6-...",
"location": "Glarus, CH",
"confidence": 50,
"tags": []
}
],
"correlations": [],
"sessions": [
{
"session_id": "session_0",
"ip": "unknown",
"location": "unknown",
"user_agent": "unknown",
"event_count": 168,
"phases": ["persistence", "info"],
"classification": "attacker_likely",
"risk_score": 60,
"first_seen": "2026-04-08T18:16:58Z",
"last_seen": "2026-04-10T13:23:32Z"
}
],
"summary": {
"first_compromise": "2026-04-08T18:16:58Z",
"persistence_mechanisms": ["oauth_consent", "Add service principal", "Update user"],
"emails_accessed": 0,
"emails_sent": 0,
"emails_deleted": 0,
"blast_radius": 183,
"attacker_ips": ["unknown", "2603:1026:2400::9", "2603:1026:2407::2b"],
"attacker_sessions": 4,
"phases_observed": ["persistence", "initial_access"],
"kill_chain_complete": false,
"total_events": 227,
"by_phase": { "persistence": 191, "initial_access": 15, "info": 21 },
"by_source": { "oauth_grant": 168, "sign_in": 15, "directory_audit": 44 },
"critical": 0,
"high": 171,
"medium": 23,
"low": 33
}
},
"meta": {
"request_id": "req_abc123",
"credits_used": 10,
"credits_remaining": 4923,
"processing_time_ms": 4520
}
}| Field | Type | Description |
|---|---|---|
| data.events | array | Chronologically ordered array of timeline events |
| data.events[].id | string | Unique event identifier |
| data.events[].timestamp | string | ISO 8601 timestamp of when the event occurred |
| data.events[].phase | string | Attack phase classification (see Attack Phases below) |
| data.events[].type | string | Event type (sign_in, mail_access, inbox_rule_created, smtp_forwarding, etc.) |
| data.events[].risk_score | number | Risk score from 0 to 100 for this individual event |
| data.events[].summary | string | Human-readable summary of what happened |
| data.events[].details | object | Event-specific details (IP, user agent, rule actions, etc.) |
| data.events[].session_id | string | Correlated session identifier linking related events |
| data.correlations | array | Session clusters grouping related events by IP and time proximity |
| data.sessions | object | Session summary: total count, attacker sessions, unique IPs |
| data.summary | object | Aggregate statistics: event counts by phase, risk distribution, time range |
| data.summary.attack_duration_hours | number | Total duration of the attack from first to last event in hours |
| meta.credits_used | number | Credits consumed by this request (10) |
| meta.credits_remaining | number | Remaining credit balance after this request |
Attack Phases
Each event is automatically classified into one of five BEC attack phases based on its type, timing, and context. The correlation engine uses session clustering and temporal proximity to map events to the kill chain.
| Phase | Description | Typical Events |
|---|---|---|
| initial_access | Attacker gains access to the mailbox | Anomalous sign-ins, MFA bypass, token theft |
| recon | Attacker explores the mailbox and organization | Bulk mail reads, folder enumeration, GAL access |
| persistence | Attacker establishes ongoing access or exfiltration | Inbox rules, SMTP forwarding, OAuth grants, delegate permissions |
| impersonation | Attacker sends emails as the compromised user | Outbound emails to internal targets, wire transfer requests |
| cleanup | Attacker covers tracks by deleting evidence | Bulk deletions, sent item removal, rule deletion |
Session Correlation
The correlation engine clusters events into sessions using IP address, user agent, and temporal proximity. Events from the same IP within a configurable time window are grouped into a single session, making it easy to distinguish attacker activity from legitimate user behavior.
Use --no-correlate to disable session clustering and receive raw, uncorrelated events. Use --group-by session to organize the output by session rather than chronologically.
Important Notes
- Requires an active Microsoft 365 connection. Run
dfir-cli bec statusto verify your session before building a timeline. - AuditLog.Read.All is required to access the unified audit log. Without it, only sign-in and mailbox settings data will be available.
- Microsoft 365 audit logs are retained for 90 days on standard plans and 1 year with E5/G5 licensing. The
--daysflag cannot exceed the tenant retention period. - Timeline construction queries multiple Microsoft Graph endpoints. For large date ranges (60+ days), expect processing times of 30-60 seconds. The 180-second timeout covers worst-case scenarios.
- Costs 10 credits per request. Use
--summary-onlyfor a quick overview before running a full timeline. - Use
--importto re-analyze a previously exported timeline offline without consuming additional credits or requiring an active M365 connection.