SOCLIFE
SOCLIFE

Evidence-led security analysis

Published knowledge

Search SOC//LIFE

DetectionsDET-007

Behavior-based detection engineering

SSPR Reset Followed by MFA Method Replacement and New Sign-In

Detects successful self-service password reset followed by authentication-method changes and a first-seen successful sign-in, then exposes Graph discovery and application-persistence pivots.

Behavior

What it detects

A successful self-service password reset is followed by authentication-method changes and a successful sign-in from a source not seen for the user in the recent baseline.

Engineering decision

Why this detection

SSPR by itself is expected. Registering a new authentication method can also be expected. A new source IP can be expected.

The useful detection surface is the sequence.

The primary analytic therefore requires a successful self-service reset, authentication-method change shortly afterward, and a successful sign-in from a source absent from the user's recent successful-sign-in baseline.

The drilldowns then ask whether the account behaved like Microsoft's Storm-2949 intrusion: broad Graph discovery or an attempt to create application/service-principal persistence.

Signal chain

Detection logic

  1. Collect successful self-service password-reset events.
  2. Collect successful authentication-method deletion, registration, update, and default-method changes.
  3. Correlate method changes to the same user within thirty minutes after the reset.
  4. Build a thirty-day per-user baseline of successful sign-in source IPs.
  5. Require a successful sign-in from a source absent from that baseline within two hours after the reset.
  6. Preserve risk, app/resource, device, user agent, session, and correlation context for triage.
  7. Use Graph and application-identity drilldowns only after the recovery sequence produces a candidate.

Primary analytic

Query

KQL and SPL express the same analytical intent using source-specific schemas.
Q-01Detection logic

SSPR reset followed by authentication-method change and new sign-in

What this checks

Generate a candidate when a successful self-service reset is followed by security-information changes and a successful sign-in from a source not seen for the user in the recent baseline.

KQL
let current_window = 1d;
let baseline_window = 30d;

let resets =
    AuditLogs
    | where TimeGenerated >= ago(current_window)
    | where LoggedByService =~ "Self-service Password Management"
    | where OperationName =~ "Reset password (self-service)"
    | where Result =~ "success"
    | extend
        TargetUser = tolower(tostring(TargetResources[0].userPrincipalName)),
        TargetUserId = tostring(TargetResources[0].id),
        ResetCorrelationId = CorrelationId
    | where isnotempty(TargetUser)
    | project ResetTime=TimeGenerated, TargetUser, TargetUserId, ResetCorrelationId;

let auth_changes =
    AuditLogs
    | where TimeGenerated >= ago(current_window)
    | where Result =~ "success"
    | where OperationName in~ (
        "User deleted security information",
        "User registered security info",
        "Delete user authentication method",
        "Add user authentication method",
        "Update user authentication methods",
        "User changed default security information"
    )
    | extend
        TargetUser = tolower(tostring(TargetResources[0].userPrincipalName)),
        InitiatingIP = tostring(InitiatedBy.user.ipAddress)
    | project
        AuthChangeTime=TimeGenerated,
        TargetUser,
        AuthChangeOperation=OperationName,
        InitiatingIP,
        AuthChangeCorrelationId=CorrelationId;

let historical_sources =
    SigninLogs
    | where TimeGenerated between (ago(baseline_window) .. ago(current_window))
    | where tostring(ResultType) == "0"
    | extend TargetUser=tolower(UserPrincipalName)
    | summarize by TargetUser, IPAddress;

let post_reset_signins =
    SigninLogs
    | where TimeGenerated >= ago(current_window)
    | where tostring(ResultType) == "0"
    | extend TargetUser=tolower(UserPrincipalName)
    | join kind=leftanti historical_sources on TargetUser, IPAddress
    | project
        SigninTime=TimeGenerated,
        TargetUser,
        IPAddress,
        AppDisplayName,
        AppId,
        ResourceDisplayName,
        AuthenticationRequirement,
        RiskLevelDuringSignIn,
        RiskLevelAggregated,
        RiskState,
        ConditionalAccessStatus,
        DeviceDetail,
        UserAgent,
        SigninCorrelationId=CorrelationId,
        SessionId;

resets
| join kind=inner auth_changes on TargetUser
| where AuthChangeTime between (ResetTime .. ResetTime + 30m)
| summarize
    AuthChangeOperations=make_set(AuthChangeOperation, 20),
    AuthChangeCount=count(),
    FirstAuthChange=min(AuthChangeTime),
    LastAuthChange=max(AuthChangeTime),
    AuthChangeIPs=make_set(InitiatingIP, 20),
    AuthCorrelations=make_set(AuthChangeCorrelationId, 20)
    by ResetTime, TargetUser, TargetUserId, ResetCorrelationId
| where AuthChangeCount >= 1
| join kind=inner post_reset_signins on TargetUser
| where SigninTime between (ResetTime .. ResetTime + 2h)
| project
    ResetTime,
    FirstAuthChange,
    LastAuthChange,
    SigninTime,
    TargetUser,
    TargetUserId,
    AuthChangeCount,
    AuthChangeOperations,
    AuthChangeIPs,
    IPAddress,
    AppDisplayName,
    ResourceDisplayName,
    RiskLevelDuringSignIn,
    RiskLevelAggregated,
    RiskState,
    ConditionalAccessStatus,
    DeviceDetail,
    UserAgent,
    SessionId,
    ResetCorrelationId,
    SigninCorrelationId
| order by SigninTime desc
SPL
(
    index=<entra_audit_index> sourcetype=<entra_audit_sourcetype> earliest=-1d
)
OR
(
    index=<entra_signin_index> sourcetype=<entra_signin_sourcetype> earliest=-30d
)
| eval
    user=lower(coalesce(user, UserPrincipalName, TargetUserPrincipalName, user_principal_name)),
    operation=coalesce(operation, OperationName, ActivityDisplayName),
    src=coalesce(src, IPAddress, InitiatingIpAddress, ipAddress),
    result=lower(coalesce(result, Result, ResultType)),
    correlation_id=coalesce(correlation_id, CorrelationId),
    event_type=case(
        operation="Reset password (self-service)" AND result="success","reset",
        operation IN (
            "User deleted security information",
            "User registered security info",
            "Delete user authentication method",
            "Add user authentication method",
            "Update user authentication methods",
            "User changed default security information"
        ) AND result="success","auth_change",
        tostring(result)="0","signin",
        true(),"other"
    ),
    is_current=if(_time>=relative_time(now(),"-1d"),1,0)
| eventstats
    values(eval(if(event_type="signin" AND is_current=0,src,null()))) as historical_signin_ips
    by user
| sort 0 user _time
| streamstats current=f
    last(eval(if(event_type="reset" AND is_current=1,_time,null()))) as reset_time
    last(eval(if(event_type="auth_change" AND is_current=1,_time,null()))) as auth_change_time
    last(eval(if(event_type="auth_change" AND is_current=1,operation,null()))) as auth_change_operation
    by user
| where
    event_type="signin"
    AND is_current=1
    AND isnotnull(reset_time)
    AND isnotnull(auth_change_time)
    AND auth_change_time>=reset_time
    AND auth_change_time<=reset_time+1800
    AND _time>=reset_time
    AND _time<=reset_time+7200
    AND mvfind(historical_signin_ips,src)<0
| table
    _time reset_time auth_change_time user src auth_change_operation
    AppDisplayName ResourceDisplayName RiskLevelDuringSignIn RiskState
    DeviceDetail UserAgent SessionId correlation_id
| sort - _time

What to look for

A compact sequence that is unusual for normal end-user recovery and closely resembles the source-reported SSPR takeover path.

Technical details

Tested signal

SSPR success + authentication-method churn within thirty minutes + first-seen successful sign-in within two hours.

Assumptions

  • AuditLogs and SigninLogs can be joined by normalized user principal name.
  • Thirty days is a useful starting baseline for successful sign-in source IPs.
  • Authentication-method audit event names are normalized to the documented Entra activity names.

Data requirements and relevant fields

identity

Microsoft Entra audit events for self-service password reset and authentication-method changes.

  • TimeGenerated
  • LoggedByService
  • Category
  • OperationName
  • Result
  • ResultReason
  • InitiatedBy
  • TargetResources
  • AdditionalDetails
  • CorrelationId
authentication

Microsoft Entra sign-in events with source, app/resource, device, risk, and session/correlation context.

  • TimeGenerated
  • UserPrincipalName
  • UserId
  • IPAddress
  • AppDisplayName
  • AppId
  • ResourceDisplayName
  • ResultType
  • ResultDescription
  • ConditionalAccessStatus
  • AuthenticationRequirement
  • RiskLevelDuringSignIn
  • RiskLevelAggregated
  • RiskState
  • DeviceDetail
  • UserAgent
  • AutonomousSystemNumber
  • CorrelationId
  • SessionId
KQL schema

Validate connector availability, nested-field shape, retention, and licensing before operational use.

SPL schema

Replace index/sourcetype placeholders and map the documented identity, Graph, and Azure control-plane concepts to local fields.

Limitations

  • Legitimate lost-device recovery while traveling can create the same three-stage sequence.
  • Source-IP novelty should be tuned around VPN, SWG, mobile carrier, VDI, and known recovery locations.
  • The rule does not assume that every SSPR reset or MFA registration is malicious.

The analytic correlates Entra audit and sign-in data. Keep the three signals separate during local mapping so normal SSPR volume, authentication-method registration, and network baselines can be tuned independently.

Analyst workflow

What the analyst should look for

  • Did the user intentionally initiate SSPR?
  • Was the reset performed under an approved helpdesk or identity-recovery workflow?
  • Which authentication methods were deleted, added, or made default after the reset?
  • Does the new sign-in source belong to an expected VPN, SWG, VDI, mobile, or travel scenario?
  • Is the new device or user agent known to the user?
  • Did the candidate identity enumerate users, applications, service principals, or roles through Microsoft Graph?
  • Did the user attempt to add credentials to an application or service principal?
  • Do Azure control-plane actions show expansion into high-value resources?

Expected result

A compact sequence that is unusual for normal end-user recovery and closely resembles the source-reported SSPR takeover path.

Investigation pivots

Drilldowns

Use the candidate context to reconstruct what executed, what changed, and what communicated next.
View query — Reconstruct the password-recovery flow
Q-02Drilldown

Reconstruct the password-recovery flow

What this checks

Review the complete SSPR flow for the candidate user rather than only the final successful reset.

KQL
let target_user = "<candidate_user>";
AuditLogs
| where TimeGenerated >= ago(7d)
| where LoggedByService =~ "Self-service Password Management"
| where OperationName in~ (
    "Reset password (self-service)",
    "Self-service password reset flow activity progress",
    "Blocked from self-service password reset"
)
| extend
    InitiatingUser = tolower(tostring(InitiatedBy.user.userPrincipalName)),
    TargetUser = tolower(tostring(TargetResources[0].userPrincipalName)),
    TargetId = tostring(TargetResources[0].id)
| where InitiatingUser == tolower(target_user) or TargetUser == tolower(target_user)
| project
    TimeGenerated,
    OperationName,
    Result,
    ResultReason,
    InitiatingUser,
    TargetUser,
    TargetId,
    AdditionalDetails,
    CorrelationId
| order by TimeGenerated asc
SPL
index=<entra_audit_index> sourcetype=<entra_audit_sourcetype>
earliest=-7d
(
    OperationName="Reset password (self-service)"
    OR OperationName="Self-service password reset flow activity progress"
    OR OperationName="Blocked from self-service password reset"
)
| eval
    user=lower(coalesce(user, userPrincipalName, InitiatingUserPrincipalName, TargetUserPrincipalName)),
    operation=coalesce(operation, OperationName, ActivityDisplayName),
    result=lower(coalesce(result, Result)),
    correlation_id=coalesce(correlation_id, CorrelationId)
| where user="<candidate_user>"
| fields _time user operation result ResultReason correlation_id AdditionalDetails
| sort 0 _time

What to look for

The sequence of reset-flow events and the final successful reset for the same user.

Technical details

Tested signal

Reset flow progress, blocks, and successful self-service reset for the candidate identity.

Assumptions

  • Replace the candidate user from Q-01.

Data requirements and relevant fields

identity

Microsoft Entra audit events for self-service password reset and authentication-method changes.

  • TimeGenerated
  • LoggedByService
  • Category
  • OperationName
  • Result
  • ResultReason
  • InitiatedBy
  • TargetResources
  • AdditionalDetails
  • CorrelationId
KQL schema

Validate connector availability, nested-field shape, retention, and licensing before operational use.

SPL schema

Replace index/sourcetype placeholders and map the documented identity, Graph, and Azure control-plane concepts to local fields.

Limitations

  • SSPR flow details can vary with tenant policy and available recovery methods.

KQL uses Microsoft Entra / Azure Monitor telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.

View query — Review authentication-method replacement
Q-03Drilldown

Review authentication-method replacement

What this checks

List security-information deletion, registration, and default-method changes around the candidate reset.

KQL
let target_user = "<candidate_user>";
AuditLogs
| where TimeGenerated >= ago(7d)
| where OperationName in~ (
    "User deleted security information",
    "User registered security info",
    "Delete user authentication method",
    "Add user authentication method",
    "Update user authentication methods",
    "User changed default security information"
)
| extend
    InitiatingUser = tolower(tostring(InitiatedBy.user.userPrincipalName)),
    TargetUser = tolower(tostring(TargetResources[0].userPrincipalName)),
    TargetId = tostring(TargetResources[0].id),
    InitiatingIP = tostring(InitiatedBy.user.ipAddress)
| where InitiatingUser == tolower(target_user) or TargetUser == tolower(target_user)
| project
    TimeGenerated,
    OperationName,
    Result,
    ResultReason,
    InitiatingUser,
    TargetUser,
    TargetId,
    InitiatingIP,
    TargetResources,
    AdditionalDetails,
    CorrelationId
| order by TimeGenerated asc
SPL
index=<entra_audit_index> sourcetype=<entra_audit_sourcetype>
earliest=-7d
(
    OperationName="User deleted security information"
    OR OperationName="User registered security info"
    OR OperationName="Delete user authentication method"
    OR OperationName="Add user authentication method"
    OR OperationName="Update user authentication methods"
    OR OperationName="User changed default security information"
)
| eval
    user=lower(coalesce(user, userPrincipalName, InitiatingUserPrincipalName, TargetUserPrincipalName)),
    operation=coalesce(operation, OperationName, ActivityDisplayName),
    src=coalesce(src, InitiatingIpAddress, ipAddress),
    result=lower(coalesce(result, Result)),
    correlation_id=coalesce(correlation_id, CorrelationId)
| where user="<candidate_user>"
| fields _time user src operation result ResultReason correlation_id TargetResources AdditionalDetails
| sort 0 _time

What to look for

Security methods deleted or added in the reset window, with the initiating IP and correlation context preserved.

Technical details

Tested signal

Authentication-method churn for the candidate identity.

Assumptions

  • Replace the candidate user from Q-01.

Data requirements and relevant fields

identity

Microsoft Entra audit events for self-service password reset and authentication-method changes.

  • TimeGenerated
  • LoggedByService
  • Category
  • OperationName
  • Result
  • ResultReason
  • InitiatedBy
  • TargetResources
  • AdditionalDetails
  • CorrelationId
KQL schema

Validate connector availability, nested-field shape, retention, and licensing before operational use.

SPL schema

Replace index/sourcetype placeholders and map the documented identity, Graph, and Azure control-plane concepts to local fields.

Limitations

  • A legitimate lost-device or authenticator migration can create similar events.

KQL uses Microsoft Entra / Azure Monitor telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.

View query — Measure Graph enumeration after takeover
Q-04Drilldown

Measure Graph enumeration after takeover

What this checks

Check whether the candidate identity rapidly enumerated directory and application objects after the suspicious recovery sequence.

KQL
let target_user_id = "<candidate_user_object_id>";
MicrosoftGraphActivityLogs
| where TimeGenerated >= ago(7d)
| where UserId == target_user_id
| where RequestMethod =~ "GET"
| where ResponseStatusCode between (200 .. 299)
| where RequestUri has_any (
    "/users",
    "/applications",
    "/servicePrincipals",
    "/directoryRoles",
    "/roleManagement/directory"
)
| extend EndpointFamily = case(
    RequestUri has "/servicePrincipals", "servicePrincipals",
    RequestUri has "/applications", "applications",
    RequestUri has "/roleManagement/directory", "roleManagement",
    RequestUri has "/directoryRoles", "directoryRoles",
    RequestUri has "/users", "users",
    "other"
)
| summarize
    FirstSeen=min(TimeGenerated),
    LastSeen=max(TimeGenerated),
    Requests=count(),
    EndpointFamilies=dcount(EndpointFamily),
    Families=make_set(EndpointFamily, 10),
    SampleUris=make_set(RequestUri, 40),
    StatusCodes=make_set(ResponseStatusCode, 10)
    by UserId, IPAddress, AppId, SessionId, UserAgent
| where Requests >= 20 and EndpointFamilies >= 3
| order by Requests desc
SPL
index=<graph_activity_index> sourcetype=<microsoft_graph_activity_sourcetype>
earliest=-7d
| eval
    user_id=coalesce(user_id, UserId),
    src=coalesce(src, IPAddress),
    method=upper(coalesce(method, RequestMethod)),
    uri=coalesce(uri, RequestUri),
    status=coalesce(status, ResponseStatusCode),
    app_id=coalesce(app_id, AppId),
    session_id=coalesce(session_id, SessionId),
    user_agent=coalesce(user_agent, UserAgent)
| where user_id="<candidate_user_object_id>" AND method="GET" AND status>=200 AND status<300
| eval endpoint_family=case(
    like(uri,"%/servicePrincipals%"),"servicePrincipals",
    like(uri,"%/applications%"),"applications",
    like(uri,"%/roleManagement/directory%"),"roleManagement",
    like(uri,"%/directoryRoles%"),"directoryRoles",
    like(uri,"%/users%"),"users",
    true(),"other"
)
| where endpoint_family!="other"
| stats
    min(_time) as first_seen
    max(_time) as last_seen
    count as requests
    dc(endpoint_family) as endpoint_families
    values(endpoint_family) as families
    values(uri) as sample_uris
    by user_id src app_id session_id user_agent
| where requests>=20 AND endpoint_families>=3
| convert ctime(first_seen) ctime(last_seen)
| sort - requests

What to look for

Directory enumeration from the candidate user/session that is inconsistent with normal role or automation behavior.

Technical details

Tested signal

Successful Microsoft Graph GET burst across several directory-discovery endpoint families.

Assumptions

  • Microsoft Graph Activity Logs are enabled.
  • The candidate's Entra user object ID is known.

Data requirements and relevant fields

cloud control plane

Microsoft Graph Activity Logs with caller identity, request URI/method, response status, source IP, app, session, and token context.

  • TimeGenerated
  • AppId
  • ClientAuthMethod
  • DeviceId
  • IPAddress
  • OperationId
  • RequestId
  • RequestMethod
  • RequestUri
  • ResponseSizeBytes
  • ResponseStatusCode
  • Roles
  • Scopes
  • ServicePrincipalId
  • SessionId
  • SignInActivityId
  • TokenIssuedAt
  • UniqueTokenId
  • UserAgent
  • UserId
  • Wids
KQL schema

Validate connector availability, nested-field shape, retention, and licensing before operational use.

SPL schema

Replace index/sourcetype placeholders and map the documented identity, Graph, and Azure control-plane concepts to local fields.

Limitations

  • IAM automation and legitimate admin tools can perform similar enumeration.

KQL uses Microsoft Entra / Azure Monitor telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.

View query — Check application and service-principal persistence attempts
Q-05Drilldown

Check application and service-principal persistence attempts

What this checks

Find credential-management activity against applications/service principals initiated by the candidate identity.

KQL
let target_user = "<candidate_user>";
AuditLogs
| where TimeGenerated >= ago(7d)
| where Category =~ "ApplicationManagement"
| where OperationName has_any (
    "Add service principal",
    "Certificates and secrets management"
)
| extend
    InitiatingUser = tolower(tostring(InitiatedBy.user.userPrincipalName)),
    InitiatingIP = tostring(InitiatedBy.user.ipAddress)
| where InitiatingUser == tolower(target_user)
| mv-apply TargetResource = TargetResources on (
    where TargetResource.type in~ ("Application", "ServicePrincipal")
    | extend
        TargetName = tostring(TargetResource.displayName),
        TargetId = tostring(TargetResource.id),
        TargetType = tostring(TargetResource.type),
        ModifiedProperties = TargetResource.modifiedProperties
)
| project
    TimeGenerated,
    OperationName,
    Result,
    ResultReason,
    InitiatingUser,
    InitiatingIP,
    TargetName,
    TargetId,
    TargetType,
    ModifiedProperties,
    CorrelationId
| order by TimeGenerated asc
SPL
index=<entra_audit_index> sourcetype=<entra_audit_sourcetype>
earliest=-7d
(
    OperationName="Add service principal credentials"
    OR OperationName="Add service principal"
    OR OperationName="Update application - Certificates and secrets management"
    OR OperationName="Certificates and secrets management"
)
| eval
    user=lower(coalesce(user, InitiatingUserPrincipalName, userPrincipalName)),
    src=coalesce(src, InitiatingIpAddress, ipAddress),
    operation=coalesce(operation, OperationName, ActivityDisplayName),
    result=lower(coalesce(result, Result)),
    target_name=coalesce(target_name, TargetResourceDisplayName),
    target_id=coalesce(target_id, TargetResourceId),
    target_type=coalesce(target_type, TargetResourceType),
    correlation_id=coalesce(correlation_id, CorrelationId)
| where user="<candidate_user>"
| fields _time user src operation result ResultReason target_name target_id target_type correlation_id ModifiedProperties
| sort 0 _time

What to look for

Successful or failed credential-management actions against application identities.

Technical details

Tested signal

Application or service-principal credential changes after SSPR takeover.

Assumptions

  • The candidate user from Q-01 is known.

Data requirements and relevant fields

identity

Microsoft Entra audit events for self-service password reset and authentication-method changes.

  • TimeGenerated
  • LoggedByService
  • Category
  • OperationName
  • Result
  • ResultReason
  • InitiatedBy
  • TargetResources
  • AdditionalDetails
  • CorrelationId
KQL schema

Validate connector availability, nested-field shape, retention, and licensing before operational use.

SPL schema

Replace index/sourcetype placeholders and map the documented identity, Graph, and Azure control-plane concepts to local fields.

Limitations

  • Legitimate app owners and administrators rotate secrets/certificates.

KQL uses Microsoft Entra / Azure Monitor telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.

Legitimate resemblance

What the analyst should confirm

Similar activity can be legitimate. Confirm the approved purpose and expected context before escalating.
  • A user legitimately resets a forgotten password, replaces a lost phone, and signs in from a new travel or home network.

    The user confirms the recovery, the new MFA device is expected, the source aligns with travel/VPN context, and no unusual Graph or cloud-control activity follows.
  • IT performs a documented identity-recovery workflow for a user who lost all registered authentication methods.

    The helpdesk ticket, named operator, recovery timestamps, trusted source locations, replacement device, and final user confirmation align.
  • A privileged cloud engineer recovers an account and then runs normal Graph inventory/admin tooling.

    The Graph app ID, user agent, endpoint families, source network, automation ownership, and change record match the approved workflow.

Confirmed match

Action after a confirmed match

  • Contain the compromised identity and revoke sessions/tokens.
  • Reset the password through a trusted recovery channel.
  • Remove attacker-controlled authentication methods and re-register trusted methods.
  • Review application and service-principal credentials added or modified by the identity.
  • Scope Microsoft Graph directory discovery and all high-value Azure operations after takeover.
  • Rotate exposed secrets, publishing credentials, storage keys, or database credentials.
  • Require phishing-resistant MFA for privileged users and harden SSPR/helpdesk recovery.

Threat hunt

Could this be happening elsewhere?

Hunt for this behavior across the environment.
View threat hunt

Technical boundary

Telemetry and limitations

Identity

Microsoft Entra audit events for self-service password reset and authentication-method changes.

Required fields
  • TimeGenerated
  • LoggedByService
  • Category
  • OperationName
  • Result
  • ResultReason
  • InitiatedBy
  • TargetResources
  • AdditionalDetails
  • CorrelationId
Authentication

Microsoft Entra sign-in events with source, app/resource, device, risk, and session/correlation context.

Required fields
  • TimeGenerated
  • UserPrincipalName
  • UserId
  • IPAddress
  • AppDisplayName
  • AppId
  • ResourceDisplayName
  • ResultType
  • ResultDescription
  • ConditionalAccessStatus
  • AuthenticationRequirement
  • RiskLevelDuringSignIn
  • RiskLevelAggregated
  • RiskState
  • DeviceDetail
  • UserAgent
  • AutonomousSystemNumber
  • CorrelationId
  • SessionId
Cloud Control Plane

Microsoft Graph Activity Logs with caller identity, request URI/method, response status, source IP, app, session, and token context.

Required fields
  • TimeGenerated
  • AppId
  • ClientAuthMethod
  • DeviceId
  • IPAddress
  • OperationId
  • RequestId
  • RequestMethod
  • RequestUri
  • ResponseSizeBytes
  • ResponseStatusCode
  • Roles
  • Scopes
  • ServicePrincipalId
  • SessionId
  • SignInActivityId
  • TokenIssuedAt
  • UniqueTokenId
  • UserAgent
  • UserId
  • Wids

Blind spots

  • Tenants that do not export SSPR and authentication-method audit events cannot reconstruct the full recovery sequence.
  • A compromised user who already has common device/network patterns can avoid the first-seen source condition.
  • Graph Activity Logs must be explicitly available to support request-level discovery analysis.
  • Legitimate account recovery can look very similar until user/helpdesk context is obtained.
  • The source-IP baseline can be noisy in organizations with large VPN, SWG, VDI, or mobile populations.

Behavior mapping

MITRE ATT&CK

Mappings describe the behavior examined by this analytic. They do not prove attribution, deployment, or technique-wide coverage.

Review boundary

Sources and limits

External sources
6

Exact fields, retention, and operational thresholds remain environment-specific.