Query Details

36 CSL ZPA Perfect Impostor Account Takeover

Query

id: a036b1c2-d3e4-4f5a-6b7c-8d9e0f1a2b3c
name: "Zscaler ZPA/ZIA - Perfect Impostor: Account Takeover Hiding Within Normal Traffic"
version: 1.0.0
kind: Scheduled
description: |
  Detects a "perfect impostor" scenario where an attacker using stolen credentials behaves
  almost identically to the real user but introduces subtle cross-layer anomalies:

  • ZPA layer: identifies ZPA sessions originating from a country not observed in the
    user's 14-day baseline — indicating a new device, VPN exit, or compromised session
    from an unexpected location.

  • ZIA layer: identifies browsing to remote-admin tools, cloud admin portals,
    account-creation sites, anonymous proxies, or tunnelling applications — tooling
    inconsistent with usual workflow patterns.

  • IdP / SaaS layer (OfficeActivity): captures security-critical account changes
    performed in the same window — MFA removal, OAuth consent grants, inbox auto-forward
    rules, mailbox delegation, and privilege escalation.

  Each layer is scored individually. A single IdP change plus one ZIA suspicious category
  is sufficient to surface the identity. Cross-layer correlation drastically reduces
  false positives compared to single-signal detections.

  Outcome: A prioritised list of "high-risk compromised account" candidates for manual
  session review, token revocation, and MFA re-enforcement before the attacker achieves
  persistence or escalates privileges.

  MITRE ATT&CK: TA0003 (Persistence), TA0004 (Privilege Escalation),
  TA0005 (Defense Evasion), TA0006 (Credential Access),
  T1078 (Valid Accounts), T1098 (Account Manipulation),
  T1114 (Email Collection), T1556 (Modify Authentication Process).
severity: High
requiredDataConnectors:
  - connectorId: CommonSecurityEvents
    dataTypes:
      - CommonSecurityLog
queryFrequency: PT4H
queryPeriod: P14D
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Persistence
  - PrivilegeEscalation
  - DefenseEvasion
  - CredentialAccess
relevantTechniques:
  - T1078
  - T1098
  - T1114
  - T1556
query: |
    let baselineWindow = 14d;
    let recentWindow   = 4h;
    let ZPA_Baseline =
        CommonSecurityLog
        | where TimeGenerated between (ago(baselineWindow) .. ago(recentWindow))
        | where DeviceVendor == "Zscaler" and DeviceProduct has "ZPA"
        | where DeviceAction !in ("block", "BLOCK", "Failed")
        | where isnotempty(SourceUserName)
        | extend BaselineCountry = tostring(geo_info_from_ip_address(SourceIP).country)
        | summarize
            BaselineCountries = make_set(BaselineCountry, 20),
            BaselineAppCount  = dcount(DestinationHostName)
          by UserName = tolower(SourceUserName);
    let ZPA_Recent =
        CommonSecurityLog
        | where TimeGenerated > ago(recentWindow)
        | where DeviceVendor == "Zscaler" and DeviceProduct has "ZPA"
        | where isnotempty(SourceUserName)
        | extend RecentCountry = tostring(geo_info_from_ip_address(SourceIP).country)
        | summarize
            RecentCountries = make_set(RecentCountry, 5),
            RecentApps      = make_set(DestinationHostName, 10),
            RecentIPs       = make_set(SourceIP, 5),
            RecentConnCount = count(),
            ZPA_FirstSeen   = min(TimeGenerated)
          by UserName = tolower(SourceUserName);
    let ZIA_Suspicious =
        CommonSecurityLog
        | where TimeGenerated > ago(recentWindow)
        | where DeviceVendor == "Zscaler"
        | where isnotempty(SourceUserName)
        | where DeviceCustomString2 has_any (
            "REMOTE_ACCESS_TOOLS", "REMOTE_ADMINISTRATION", "WEB_BASED_ADMIN",
            "ACCOUNT_CREATION", "TUNNEL_SOCKS_PROXY", "ANONYMOUS_PROXY",
            "ONLINE_STORAGE", "FILE_SHARE", "TOR", "VPN_SERVICES",
            "NEWLY_REGISTERED_DOMAINS", "PHISHING")
        | summarize
            ZIA_Categories   = make_set(DeviceCustomString2, 10),
            ZIA_Destinations = make_set(DestinationHostName, 10),
            ZIA_RequestCount = count(),
            ZIA_FirstSeen    = min(TimeGenerated)
          by UserName = tolower(SourceUserName);
    let IdP_Changes =
        OfficeActivity
        | where TimeGenerated > ago(recentWindow)
        | where isnotempty(UserId)
        | where Operation in (
            "New-InboxRule",
            "Set-Mailbox",
            "Add-MailboxPermission",
            "Add OAuth2PermissionGrant",
            "Consent to application",
            "Add service principal credentials",
            "Update user",
            "Disable Strong Authentication",
            "Update StsRefreshTokenValidFrom",
            "Add member to role")
        | summarize
            IdP_EventCount = count(),
            IdP_Operations = make_set(Operation, 10),
            IdP_FirstSeen  = min(TimeGenerated)
          by UserName = tolower(UserId);
    ZPA_Recent
    | join kind=inner ZPA_Baseline on UserName
    | extend NewCountries = set_difference(RecentCountries, BaselineCountries)
    | join kind=leftouter ZIA_Suspicious on UserName
    | join kind=leftouter IdP_Changes on UserName
    | extend
        ZIA_RequestCount = coalesce(ZIA_RequestCount, 0),
        IdP_EventCount   = coalesce(IdP_EventCount, 0)
    | extend ImpostorScore =
        iff(array_length(NewCountries) > 0, 40, 0)
      + (ZIA_RequestCount * 5)
      + (IdP_EventCount * 30)
    | where ImpostorScore >= 40
        or (IdP_EventCount >= 1 and ZIA_RequestCount >= 1)
        or array_length(NewCountries) > 0
    | project
        UserName,
        NewCountries, RecentCountries, BaselineCountries,
        RecentApps, RecentIPs, RecentConnCount,
        ZIA_Categories, ZIA_Destinations, ZIA_RequestCount,
        IdP_Operations, IdP_EventCount,
        ImpostorScore,
        ZPA_FirstSeen
    | order by ImpostorScore desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserName
customDetails:
  ImpostorScore: ImpostorScore
  IdP_EventCount: IdP_EventCount
  ZIA_RequestCount: ZIA_RequestCount
  NewCountries: NewCountries
alertDetailsOverride:
  alertDisplayNameFormat: "Perfect Impostor Candidate - {{UserName}} (score: {{ImpostorScore}})"
  alertDescriptionFormat: "User {{UserName}} shows cross-layer anomalies: new countries {{NewCountries}}, {{ZIA_RequestCount}} ZIA suspicious requests, {{IdP_EventCount}} identity/security changes. ImpostorScore: {{ImpostorScore}}."
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT8H
    matchingMethod: Selected
    groupByEntities:
      - Account
    groupByAlertDetails: []
    groupByCustomDetails: []

Explanation

This query is designed to detect potential account takeovers by identifying unusual behavior across different layers of user activity. Here's a simplified breakdown:

  1. Purpose: The query aims to identify "perfect impostor" scenarios where an attacker uses stolen credentials and mimics a legitimate user's behavior but introduces subtle anomalies.

  2. Layers Analyzed:

    • ZPA Layer: Checks for user sessions from countries not seen in the past 14 days, indicating possible use of a new device, VPN, or compromised session.
    • ZIA Layer: Looks for browsing activity to suspicious sites like remote admin tools, proxies, or newly registered domains, which are not typical for the user.
    • IdP/SaaS Layer: Monitors critical account changes such as MFA removal or privilege escalation.
  3. Scoring: Each layer is scored individually. A high score or a combination of suspicious activities across layers flags the account as potentially compromised.

  4. Outcome: The query generates a prioritized list of high-risk accounts for further review and action, such as session termination or MFA re-enforcement, to prevent attackers from gaining persistence or escalating privileges.

  5. Frequency and Duration: The query runs every 4 hours and analyzes data from the past 14 days.

  6. Alerting: If suspicious activity is detected, an alert is created with details about the anomalies and a calculated "ImpostorScore" to prioritize investigation.

Overall, this query helps security teams quickly identify and respond to potential account takeovers by correlating anomalies across multiple layers of user activity.

Details

David Alonso profile picture

David Alonso

Released: March 2, 2026

Tables

CommonSecurityLogOfficeActivity

Keywords

DevicesZscalerZPAZIAIdPSaaSOfficeActivityUserAccountCommonSecurityEventsCommonSecurityLogRemoteAccessToolsRemoteAdministrationWebBasedAdminAccountCreationTunnelSocksProxyAnonymousProxyOnlineStorageFileShareTORVPNServicesNewlyRegisteredDomainsPhishingNewInboxRuleSetMailboxAddMailboxPermissionAddOAuth2PermissionGrantConsentToApplicationAddServicePrincipalCredentialsUpdateUserDisableStrongAuthenticationUpdateStsRefreshTokenValidFromAddMemberToRole

Operators

letbetweenagohas!inisnotemptytostringgeo_info_from_ip_addresssummarizemake_setdcounttolowerhas_anycountmininjoinkindextendset_differenceleftoutercoalesceiffarray_lengthprojectorder bydesc

Actions