SOCLIFE
SOCLIFE

Evidence-led security analysis

Published knowledge

Search SOC//LIFE

HuntsHUNT-003

Hypothesis-led threat hunting

Hunt for AiTM Exposure and Session Replay Across Identities

Traces campaign recipients and clickers into suspicious sessions, mailbox activity, source-scoped infrastructure, and follow-on phishing.

Question

Hunt goal

A SharePoint-style AiTM campaign may have expanded beyond the initially identified identity through additional recipients who clicked, replayed cloud sessions, mailbox concealment, and follow-on phishing from compromised trusted accounts.

Why this hunt

Microsoft's January 2026 reporting documented exactly this expansion pattern: a trusted compromised sender delivered the lure, the attacker later used compromised identities, created an Inbox rule, sent more than 600 phishing messages, and drew additional clickers into new AiTM flows.

Data sources

Where to look

  • EmailMail telemetry with message IDs, sender, recipient, subject, delivery, direction, and threat verdicts for recipient and follow-on sender scoping.
  • NetworkSafe Links URL-click activity with user, source IP, URL, redirect chain, action, message ID, and threat context.
  • AuthenticationEntra browser sign-ins with source, application, risk, device-trust, and session context.
  • SaaS AuditExchange Online and Microsoft 365 audit activity with identity, action, source, object, and rule details.

Search path

Hunt steps

Each search broadens the view from patient zero to related users, devices, infrastructure, and follow-on activity.
  1. Step 1First search

    Find all source-campaign recipients

    Finding

    Microsoft published the subject NEW PROPOSAL – NDA as a hunting value for the January campaign. The first search uses that source-scoped value to define the recipient population.

    Recipients become the bounded starting population; the subject is not treated as a universal AiTM indicator.

    View query
    Q-01First search

    Find all source-campaign recipients

    What this checks

    Identify every recipient of messages matching Microsoft's January 2026 hunting subject and preserve sender, delivery, and verdict context.

    KQL
    let lookback = 7d;
    EmailEvents
    | where Timestamp >= ago(lookback)
    | where Subject has "NEW PROPOSAL" and Subject has "NDA"
    | summarize
        FirstSeen=min(Timestamp),
        LastSeen=max(Timestamp),
        Messages=count(),
        UniqueRecipients=dcount(RecipientEmailAddress),
        Recipients=make_set(RecipientEmailAddress, 500),
        Senders=make_set(SenderFromAddress, 50),
        DeliveryActions=make_set(DeliveryAction, 20),
        ThreatVerdicts=make_set(ThreatTypes, 20)
        by Subject
    | order by LastSeen desc
    SPL
    index=<m365_email_index> sourcetype=<email_events_sourcetype>
    earliest=-7d
    | eval
        subject=coalesce(subject, Subject),
        sender=lower(coalesce(sender, SenderFromAddress, SenderMailFromAddress)),
        recipient=lower(coalesce(recipient, RecipientEmailAddress)),
        action=coalesce(action, LatestDeliveryAction, DeliveryAction),
        threat_types=coalesce(threat_types, ThreatTypes)
    | where like(subject, "%NEW PROPOSAL%") AND like(subject, "%NDA%")
    | stats
        min(_time) as first_seen
        max(_time) as last_seen
        count as messages
        dc(recipient) as unique_recipients
        values(recipient) as recipients
        values(sender) as senders
        values(action) as delivery_actions
        values(threat_types) as threat_verdicts
        by subject
    | sort - last_seen
    What to look for

    A recipient population that can be pivoted into URL-click telemetry.

    Technical details
    Tested signal

    Source-scoped campaign subject across mail telemetry.

    Assumptions
    • The published subject is used only for the cited January campaign.
    • Mail telemetry is retained for the review period.
    Data requirements and relevant fields
    email

    Mail events with sender, recipient, subject, delivery, and threat context.

    • Timestamp
    • SenderFromAddress
    • SenderMailFromAddress
    • RecipientEmailAddress
    • Subject
    • DeliveryAction
    • ThreatTypes
    KQL schema

    Uses documented EmailEvents fields and source-scoped subject matching.

    SPL schema

    Replace index/sourcetype and field aliases with the local message source.

    Limitations
    • Subject variants can miss related messages.
    • Legitimate messages can reuse generic words such as proposal or NDA; the full source context matters.

    Both variants scope a source-published hunting value; they do not present the subject as a universal AiTM signature.

  2. Step 2Pivot

    Find every clicker from the campaign messages

    Finding

    Microsoft reported that recipients who clicked were drawn into another AiTM flow. Message-to-click correlation identifies the users who require identity review.

    A click establishes exposure, while UrlChain and click source provide additional pivots without proving compromise.

    View query
    Q-02Pivot

    Find every clicker from the campaign messages

    What this checks

    Join source campaign messages to email-origin URL-click telemetry by NetworkMessageId and identify users who followed the links.

    KQL
    let lookback = 7d;
    let campaign_messages =
        EmailEvents
        | where Timestamp >= ago(lookback)
        | where Subject has "NEW PROPOSAL" and Subject has "NDA"
        | project
            NetworkMessageId,
            RecipientEmailAddress,
            SenderFromAddress,
            Subject;
    
    campaign_messages
    | join kind=inner (
        UrlClickEvents
        | where Timestamp >= ago(lookback)
        | where Workload == "Email"
        | project
            ClickTime=Timestamp,
            NetworkMessageId,
            AccountUpn,
            ClickIP=IPAddress,
            Url,
            UrlChain,
            ActionType,
            IsClickedThrough,
            ThreatTypes
    ) on NetworkMessageId
    | project
        ClickTime,
        AccountUpn,
        RecipientEmailAddress,
        SenderFromAddress,
        Subject,
        ClickIP,
        Url,
        UrlChain,
        ActionType,
        IsClickedThrough,
        ThreatTypes,
        NetworkMessageId
    | order by ClickTime asc
    SPL
    | multisearch
        [ search index=<m365_email_index> sourcetype=<email_events_sourcetype> earliest=-7d
          | eval stage="email"
        ]
        [ search index=<m365_security_index> sourcetype=<url_click_events_sourcetype> earliest=-7d
          | eval stage="click"
        ]
    | eval
        message_id=coalesce(NetworkMessageId, network_message_id, message_id),
        subject=if(stage="email", coalesce(Subject, subject), null()),
        recipient=if(stage="email", lower(coalesce(RecipientEmailAddress, recipient)), null()),
        sender=if(stage="email", lower(coalesce(SenderFromAddress, SenderMailFromAddress, sender)), null()),
        user=if(stage="click", lower(coalesce(AccountUpn, user)), null()),
        url=if(stage="click", coalesce(Url, url), null()),
        url_chain=if(stage="click", coalesce(UrlChain, url_chain), null()),
        click_src=if(stage="click", coalesce(IPAddress, src, click_src), null()),
        workload=if(stage="click", coalesce(Workload, workload), null()),
        click_action=if(stage="click", coalesce(ActionType, action), null()),
        click_threat_types=if(stage="click", coalesce(ThreatTypes, threat_types), null())
    | where isnotnull(message_id)
    | eventstats
        values(eval(if(stage="email", subject, null()))) as message_subjects
        values(eval(if(stage="email", recipient, null()))) as message_recipients
        values(eval(if(stage="email", sender, null()))) as message_senders
        by message_id
    | where stage="click" AND lower(workload)="email"
    | where like(mvjoin(message_subjects, " "), "%NEW PROPOSAL%")
        AND like(mvjoin(message_subjects, " "), "%NDA%")
    | table
        _time message_id user click_src url url_chain click_action
        click_threat_types message_senders message_recipients message_subjects
    | sort 0 _time
    What to look for

    A list of campaign clickers with source IP, clicked URL, redirect chain, and message context.

    Technical details
    Tested signal

    Campaign message ID reused in Safe Links click telemetry.

    Assumptions
    • NetworkMessageId is retained across EmailEvents and UrlClickEvents for the relevant messages.
    • Draft/Sent click scenarios may require a different local correlation path.
    Data requirements and relevant fields
    email

    Campaign messages keyed by NetworkMessageId.

    • Timestamp
    • NetworkMessageId
    • SenderFromAddress
    • RecipientEmailAddress
    • Subject
    network

    Email-origin click events keyed by NetworkMessageId with user, source, URL, and redirect chain.

    • Timestamp
    • NetworkMessageId
    • AccountUpn
    • IPAddress
    • Url
    • UrlChain
    • ActionType
    • IsClickedThrough
    • ThreatTypes
    • Workload
    KQL schema

    Uses EmailEvents plus UrlClickEvents. The SPL variant propagates campaign message context to clicks by message ID.

    SPL schema

    Replace both Microsoft 365 index/sourcetype placeholders and preserve NetworkMessageId.

    Limitations
    • A click is exposure evidence, not proof of session compromise.
    • Safe Links can rewrite or mediate URLs, and some click events lack complete redirect context.

    KQL uses a bounded join. SPL avoids a broad join by propagating message context with eventstats over normalized message IDs.

  3. Step 3Pivot

    Pivot clickers into post-click sign-ins

    Finding

    The hunt preserves every same-user successful browser sign-in within two hours of a campaign click and exposes source-change, risk, and device-trust context.

    This deliberately keeps lower-confidence candidates that DET-003 may suppress, because hunting should surface plausible delayed or weak-signal replay for analyst review.

    View query
    Q-03Pivot

    Pivot clickers into post-click sign-ins

    What this checks

    Review successful browser sign-ins for campaign clickers inside two hours and expose source-change, risk, and device-trust context without enforcing the stricter detection threshold.

    KQL
    let lookback = 7d;
    let hunt_window = 2h;
    
    let clicked_users =
        EmailEvents
        | where Timestamp >= ago(lookback)
        | where Subject has "NEW PROPOSAL" and Subject has "NDA"
        | project NetworkMessageId
        | join kind=inner (
            UrlClickEvents
            | where Timestamp >= ago(lookback)
            | where Workload == "Email"
            | project
                ClickTime=Timestamp,
                NetworkMessageId,
                AccountUpn=tolower(AccountUpn),
                ClickIP=IPAddress,
                Url
        ) on NetworkMessageId
        | project ClickTime, AccountUpn, ClickIP, Url;
    
    clicked_users
    | join kind=inner (
        EntraIdSignInEvents
        | where Timestamp >= ago(lookback)
        | where ErrorCode == 0
        | where ClientAppUsed == "Browser"
        | project
            SignInTime=Timestamp,
            AccountUpn=tolower(AccountUpn),
            SignInIP=IPAddress,
            Application,
            ResourceDisplayName,
            Country,
            State,
            City,
            Browser,
            UserAgent,
            DeviceName,
            DeviceTrustType,
            IsManaged,
            IsCompliant,
            RiskLevelDuringSignIn,
            RiskLevelAggregated,
            RiskEventTypes,
            RiskState,
            SessionId
    ) on AccountUpn
    | where SignInTime between (ClickTime .. ClickTime + hunt_window)
    | extend
        SourceChanged=isnotempty(ClickIP) and isnotempty(SignInIP) and ClickIP != SignInIP,
        RiskSignal=
            RiskLevelDuringSignIn in (10,50,100)
            or RiskLevelAggregated in (10,50,100)
            or RiskState in (4,5)
            or isnotempty(RiskEventTypes),
        DeviceTrustSignal=IsManaged == 0 or IsCompliant == 0
    | project
        ClickTime,
        SignInTime,
        AccountUpn,
        ClickIP,
        SignInIP,
        SourceChanged,
        RiskSignal,
        DeviceTrustSignal,
        Url,
        Application,
        ResourceDisplayName,
        Country,
        State,
        City,
        Browser,
        UserAgent,
        DeviceName,
        DeviceTrustType,
        IsManaged,
        IsCompliant,
        RiskLevelDuringSignIn,
        RiskLevelAggregated,
        RiskEventTypes,
        RiskState,
        SessionId
    | order by AccountUpn asc, SignInTime asc
    SPL
    | multisearch
        [ search index=<m365_email_index> sourcetype=<email_events_sourcetype> earliest=-7d
          | eval stage="email"
        ]
        [ search index=<m365_security_index> sourcetype=<url_click_events_sourcetype> earliest=-7d
          | eval stage="click"
        ]
        [ search index=<identity_index> sourcetype=<entra_signin_sourcetype> earliest=-7d
          | eval stage="signin"
        ]
    | eval
        message_id=coalesce(NetworkMessageId, network_message_id, message_id),
        user=lower(coalesce(AccountUpn, user, user_principal_name)),
        subject=if(stage="email", coalesce(Subject, subject), null()),
        workload=if(stage="click", coalesce(Workload, workload), null()),
        src=coalesce(IPAddress, src, ip_address),
        click_url=if(stage="click", coalesce(Url, url), null()),
        error_code=if(stage="signin", tonumber(coalesce(ErrorCode, error_code)), null()),
        auth_action=if(stage="signin", coalesce(action, auth_action), null()),
        app=if(stage="signin", coalesce(Application, ResourceDisplayName, app), null()),
        risk_during=if(stage="signin", tonumber(coalesce(RiskLevelDuringSignIn, risk_level_during)), null()),
        risk_aggregated=if(stage="signin", tonumber(coalesce(RiskLevelAggregated, risk_level_aggregated)), null()),
        risk_state=if(stage="signin", tonumber(coalesce(RiskState, risk_state)), null()),
        risk_events=if(stage="signin", coalesce(RiskEventTypes, risk_event_types), null()),
        is_managed=if(stage="signin", tonumber(coalesce(IsManaged, is_managed)), null()),
        is_compliant=if(stage="signin", tonumber(coalesce(IsCompliant, is_compliant)), null()),
        device_trust=if(stage="signin", coalesce(DeviceTrustType, device_trust_type), null())
    | eventstats
        values(eval(if(stage="email", subject, null()))) as message_subjects
        by message_id
    | eval campaign_click=if(
        stage="click"
        AND lower(workload)="email"
        AND like(mvjoin(message_subjects, " "), "%NEW PROPOSAL%")
        AND like(mvjoin(message_subjects, " "), "%NDA%"),
        1,0
    )
    | sort 0 user _time
    | streamstats current=f
        last(eval(if(campaign_click=1, _time, null()))) as click_time
        last(eval(if(campaign_click=1, src, null()))) as click_src
        last(eval(if(campaign_click=1, click_url, null()))) as click_url
        by user
    | where stage="signin"
        AND (auth_action="success" OR error_code=0)
        AND isnotnull(click_time)
        AND _time>=click_time
        AND _time<=click_time+7200
    | eval
        source_changed=if(isnotnull(click_src) AND isnotnull(src) AND click_src!=src,1,0),
        risk_signal=if(
            risk_during IN (10,50,100)
            OR risk_aggregated IN (10,50,100)
            OR risk_state IN (4,5)
            OR isnotnull(risk_events),
            1,0
        ),
        device_trust_signal=if(is_managed=0 OR is_compliant=0,1,0),
        delta_seconds=_time-click_time
    | table
        click_time _time delta_seconds user click_src src
        source_changed risk_signal device_trust_signal click_url
        app risk_during risk_aggregated risk_state risk_events
        is_managed is_compliant device_trust
    | sort 0 user _time
    What to look for

    Clickers with post-click sessions that warrant deeper review because source, risk, or device context is inconsistent.

    Technical details
    Tested signal

    Campaign click followed by same-user successful browser authentication.

    Assumptions
    • Email, click, and sign-in timestamps are comparable.
    • Expected VPN, secure web gateway, VDI, mobile, and roaming transitions are known to the analyst.
    Data requirements and relevant fields
    email

    Campaign message IDs used to identify the source click population.

    • Timestamp
    • NetworkMessageId
    • Subject
    network

    Email-origin click telemetry for campaign messages.

    • Timestamp
    • NetworkMessageId
    • AccountUpn
    • IPAddress
    • Url
    • Workload
    authentication

    Successful browser sign-ins for clickers with risk and device-trust context.

    • Timestamp
    • Application
    • ErrorCode
    • SessionId
    • AccountUpn
    • ResourceDisplayName
    • DeviceName
    • DeviceTrustType
    • IsManaged
    • IsCompliant
    • RiskLevelAggregated
    • RiskLevelDuringSignIn
    • RiskEventTypes
    • RiskState
    • UserAgent
    • ClientAppUsed
    • Browser
    • IPAddress
    • Country
    • State
    • City
    KQL schema

    Uses a two-hour post-click hunt window and exposes signals rather than filtering on a final score.

    SPL schema

    Replace three index/sourcetype placeholders. The SPL propagates campaign message context to clicks, then uses streamstats by normalized user for bounded post-click sign-ins.

    Limitations
    • This is a hunt and intentionally retains lower-confidence candidates that would not meet DET-003's threshold.
    • Delayed replay beyond two hours can still be missed.
    • Source change alone remains non-diagnostic.

    The hunt deliberately broadens beyond DET-003 while preserving same-user and bounded-time correlation.

  4. Step 4Pivot

    Search source-reported sign-in IPs across identities

    Finding

    Microsoft published 178[.]130[.]46[.]8 and 193[.]36[.]221[.]10 in the January campaign context. Searching those addresses across identity telemetry can identify additional affected accounts.

    The addresses are precise source pivots, but a negative result does not weaken the behavior hunt because AiTM infrastructure can rotate.

    View query
    Q-04Pivot

    Search source-reported sign-in IPs across identities

    What this checks

    Search the two Microsoft-published January campaign IP addresses across identity telemetry and identify every affected account.

    KQL
    let campaign_ips = dynamic([
        "178.130.46.8",
        "193.36.221.10"
    ]);
    EntraIdSignInEvents
    | where Timestamp >= ago(7d)
    | where IPAddress in (campaign_ips)
    | project
        Timestamp,
        AccountUpn,
        Application,
        ResourceDisplayName,
        IPAddress,
        Country,
        State,
        City,
        Browser,
        UserAgent,
        DeviceName,
        DeviceTrustType,
        IsManaged,
        IsCompliant,
        RiskLevelDuringSignIn,
        RiskLevelAggregated,
        RiskEventTypes,
        RiskState,
        SessionId
    | order by Timestamp asc
    SPL
    index=<identity_index> sourcetype=<entra_signin_sourcetype>
    earliest=-7d
    | eval
        user=lower(coalesce(user, AccountUpn, user_principal_name)),
        src=coalesce(src, IPAddress, ip_address),
        app=coalesce(app, Application, ResourceDisplayName),
        risk=coalesce(RiskEventTypes, risk_event_types)
    | where src IN ("178.130.46.8","193.36.221.10")
    | stats
        min(_time) as first_seen
        max(_time) as last_seen
        count as signins
        dc(user) as unique_users
        values(user) as users
        values(app) as applications
        values(risk) as risk_events
        by src
    | convert ctime(first_seen) ctime(last_seen)
    | sort - unique_users
    What to look for

    One or more identities using the source-reported infrastructure during the relevant review window.

    Technical details
    Tested signal

    Source-scoped campaign infrastructure in sign-in telemetry.

    Assumptions
    • The addresses are historical source-scoped indicators and may have changed ownership.
    • A match requires user, time, application, and risk context before escalation.
    Data requirements and relevant fields
    authentication

    Identity sign-ins with source IP, user, application, geography, risk, device, and session context.

    • Timestamp
    • AccountUpn
    • Application
    • ResourceDisplayName
    • IPAddress
    • Country
    • State
    • City
    • Browser
    • UserAgent
    • DeviceName
    • DeviceTrustType
    • IsManaged
    • IsCompliant
    • RiskLevelDuringSignIn
    • RiskLevelAggregated
    • RiskEventTypes
    • RiskState
    • SessionId
    KQL schema

    The KQL and SPL contain refanged source-backed IPs for defensive searching; the public indicator display remains defanged.

    SPL schema

    Replace the Entra index/sourcetype and map user/src/app/risk fields.

    Limitations
    • Historical IP reuse or shared infrastructure can produce unrelated matches.
    • Absence of these two IPs does not rule out AiTM because infrastructure rotates.

    This is precise source-scoped IOC expansion, not the complete hunt.

  5. Step 5Pivot

    Find suspicious mailbox-rule changes

    Finding

    The source campaign created an Inbox rule that deleted incoming messages and marked them as read.

    A suspicious rule aligned with the identity timeline raises confidence that session use progressed into mailbox control and concealment.

    View query
    Q-05Pivot

    Find suspicious mailbox-rule changes

    What this checks

    Review Exchange Online mailbox-rule operations for confirmed or suspected identities after the AiTM sequence.

    KQL
    let suspicious_users = dynamic([
        "denis@example.com"
    ]);
    CloudAppEvents
    | where Timestamp >= ago(7d)
    | where Application == "Microsoft Exchange Online"
    | where AccountId in~ (suspicious_users)
    | where ActionType in (
        "Set-Mailbox",
        "New-InboxRule",
        "Set-InboxRule",
        "UpdateInboxRules"
    )
    | project
        Timestamp,
        AccountId,
        AccountObjectId,
        ActionType,
        IPAddress,
        CountryCode,
        City,
        Isp,
        UserAgent,
        RuleConfig=RawEventData.Parameters,
        RawEventData
    | order by Timestamp asc
    SPL
    index=<m365_audit_index> sourcetype=<exchange_audit_sourcetype>
    earliest=-7d
    | eval
        user=lower(coalesce(user, UserId, AccountId, account_id)),
        operation=coalesce(Operation, action, ActionType),
        src=coalesce(ClientIP, src, src_ip, IPAddress),
        object=coalesce(ObjectId, ObjectName, object, object_id),
        parameters=coalesce(Parameters, parameters)
    | where user IN ("denis@example.com")
    | where in(operation,
        "New-InboxRule",
        "Set-InboxRule",
        "UpdateInboxRules",
        "Set-Mailbox"
    )
    | table _time user src operation object parameters
    | sort 0 _time
    What to look for

    Mailbox-rule activity that aligns with suspicious sign-in context and lacks an approved business explanation.

    Technical details
    Tested signal

    Mailbox-rule creation or modification after suspicious session use.

    Assumptions
    • The suspicious user list is populated from prior hunt steps.
    • CloudAppEvents or equivalent Microsoft 365 audit data is retained.
    Data requirements and relevant fields
    saas audit

    Exchange Online rule operations with identity, source, object, and rule details.

    • Timestamp
    • ActionType
    • Application
    • AccountObjectId
    • AccountId
    • IPAddress
    • CountryCode
    • City
    • Isp
    • UserAgent
    • RawEventData
    KQL schema

    Uses Microsoft-documented Exchange Online rule ActionType values.

    SPL schema

    Map Exchange audit operation, user, source, object, and parameters to local fields.

    Limitations
    • Legitimate users and administrators create mailbox rules.
    • AccountId can require object-ID mapping in some tenants.

    The example starts with denis@example.com; replace or expand the suspicious user list with identities returned by earlier hunt steps.

  6. Step 6Pivot

    Find follow-on phishing from compromised identities

    Finding

    Microsoft reported more than 600 follow-on phishing messages from a compromised identity and additional recipients entering new AiTM flows.

    The hunt loops: each compromised trusted sender creates a new recipient population that must be checked for clicks, sign-ins, and mailbox activity.

    View query
    Q-06Pivot

    Find follow-on phishing from compromised identities

    What this checks

    Measure outbound and intra-organization sending from confirmed compromised identities and expose the next recipient population.

    KQL
    let compromised_users = dynamic([
        "denis@example.com"
    ]);
    EmailEvents
    | where Timestamp >= ago(7d)
    | where SenderFromAddress in~ (compromised_users)
    | where EmailDirection in ("Outbound", "Intra-org")
    | summarize
        FirstSeen=min(Timestamp),
        LastSeen=max(Timestamp),
        MessageEvents=count(),
        UniqueRecipients=dcount(RecipientEmailAddress),
        Recipients=make_set(RecipientEmailAddress, 500),
        Subjects=make_set(Subject, 50),
        ThreatVerdicts=make_set(ThreatTypes, 20)
        by SenderFromAddress, EmailDirection
    | order by UniqueRecipients desc
    SPL
    | tstats summariesonly=t allow_old_summaries=t
        count
        min(_time) as first_seen
        max(_time) as last_seen
        values(Email.recipient) as recipients
        values(Email.subject) as subjects
        values(Email.action) as actions
        from datamodel=Email.All_Email
        where Email.sender="denis@example.com"
        by Email.sender
    | rename Email.sender as sender
    | eval unique_recipients=mvcount(recipients)
    | table first_seen last_seen sender unique_recipients recipients subjects actions count
    | sort - unique_recipients
    What to look for

    A recipient population and repeated lure subjects that can be fed back into the click and sign-in hunt.

    Technical details
    Tested signal

    Unexpected campaign-like sending from an identity already tied to suspicious session activity.

    Assumptions
    • The compromised user list is populated from the identity and mailbox hunt steps.
    • Mail direction and recipient telemetry is retained.
    Data requirements and relevant fields
    email

    Outbound and intra-organization message activity from suspected compromised senders.

    • Timestamp
    • SenderFromAddress
    • RecipientEmailAddress
    • Subject
    • EmailDirection
    • ThreatTypes
    KQL schema

    KQL uses EmailEvents. SPL uses Email CIM for portable sender-to-recipient expansion.

    SPL schema

    Validate local direction handling and recipient semantics in the Email CIM mapping.

    Limitations
    • Legitimate bulk or distribution-list sending can create high recipient counts.
    • The source-reported volume of more than 600 messages is not used as a universal threshold.

    This final step turns a compromised trusted identity into a new exposure population rather than ending the hunt at patient zero.

Blast radius

Wider-compromise pivots

  • Campaign message → every recipient
  • NetworkMessageId → every Safe Links click
  • Clicker → successful browser sign-ins after the click
  • Suspicious sign-in IP → every identity using that source
  • Compromised identity → Inbox-rule and mailbox changes
  • Compromised sender → every follow-on recipient
  • Follow-on recipient → repeat click and sign-in review
  • Shared redirect domain or URL chain → other users and messages where retained

Evidence threshold

What would increase confidence

  • Source-campaign message received.
  • Message URL clicked.
  • Successful post-click browser sign-in.
  • Source context changes without an expected VPN, proxy, mobile, or VDI explanation.
  • Entra risk or unmanaged/non-compliant device context appears.
  • A source-reported campaign IP appears during the relevant window.
  • Inbox-rule or concealment activity follows suspicious authentication.
  • Compromised identity sends unexpected campaign-like mail.
  • Additional recipients repeat the same click-to-session pattern.

Conclusion

Result and next action

The source reporting shows that AiTM compromise can propagate through trusted identities. A reusable hunt should move message → recipients → clickers → post-click sign-ins → source infrastructure → mailbox behavior → follow-on senders, while treating IP changes and campaign IOCs as context rather than universal proof.

  • Revoke sessions and tokens for confirmed identities.
  • Reset credentials after revocation.
  • Remove suspicious MFA changes and mailbox rules.
  • Purge confirmed follow-on phishing where supported.
  • Block source-scoped malicious infrastructure.
  • Identify and investigate every clicker.
  • Repeat the identity hunt for newly compromised accounts.
  • Preserve evidence required for BEC or fraud investigation.

The January 2026 Microsoft campaign shows why an AiTM hunt cannot stop at the first compromised user. A trusted identity became a new phishing sender, more than 600 messages were sent, and additional recipients who clicked were pulled into new AiTM flows.

This hunt therefore follows the propagation path rather than replaying the Case: identify recipients, find clickers, review post-click sessions, expand source-scoped infrastructure, check mailbox concealment, and feed follow-on recipients back into the same cycle.

The source establishes that this behavior occurred. The queries are a reviewed hunt plan and were not executed against a live SOC//LIFE or customer environment.

Context

ATT&CK and limits

Behavior mapping

MITRE ATT&CK

This mapping describes the valid-account behavior examined by the Hunt. It does not prove token theft, attribution, or technique-wide coverage.

Review boundary

Sources and limits

The conclusion stays bounded to the stated scope and available logs.