Query Details

31 CSL ZPA Anomalous Geolocation Access

Query

id: e531f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a
name: "Zscaler ZPA - Access to Internal Application from Anomalous Geolocation"
version: 1.0.0
kind: Scheduled
description: |
  Detects Zscaler Private Access (ZPA) connections to internal applications originating from countries the user has never accessed from in the previous 14 days. ZPA provides remote access to internal apps without a VPN; connections from unexpected geolocations indicate credential compromise, a threat actor using stolen credentials to access internal resources, or legitimate users travelling to unusual locations. MITRE ATT&CK: T1078 (Valid Accounts), T1133 (External Remote Services).
severity: High
requiredDataConnectors:
  - connectorId: CommonSecurityEvents
    dataTypes:
      - CommonSecurityLog
queryFrequency: PT1H
queryPeriod: P14D
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - LateralMovement
relevantTechniques:
  - T1078
  - T1133
query: |
    let recentWindow   = 1h;
    let baselineWindow = 14d;
    let 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) and isnotempty(SourceIP)
        | extend SrcCountry = tostring(geo_info_from_ip_address(SourceIP).country)
        | summarize BaselineCountries = make_set(SrcCountry)
            by SourceUserName;
    let recent = CommonSecurityLog
        | where TimeGenerated > ago(recentWindow)
        | where DeviceVendor == "Zscaler" and DeviceProduct has "ZPA"
        | where DeviceAction !in ("block", "BLOCK", "Failed")
        | where isnotempty(SourceUserName) and isnotempty(SourceIP)
        | extend SrcCountry = tostring(geo_info_from_ip_address(SourceIP).country)
        | summarize
            RecentCountries = make_set(SrcCountry),
            AccessedApps    = make_set(DestinationHostName, 10),
            ConnectionCount = count(),
            SrcIPs          = make_set(SourceIP, 5)
            by SourceUserName;
    recent
    | join kind=leftouter baseline on SourceUserName
    | extend BaselineCountries = coalesce(BaselineCountries, dynamic([]))
    | extend NewCountries = set_difference(RecentCountries, BaselineCountries)
    | where array_length(NewCountries) > 0
    | project SourceUserName, NewCountries, AccessedApps, ConnectionCount, SrcIPs
    | order by ConnectionCount desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SourceUserName
customDetails:
  ConnectionCount: ConnectionCount
  NewCountries: NewCountries
  AccessedApps: AccessedApps
alertDetailsOverride:
  alertDisplayNameFormat: "ZPA Anomalous Geolocation - {{SourceUserName}} from new countries"
  alertDescriptionFormat: "ZPA user {{SourceUserName}} connected from {{NewCountries}} — not seen in 14-day baseline. {{ConnectionCount}} connections to internal apps."
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT6H
    matchingMethod: Selected
    groupByEntities:
      - Account
    groupByAlertDetails: []
    groupByCustomDetails: []

Explanation

This query is designed to detect unusual access patterns to internal applications using Zscaler Private Access (ZPA). Here's a simplified breakdown of what it does:

  1. Purpose: It identifies when a user accesses internal applications from a country they haven't connected from in the past 14 days. This could indicate a security issue, such as compromised credentials or a legitimate user traveling to an unusual location.

  2. Data Source: It uses logs from Zscaler's CommonSecurityLog, focusing on successful connections (not blocked or failed attempts).

  3. Time Frames:

    • Baseline Window: Looks at the past 14 days to establish a baseline of countries from which each user has accessed internal applications.
    • Recent Window: Examines the last hour to identify new access locations.
  4. Process:

    • Baseline Creation: For each user, it collects a list of countries they have accessed from in the past 14 days.
    • Recent Activity: It gathers data on the countries accessed in the last hour, the applications accessed, the number of connections, and the source IPs.
    • Comparison: It compares the recent countries to the baseline. If there are new countries not seen in the baseline, it flags this as anomalous.
  5. Output: The query outputs details such as the username, new countries accessed, applications accessed, the number of connections, and source IPs. It orders the results by the number of connections.

  6. Alerting: If any anomalies are detected, it creates an alert with details about the user and the new countries accessed. It also supports incident creation and grouping based on user accounts.

  7. Severity and Techniques: The alert is marked with high severity and is associated with MITRE ATT&CK techniques related to valid accounts and external remote services.

In summary, this query helps identify potentially unauthorized access to internal applications by detecting connections from new and unusual geolocations.

Details

David Alonso profile picture

David Alonso

Released: March 2, 2026

Tables

CommonSecurityLog

Keywords

ZscalerZPAInternalApplicationGeolocationCredentialUserAccountSourceIPDestinationHostNameConnectionCountCountry

Operators

letbetweenagohas!inisnotemptytostringgeo_info_from_ip_addresssummarizemake_setby>joinkind=leftouteroncoalescedynamicset_differencearray_lengthprojectorder bydesc

Actions