{
  "technique_id": "T1566",
  "name": "Phishing",
  "priority": "high",
  "status": "complete",
  "version": "0.1.0",
  "last_reviewed": "2026-07-23",
  "generated_by": "SOC Response Atlas by Basyrix",
  "tactics": [
    "Initial Access"
  ],
  "platforms": [
    "Office 365",
    "SaaS",
    "Windows",
    "Linux",
    "macOS"
  ],
  "summary": "Adversaries may send phishing messages — spearphishing links, attachments, or service-based lures — to gain initial access via credential harvesting, malicious attachments, or social engineering.\n",
  "soc_recommendation": "Treat a reported or detected phishing message as a potential initial access event, not just a spam nuisance. Confirm whether the message was opened, links clicked, attachments executed, or credentials entered, and scope to every recipient of the same campaign.\n",
  "d3fend_mappings": [
    {
      "id": "D3-SRA",
      "name": "Sender Reputation Analysis",
      "relationship": "detect",
      "practical_action": "Analyze sender reputation, SPF/DKIM/DMARC results, and message content for known phishing indicators.\n",
      "tooling": [
        "Defender for Office 365",
        "Sentinel"
      ]
    },
    {
      "id": "D3-UA",
      "name": "URL Analysis",
      "relationship": "detect",
      "practical_action": "Detonate and analyze embedded URLs for credential-harvesting or malware delivery.",
      "tooling": [
        "Defender for Office 365 Safe Links"
      ]
    },
    {
      "id": "D3-ER",
      "name": "Email Removal",
      "relationship": "evict",
      "practical_action": "Purge the message from every recipient mailbox once confirmed malicious.",
      "tooling": [
        "Defender for Office 365"
      ]
    },
    {
      "id": "D3-EF",
      "name": "Email Filtering",
      "relationship": "isolate",
      "practical_action": "Block the sender domain/URL tenant-wide once confirmed malicious.",
      "tooling": [
        "Defender for Office 365"
      ]
    }
  ],
  "investigation_steps": {
    "microsoft": [
      "MDE portal → Device inventory → search device name → Isolate device",
      "Record: device name, user account, parent Office app, child process and full command line",
      "Is the command line encoded? (contains -EncodedCommand, FromBase64String) → decode immediately (see Step 2)",
      "Is the user a privileged account (IT admin, finance, C-suite)? → escalate to SOC Manager immediately",
      "Notify on-call Senior Analyst and open P1 incident ticket",
      "Download URLs → submit to VirusTotal immediately",
      "File paths → check DeviceFileEvents for dropped files",
      "Persistence commands → check registry and scheduled tasks"
    ],
    "generic": [
      "Confirm the message was actually delivered and opened, and by whom.",
      "Determine whether a link was clicked or an attachment executed.",
      "Check for credential entry on any resulting landing page.",
      "Identify whether any endpoint activity followed (process execution, network connections).",
      "Real detection reference: \"Phishing Click Followed by Account Change\" -- Detects a user clicking a suspicious/phishing URL followed shortly by account changes often seen after credential capture, such as MFA method changes, password reset activity, or risky consent/registration events."
    ]
  },
  "evidence_to_collect": [
    "Sender address and display name",
    "Message headers (SPF/DKIM/DMARC results)",
    "Recipient list",
    "URLs and attachment hashes",
    "Click/detonation verdicts",
    "Any subsequent sign-in or endpoint activity"
  ],
  "response_actions": [
    {
      "name": "Isolate device — MDE portal",
      "category": "Containment",
      "risk": "Medium",
      "automation_safe": false,
      "approval_required": true,
      "tool": "Defender for Endpoint"
    },
    {
      "name": "Notify customer primary security contact by phone",
      "category": "Containment",
      "risk": "Low",
      "automation_safe": true,
      "approval_required": false,
      "tool": "See investigation guide"
    },
    {
      "name": "Block sender domain — MDO / Exchange Online",
      "category": "Containment",
      "risk": "Medium",
      "automation_safe": false,
      "approval_required": true,
      "tool": "Defender for Office 365"
    },
    {
      "name": "Block attachment SHA256 — MDE custom indicators",
      "category": "Containment",
      "risk": "Medium",
      "automation_safe": false,
      "approval_required": true,
      "tool": "Defender for Endpoint"
    },
    {
      "name": "Revoke user sessions — Entra ID portal",
      "category": "Containment",
      "risk": "Medium",
      "automation_safe": false,
      "approval_required": true,
      "tool": "Entra ID"
    },
    {
      "name": "Open P1 incident ticket — tag PHISHING-MACRO-P1",
      "category": "Containment",
      "risk": "Low",
      "automation_safe": true,
      "approval_required": false,
      "tool": "See investigation guide"
    },
    {
      "name": "Search all campaign recipients and check their devices",
      "category": "Eradication",
      "risk": "Low",
      "automation_safe": true,
      "approval_required": false,
      "tool": "See investigation guide"
    },
    {
      "name": "Block all identified C2 IPs/domains — MDE indicators + Senti",
      "category": "Eradication",
      "risk": "Medium",
      "automation_safe": false,
      "approval_required": true,
      "tool": "Defender for Endpoint",
      "notes": "Block all identified C2 IPs/domains — MDE indicators + Sentinel watchlist"
    }
  ],
  "queries": {
    "kql": [
      {
        "name": "GEN-IA-005 — Phishing Click Followed by Account Change",
        "description": "Detects a user clicking a suspicious/phishing URL followed shortly by account changes often seen after credential capture, such as MFA method changes, password reset activity, or risky consent/registration events. (Source: Bell Integration baseline detection library, mapped via sub-technique T1566.002.)",
        "query": "let lookback = 1h;\nlet correlationWindow = 45m;\nlet suspiciousAccountOperations = dynamic([\n    \"User registered security info\",\n    \"User changed default security info\",\n    \"User deleted security info\",\n    \"Admin registered security info\",\n    \"Reset password (self-service)\",\n    \"Reset user password\",\n    \"Change user password\",\n    \"Consent to application\",\n    \"Add app role assignment to service principal\"\n]);\nlet urlClickEventsSource =\n    union isfuzzy=true UrlClickEvents,\n    (datatable(TimeGenerated:datetime, Url:string, AccountUpn:string, ThreatTypes:string, IsClickedThrough:string, ActionType:string, NetworkMessageId:string, IPAddress:string)[]);\nlet auditLogsSource =\n    union isfuzzy=true AuditLogs,\n    (datatable(TimeGenerated:datetime, OperationName:string, InitiatedBy:dynamic, TargetResources:dynamic, Result:string, AdditionalDetails:dynamic)[]);\nlet suspiciousClicks =\n    urlClickEventsSource\n    | where TimeGenerated >= ago(lookback)\n    | extend AccountUpn = tolower(tostring(column_ifexists(\"AccountUpn\", \"\"))),\n             Url = tostring(column_ifexists(\"Url\", \"\")),\n             ThreatTypes = tostring(column_ifexists(\"ThreatTypes\", \"\")),\n             IsClickedThrough = tolower(tostring(column_ifexists(\"IsClickedThrough\", \"false\"))),\n             ActionType = tostring(column_ifexists(\"ActionType\", \"\")),\n             ClickIP = tostring(column_ifexists(\"IPAddress\", \"\"))\n    | where isnotempty(AccountUpn) and isnotempty(Url)\n    | where ThreatTypes has \"Phish\" or IsClickedThrough == \"true\" or ActionType in~ (\"ClickAllowed\", \"ClickThrough\")\n    | extend ParsedUrl = parse_url(Url),\n             UrlHost = tolower(tostring(ParsedUrl.Host))\n    | project ClickTime = TimeGenerated, AccountUpn, Url, UrlHost, ThreatTypes, ActionType, IsClickedThrough, ClickIP, NetworkMessageId;\nlet accountChanges =\n    auditLogsSource\n    | where TimeGenerated >= ago(lookback)\n    | where OperationName has_any (suspiciousAccountOperations)\n    | extend Actor = tolower(tostring(InitiatedBy.user.userPrincipalName)),\n             TargetUser = tolower(tostring(TargetResources[0].userPrincipalName)),\n             TargetDisplayName = tostring(TargetResources[0].displayName),\n             ChangeDetails = tostring(AdditionalDetails)\n    | extend CorrelationUser = coalesce(Actor, TargetUser)\n    | where isnotempty(CorrelationUser)\n    | project ChangeTime = TimeGenerated, CorrelationUser, Actor, TargetUser, TargetDisplayName, OperationName, Result, ChangeDetails;\nsuspiciousClicks\n| join kind=inner accountChanges on $left.AccountUpn == $right.CorrelationUser\n| where ChangeTime between (ClickTime .. ClickTime + correlationWindow)\n| project\n    TimeGenerated = ChangeTime,\n    AccountUpn,\n    ClickTime,\n    ChangeTime,\n    Url,\n    UrlHost,\n    ThreatTypes,\n    ActionType,\n    ClickIP,\n    OperationName,\n    Actor,\n    TargetUser,\n    TargetDisplayName,\n    Result,\n    ChangeDetails,\n    NetworkMessageId\n| extend AlertTitle = strcat(\"Phishing click followed by account change: \", AccountUpn),\n         timestamp = TimeGenerated,\n         AccountCustomEntity = AccountUpn,\n         IPCustomEntity = ClickIP,\n         URLCustomEntity = Url\n| order by TimeGenerated desc"
      },
      {
        "name": "GEN-RD-001 — Malicious OAuth Application Registered or Consented",
        "description": "Flags new OAuth application registration or third-party consent with high-risk Graph/Exchange scopes. PS-001 now covers persistence via service principal credential additions. (Source: Bell Integration baseline detection library, mapped via sub-technique T1566.002.)",
        "query": "let HighRiskPerms = dynamic([\n    \"Mail.Read\", \"Mail.ReadWrite\", \"Mail.Send\", \"MailboxSettings.ReadWrite\",\n    \"Files.Read.All\", \"Files.ReadWrite.All\", \"Sites.Read.All\", \"Sites.ReadWrite.All\",\n    \"Calendars.ReadWrite\", \"Contacts.Read\", \"People.Read.All\",\n    \"User.ReadWrite.All\", \"Group.ReadWrite.All\", \"Directory.ReadWrite.All\",\n    \"Application.ReadWrite.All\", \"AppRoleAssignment.ReadWrite.All\",\n    \"RoleManagement.ReadWrite.Directory\", \"offline_access\", \"full_access_as_app\"\n]);\nlet auditLogsSource =\n    union isfuzzy=true AuditLogs,\n    (datatable(TimeGenerated:datetime, OperationName:string, InitiatedBy:dynamic, TargetResources:dynamic, AdditionalDetails:dynamic, Result:string)[]);\nlet NewAppRegistrations =\n    auditLogsSource\n    | where TimeGenerated >= ago(1h)\n    | where OperationName in~ (\"Add application\", \"Add service principal\", \"Add OAuth2PermissionGrant\")\n    | extend Actor = tostring(InitiatedBy.user.userPrincipalName),\n             AppName = tostring(TargetResources[0].displayName),\n             Permissions = strcat(tostring(TargetResources[0].modifiedProperties), \" \", tostring(AdditionalDetails))\n    | where Permissions has_any (HighRiskPerms)\n    | project TimeGenerated, Actor, AppName, OperationName, Detail = Permissions, Result, DetectionPath = \"HighRiskAppRegistration\";\nlet ConsentGrants =\n    auditLogsSource\n    | where TimeGenerated >= ago(1h)\n    | where OperationName =~ \"Consent to application\"\n    | extend Actor = tostring(InitiatedBy.user.userPrincipalName),\n             AppName = tostring(TargetResources[0].displayName),\n             Scopes = strcat(tostring(TargetResources[0].modifiedProperties), \" \", tostring(AdditionalDetails))\n    | where Scopes has_any (HighRiskPerms)\n    | project TimeGenerated, Actor, AppName, OperationName, Detail = Scopes, Result, DetectionPath = \"HighRiskConsentGrant\";\nNewAppRegistrations\n| union ConsentGrants\n| extend timestamp = TimeGenerated,\n         AccountCustomEntity = Actor\n| order by TimeGenerated desc"
      },
      {
        "name": "GEN-IA-012 — Spearphishing Link With Endpoint Follow-On",
        "description": "Higher-fidelity spearphishing link detector. Keeps the original URL-click and TI scoring, and adds endpoint network follow-on correlation. (Source: Bell Integration baseline detection library, mapped via sub-technique T1566.002.)",
        "query": "let lookback = 1d;\nlet endpointWindow = 45m;\nlet tiLookback = 14d;\nlet suspiciousSchemes = dynamic([\"http\", \"https\"]);\nlet urlClickEventsSource =\n    union isfuzzy=true UrlClickEvents,\n    (datatable(TimeGenerated:datetime, Url:string, AccountUpn:string, NetworkMessageId:string, ThreatTypes:string, IsClickedThrough:string, ActionType:string)[]);\nlet emailEventsSource =\n    union isfuzzy=true EmailEvents,\n    (datatable(TimeGenerated:datetime, EmailDirection:string, NetworkMessageId:string, RecipientEmailAddress:string, SenderFromAddress:string, SenderFromDomain:string, Subject:string, LatestDeliveryAction:string)[]);\nlet deviceNetworkEventsSource =\n    union isfuzzy=true DeviceNetworkEvents,\n    (datatable(TimeGenerated:datetime, DeviceId:string, DeviceName:string, RemoteUrl:string, RemoteIP:string, InitiatingProcessAccountUpn:string, InitiatingProcessAccountName:string, InitiatingProcessFileName:string, InitiatingProcessCommandLine:string)[]);\nlet tiUrlDomainIndicators =\n    union isfuzzy=true ThreatIntelligenceIndicator, ThreatIntelIndicators\n    | where TimeGenerated >= ago(tiLookback)\n    | where tobool(column_ifexists(\"Active\", true))\n    | where isempty(todatetime(column_ifexists(\"ExpirationDateTime\", datetime(null)))) or todatetime(column_ifexists(\"ExpirationDateTime\", now() + 365d)) > now()\n    | extend IndicatorUrl = tolower(tostring(column_ifexists(\"Url\", \"\"))),\n             IndicatorDomain = tolower(tostring(column_ifexists(\"DomainName\", \"\"))),\n             ThreatType = tostring(column_ifexists(\"ThreatType\", \"\")),\n             Provider = tostring(coalesce(column_ifexists(\"IndicatorProvider\", \"\"), column_ifexists(\"SourceSystem\", \"SentinelTI\"))),\n             ConfidenceScore = toint(coalesce(column_ifexists(\"ConfidenceScore\", 0), 0))\n    | where isnotempty(IndicatorUrl) or isnotempty(IndicatorDomain)\n    | summarize arg_max(TimeGenerated, *) by IndicatorUrl, IndicatorDomain\n    | project IndicatorUrl, IndicatorDomain, ThreatType, Provider, ConfidenceScore;\nlet clickedPhishLinks =\n    urlClickEventsSource\n    | where TimeGenerated >= ago(lookback)\n    | extend Url = tostring(column_ifexists(\"Url\", \"\"))\n    | where isnotempty(Url)\n    | extend ParsedUrl = parse_url(Url)\n    | extend UrlHost = tolower(tostring(ParsedUrl.Host)),\n             UrlScheme = tolower(tostring(ParsedUrl.Scheme)),\n             AccountUpn = tolower(tostring(column_ifexists(\"AccountUpn\", \"\"))),\n             NetworkMessageId = tostring(column_ifexists(\"NetworkMessageId\", \"\")),\n             ThreatTypes = tostring(column_ifexists(\"ThreatTypes\", \"\")),\n             IsClickedThrough = tolower(tostring(column_ifexists(\"IsClickedThrough\", \"false\"))),\n             ActionType = tostring(column_ifexists(\"ActionType\", \"\"))\n    | where UrlScheme in (suspiciousSchemes)\n    | where isnotempty(UrlHost)\n    | where ThreatTypes has \"Phish\" or IsClickedThrough == \"true\" or ActionType in~ (\"ClickAllowed\", \"ClickThrough\")\n    | project ClickTime = TimeGenerated, AccountUpn, NetworkMessageId, ThreatTypes, Url, UrlHost, IsClickedThrough, ActionType;\nlet emailContext =\n    emailEventsSource\n    | where TimeGenerated >= ago(lookback)\n    | where tostring(column_ifexists(\"EmailDirection\", \"\")) =~ \"Inbound\"\n    | project NetworkMessageId = tostring(column_ifexists(\"NetworkMessageId\", \"\")),\n              RecipientEmailAddress = tolower(tostring(column_ifexists(\"RecipientEmailAddress\", \"\"))),\n              SenderFromAddress = tostring(column_ifexists(\"SenderFromAddress\", \"\")),\n              SenderFromDomain = tostring(column_ifexists(\"SenderFromDomain\", \"\")),\n              Subject = tostring(column_ifexists(\"Subject\", \"\")),\n              DeliveryAction = tostring(column_ifexists(\"LatestDeliveryAction\", \"\"));\nlet endpointFollowOn =\n    deviceNetworkEventsSource\n    | where TimeGenerated >= ago(lookback)\n    | extend DstHost = tolower(tostring(column_ifexists(\"RemoteUrl\", \"\"))),\n             InitiatingProcessAccountUpn = tolower(tostring(column_ifexists(\"InitiatingProcessAccountUpn\", \"\")))\n    | where isnotempty(DstHost)\n    | project NetworkTime = TimeGenerated, DeviceId, DeviceName, DstHost,\n              RemoteIP = tostring(column_ifexists(\"RemoteIP\", \"\")),\n              InitiatingProcessAccountUpn,\n              InitiatingProcessAccountName = tostring(column_ifexists(\"InitiatingProcessAccountName\", \"\")),\n              InitiatingProcessFileName = tostring(column_ifexists(\"InitiatingProcessFileName\", \"\")),\n              InitiatingProcessCommandLine = tostring(column_ifexists(\"InitiatingProcessCommandLine\", \"\"));\nclickedPhishLinks\n| join kind=leftouter emailContext on NetworkMessageId\n| where isempty(DeliveryAction) or DeliveryAction !in~ (\"Blocked\", \"Dropped\", \"Quarantined\")\n| join kind=leftouter endpointFollowOn on $left.UrlHost == $right.DstHost\n| where isnull(NetworkTime)\n   or NetworkTime between (ClickTime .. ClickTime + endpointWindow)\n   or (isnotempty(AccountUpn) and isnotempty(InitiatingProcessAccountUpn) and AccountUpn == InitiatingProcessAccountUpn and NetworkTime between (ClickTime .. ClickTime + endpointWindow))\n| join kind=leftouter tiUrlDomainIndicators on $left.UrlHost == $right.IndicatorDomain\n| extend UrlMatchedInTI = isnotempty(IndicatorDomain),\n         RiskScore = 0\n             + iif(ThreatTypes has \"Phish\", 2, 0)\n             + iif(IsClickedThrough == \"true\" or ActionType in~ (\"ClickAllowed\", \"ClickThrough\"), 2, 0)\n             + iif(isnotempty(NetworkTime), 2, 0)\n             + iif(UrlMatchedInTI, 3, 0)\n             + iif(Provider has_any (\"phishtank\", \"openphish\"), 1, 0)\n| where RiskScore >= 5\n| project TimeGenerated = coalesce(NetworkTime, ClickTime), ClickTime, NetworkTime,\n          AccountUpn, RecipientEmailAddress, SenderFromAddress, SenderFromDomain, Subject,\n          NetworkMessageId, ThreatTypes, Url, UrlHost, IsClickedThrough, ActionType,\n          DeviceName, DeviceId, RemoteIP, InitiatingProcessFileName, InitiatingProcessCommandLine,\n          InitiatingProcessAccountName, UrlMatchedInTI, TIProvider = Provider,\n          TIThreatType = ThreatType, TIConfidenceScore = ConfidenceScore, RiskScore\n| extend AlertTitle = strcat(\"Spearphishing link compromise chain: \", coalesce(AccountUpn, RecipientEmailAddress), \" -> \", UrlHost),\n         timestamp = TimeGenerated,\n         AccountCustomEntity = coalesce(AccountUpn, RecipientEmailAddress, InitiatingProcessAccountName),\n         HostCustomEntity = DeviceName,\n         IPCustomEntity = RemoteIP,\n         URLCustomEntity = Url"
      }
    ],
    "spl": [
      {
        "name": "Post-phishing endpoint activity",
        "description": "Review endpoint activity for recipients shortly after delivery.",
        "query": "index=endpoint user IN (\"<recipient1>\", \"<recipient2>\") earliest=<delivery_time>\n"
      }
    ],
    "esql": [
      {
        "name": "3db029b3-fbb7-4697-ad07-33cbfd5bd080 — Entra ID OAuth Device Code Flow with Concurrent Sign-ins",
        "description": "(ESQL) Identifies Entra ID device code authentication flows where multiple user agents are observed within the same session. This pattern is indicative of device code phishing, where an attacker's polling client (e.g., Python script) and the victim's browser both appear in the same authentication session... (Source: Elastic's official detection-rules repository (Elastic License v2).)",
        "query": "from logs-azure.signinlogs-* metadata _id, _version, _index\n\n| where event.category == \"authentication\" and data_stream.dataset == \"azure.signinlogs\" and\n        azure.signinlogs.properties.original_transfer_method == \"deviceCodeFlow\"\n\n// Track events with deviceCode authentication protocol (browser auth) vs polling client\n| eval is_device_code_auth = case(azure.signinlogs.properties.authentication_protocol == \"deviceCode\", 1, 0)\n\n| stats Esql.count_logon = count(*),\n        Esql.device_code_auth_count = sum(is_device_code_auth),\n        Esql.timestamp_values = values(@timestamp),\n        Esql.source_ip_count_distinct = count_distinct(source.ip),\n        Esql.user_agent_count_distinct = count_distinct(user_agent.original),\n        Esql.user_agent_values = values(user_agent.original),\n        Esql.authentication_protocol_values = values(azure.signinlogs.properties.authentication_protocol),\n        Esql.azure_signinlogs_properties_client_app_values = values(azure.signinlogs.properties.app_display_name),\n        Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id),\n        Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),\n        Esql.azure_signinlogs_properties_auth_requirement_values = values(azure.signinlogs.properties.authentication_requirement),\n        Esql.azure_signinlogs_properties_tenant_id = values(azure.tenant_id),\n        Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code),\n        Esql.message_values = values(message),\n        Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id),\n        Esql.source_ip_values = values(source.ip)\n        by azure.signinlogs.properties.session_id, azure.signinlogs.identity\n\n// Require: 2+ events, at least one deviceCode auth protocol event, and either 2+ IPs or 2+ user agents\n| where Esql.count_logon >= 2 and Esql.device_code_auth_count >= 1 and (Esql.source_ip_count_distinct >= 2 or Esql.user_agent_count_distinct >= 2)\n| keep\n       Esql.*,\n       azure.signinlogs.properties.session_id,\n       azure.signinlogs.identity"
      },
      {
        "name": "e3bd85e9-7aff-46eb-b60e-20dfc9020d98 — Entra ID Concurrent Sign-in with Suspicious Properties",
        "description": "(ESQL) Identifies concurrent azure signin events for the same user and from multiple sources, and where one of the authentication event has some suspicious properties often associated to DeviceCode and OAuth phishing. Adversaries may steal Refresh Tokens (RTs) via phishing to bypass multi-factor authentication (MFA) and gain unauthorized access to Azure resources. (Source: Elastic's official detection-rules repository (Elastic License v2).)",
        "query": "from logs-azure.signinlogs-* metadata _id, _version, _index\n\n// Scheduled to run every hour, reviewing events from past hour\n| where\n  @timestamp > now() - 1 hours\n  and data_stream.dataset == \"azure.signinlogs\"\n  and source.ip is not null\n  and azure.signinlogs.identity is not null\n  and to_lower(event.outcome) == \"success\"\n\n// keep relevant raw fields\n| keep\n  @timestamp,\n  azure.signinlogs.identity,\n  source.ip,\n  azure.signinlogs.properties.authentication_requirement,\n  azure.signinlogs.properties.app_id,\n  azure.signinlogs.properties.resource_display_name,\n  azure.signinlogs.properties.authentication_protocol,\n  azure.signinlogs.properties.app_display_name\n\n// case classifications for identity usage\n| eval\n      Esql.azure_signinlogs_properties_authentication_device_code_case = case(\n      azure.signinlogs.properties.authentication_protocol == \"deviceCode\"\n      and azure.signinlogs.properties.authentication_requirement != \"multiFactorAuthentication\",\n      azure.signinlogs.identity,\n      null),\n\n    Esql.azure_signinlogs_auth_visual_studio_case = case(\n      azure.signinlogs.properties.app_id == \"aebc6443-996d-45c2-90f0-388ff96faa56\"\n      and azure.signinlogs.properties.resource_display_name == \"Microsoft Graph\",\n      azure.signinlogs.identity,\n      null),\n\n    Esql.azure_signinlogs_auth_other_case = case(\n      azure.signinlogs.properties.authentication_protocol != \"deviceCode\"\n      and azure.signinlogs.properties.app_id != \"aebc6443-996d-45c2-90f0-388ff96faa56\",\n      azure.signinlogs.identity,\n      null)\n\n// Aggregate metrics by user identity\n| stats\n    Esql.event_count = count(*),\n    Esql.azure_signinlogs_properties_authentication_device_code_case_count_distinct = count_distinct(Esql.azure_signinlogs_properties_authentication_device_code_case),\n    Esql.azure_signinlogs_properties_auth_visual_studio_count_distinct = count_distinct(Esql.azure_signinlogs_auth_visual_studio_case),\n    Esql.azure_signinlogs_properties_auth_other_count_distinct = count_distinct(Esql.azure_signinlogs_auth_other_case),\n    Esql.azure_signinlogs_properties_source_ip_count_distinct = count_distinct(source.ip),\n    Esql.azure_signinlogs_properties_source_ip_values = values(source.ip),\n    Esql.azure_signinlogs_properties_client_app_values = values(azure.signinlogs.properties.app_display_name),\n    Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),\n    Esql.azure_signinlogs_properties_auth_requirement_values = values(azure.signinlogs.properties.authentication_requirement)\n  by azure.signinlogs.identity\n\n// Detect multiple unique IPs for one user with signs of deviceCode or VSC OAuth usage\n| where\n  Esql.azure_signinlogs_properties_source_ip_count_distinct >= 2\n  and (\n    Esql.azure_signinlogs_properties_authentication_device_code_case_count_distinct > 0\n    or Esql.azure_signinlogs_properties_auth_visual_studio_count_distinct > 0\n  )"
      }
    ]
  },
  "automation": {
    "safe": [
      "Add recommendation as Sentinel incident comment.",
      "Purge confirmed-malicious message from all mailboxes.",
      "Block confirmed-malicious sender domain/URL.",
      "Create ServiceNow SecOps task."
    ],
    "approval_required": [
      "Reset password for affected recipients.",
      "Disable account for a recipient who entered credentials."
    ]
  },
  "escalation_criteria": [
    "Executive, finance, or privileged account among recipients.",
    "Credentials were entered on a harvesting page.",
    "Attachment was executed and produced endpoint activity.",
    "Campaign reached a large number of recipients.",
    "OAuth consent grant followed the message."
  ],
  "false_positive_considerations": [
    "Signed corporate macro launching a helper script — InitiatingProcessSHA256 matches a known and approved template hash",
    "RMM agent launched via Office integration — Child process is a signed binary from an approved vendor folder",
    "Legitimate PDF conversion tool (Office plugin) — Vendor-signed binary, consistent across all users, same path"
  ],
  "confluence": {
    "title": "T1566 - Phishing Response Guidance",
    "labels": [
      "mitre",
      "attack",
      "d3fend",
      "secops",
      "basyrix"
    ],
    "sections": [
      "summary",
      "d3fend_mappings",
      "investigation_steps",
      "response_actions",
      "queries",
      "automation",
      "escalation_criteria",
      "false_positive_considerations"
    ]
  }
}