11.5 Sign-In Log Investigation

4-6 hours · Module 11

Sign-In Log Investigation

The email analysis (11.4) identified users who clicked the phishing URL. This subsection determines whether their credentials were compromised: did the AiTM proxy capture the session token? The answer is in the sign-in logs.

Required role: Microsoft Sentinel Reader or Global Reader for sign-in log queries.


Step 1: Examine the initial compromised user’s sign-in timeline

Start with j.morrison — the user from the original alert.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Complete sign-in timeline for the first compromised user
// around the time of the phishing click
SigninLogs
| where TimeGenerated between(datetime(2026-02-27T08:30:00Z) .. datetime(2026-02-27T10:00:00Z))
| where UserPrincipalName == "j.morrison@northgateeng.com"
| project
    TimeGenerated,
    IPAddress,
    Location = strcat(tostring(LocationDetails.countryOrRegion), " / ",
        tostring(LocationDetails.city)),
    AppDisplayName,
    ResourceDisplayName,
    ResultType,                          // 0 = success
    ResultDescription,
    AuthenticationRequirement,           // singleFactorAuthentication or multiFactorAuthentication
    MfaDetail = tostring(AuthenticationDetails),
    ConditionalAccessStatus,
    RiskLevelDuringSignIn,
    UserAgent = tostring(DeviceDetail.browser),
    DeviceId = tostring(DeviceDetail.deviceId),
    IsInteractive
| order by TimeGenerated asc

What to look for in the results — the AiTM signature:

Two sign-ins in rapid succession from different IPs. The legitimate sign-in (from the user’s device, through the AiTM proxy) appears first. Within seconds to minutes, a second sign-in from a different IP (the attacker’s infrastructure) appears. Both show MFA satisfied. This is the token replay.

In the Northgate Engineering scenario:

  • 08:52 — Sign-in from 192.0.2.10 (user’s office IP), MFA satisfied, result 0. This is the user authenticating through the AiTM proxy. The proxy forwarded the auth to Microsoft, Microsoft saw the request as coming from the user’s IP.
  • 08:54 — Sign-in from 203.0.113.47 (attacker IP), no MFA challenge, result 0, AuthenticationRequirement = “singleFactorAuthentication”. This is the token replay. The attacker used the captured session token — no MFA needed because the token already passed MFA.

The key differentiator: The second sign-in shows AuthenticationRequirement = "singleFactorAuthentication" or shows no MFA details despite the tenant requiring MFA. This is because the attacker is replaying a session token — the identity provider does not re-evaluate MFA for a valid token.


Step 2: Check non-interactive sign-ins for token replay

Interactive sign-ins show the initial token replay. Non-interactive sign-ins show ongoing use of the stolen token for API access (Graph API, EWS, PowerShell).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Non-interactive sign-ins  token-based access to M365 services
AADNonInteractiveUserSignInLogs
| where TimeGenerated between(datetime(2026-02-27T08:30:00Z) .. datetime(2026-02-27T12:00:00Z))
| where UserPrincipalName == "j.morrison@northgateeng.com"
| where IPAddress == "203.0.113.47"
| project
    TimeGenerated,
    IPAddress,
    AppDisplayName,
    ResourceDisplayName,
    ResultType,
    UserAgent = tostring(DeviceDetail.browser),
    TokenIssuerType,
    AuthenticationProcessingDetails
| order by TimeGenerated asc

What to look for: A series of non-interactive sign-ins from the attacker IP (203.0.113.47) accessing various M365 services — Outlook, SharePoint, Graph API. Each sign-in uses the stolen refresh token to obtain new access tokens for different resources. The UserAgent may differ from the user’s normal browser — attackers often use scripts or API tools that present a different user agent string.


Step 3: Identify all sign-ins from the attacker IP

The attacker IP may have been used to access other compromised accounts — not just j.morrison.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// All successful sign-ins from the attacker IP  any account
SigninLogs
| where TimeGenerated > datetime(2026-02-26T09:00:00Z)
| where IPAddress == "203.0.113.47"
| where ResultType == "0"
| summarize
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    SigninCount = count(),
    Apps = make_set(AppDisplayName, 10)
    by UserPrincipalName
| order by FirstSeen asc

Expected result: At this stage of the investigation (Wave 1), this may show only j.morrison. As the investigation expands (Waves 2-5), additional compromised accounts will appear from this IP or from different attacker IPs.

Investigation decision point: If this query returns multiple users from the same attacker IP, you have confirmed a campaign — not an isolated compromise. Escalate: notify management, consider org-wide password reset, and start the campaign tracking process (subsection 11.9).


Step 4: Confirm AiTM by correlating click time with sign-in time

The definitive AiTM confirmation: a phishing URL click followed by a sign-in from a different IP within minutes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Correlate: phishing click  sign-in from new IP within 30 minutes
let PhishingClickers = UrlClickEvents
| where TimeGenerated > datetime(2026-02-26T09:00:00Z)
| where Url has "secure-portal-verify.com"
| where ActionType == "ClickAllowed"
| project ClickTime = TimeGenerated, AccountUpn;
SigninLogs
| where TimeGenerated > datetime(2026-02-26T09:00:00Z)
| where ResultType == "0"
| where IPAddress !in ("192.0.2.10", "192.0.2.15")  // Exclude known corporate IPs
| join kind=inner PhishingClickers
    on $left.UserPrincipalName == $right.AccountUpn
| where TimeGenerated between(ClickTime .. (ClickTime + 30m))
| project
    AccountUpn,
    ClickTime,
    SigninTime = TimeGenerated,
    GapMinutes = datetime_diff('minute', TimeGenerated, ClickTime),
    AttackerIP = IPAddress,
    AppDisplayName,
    Location = tostring(LocationDetails.countryOrRegion)
| order by ClickTime asc

This is the core AiTM detection query. A gap of 1-5 minutes between URL click and sign-in from a new IP is the AiTM pattern. The user clicked the phishing URL, entered credentials on the proxy, the proxy captured the token, and the attacker replayed it within minutes.

Verify: Each result row represents a confirmed or highly probable compromise. Create a bookmark for each row. Tag: INC-2026-0227-001, signin-investigation, confirmed-compromise.


Step 5: Check for attacker IP infrastructure

Determine whether 203.0.113.47 is a known hosting provider, VPN, or residential proxy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Geolocation and ASN for the attacker IP
SigninLogs
| where TimeGenerated > datetime(2026-02-26T09:00:00Z)
| where IPAddress == "203.0.113.47"
| take 1
| project
    IPAddress,
    Country = tostring(LocationDetails.countryOrRegion),
    City = tostring(LocationDetails.city),
    ASN = AutonomousSystemNumber,
    ISP = tostring(NetworkLocationDetails)

Operational context: AiTM campaigns typically use VPS infrastructure (DigitalOcean, Vultr, OVH) or residential proxy networks. If the ASN belongs to a cloud hosting provider, this strongly confirms attacker infrastructure — legitimate users do not sign in from cloud hosting IPs. If the ASN belongs to a residential ISP, the attacker is using a residential proxy to evade IP-based detection — more sophisticated.

Subsection artifact: The five sign-in investigation queries above. These form the sign-in analysis section of your AiTM investigation playbook. The AiTM correlation query (Step 4) is also the basis for the AiTM detection analytics rule you will build in subsection 11.13.


Knowledge check

Check your understanding

1. You see two successful sign-ins for the same user within 2 minutes: one from IP 192.0.2.10 (known office IP) and one from IP 203.0.113.47 (unknown). Both show MFA satisfied. What is the most likely explanation?

AiTM token replay. The first sign-in (192.0.2.10) is the user authenticating through the AiTM proxy — the proxy forwarded the authentication to Microsoft, so Microsoft logged the user's real IP. The second sign-in (203.0.113.47) is the attacker replaying the captured session token from their own infrastructure. Both show MFA satisfied because the token already passed MFA during the first authentication. The 2-minute gap matches the AiTM proxy capture → replay cycle.
The user signed in from two devices simultaneously
A VPN IP change during the session
A false positive in the sign-in logs

2. The AADNonInteractiveUserSignInLogs shows the attacker IP accessing Outlook, SharePoint, and Graph API over a 3-hour period. What does this tell you?

The attacker has a valid refresh token and is using it to obtain new access tokens for different M365 services. The refresh token persists — it generates new access tokens for each service the attacker wants to access. Three hours of activity means the attacker is actively operating in the environment. Token revocation (subsection 11.7) must happen immediately — every minute of delay is a minute the attacker is reading email, accessing files, and potentially establishing persistence.
The user is working normally across multiple apps
Non-interactive sign-ins are always benign
The attacker only accessed Outlook