Detection rules › Panther

Panther rules: zia

ZIA Account Access Removed

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when admin user/role was deleted.

MITRE ATT&CK coverage

TacticTechniques
Impact

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success

SENSITIVE_CATEGORIES = ["ADMINISTRATOR_MANAGEMENT", "ROLE_MANAGEMENT"]


def rule(event):
    if not zia_success(event):
        return False
    event_data = event.get("event", {})
    return (
        event_data.get("action", "ACTION_NOT_FOUND") == "DELETE"
        and event_data.get("category", "CATEGORY_NOT_FOUND") in SENSITIVE_CATEGORIES
    )


def title(event):
    return (
        f"[Zscaler.ZIA]: Admin account was deleted by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Account.Access.Removed
Description: This rule detects when admin user/role was deleted.
DisplayName: ZIA Account Access Removed
Runbook: Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.
Reference: https://help.zscaler.com/zia/authentication-administration/administrator-role-management
Enabled: true
Filename: zia_account_access_removal.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0040:T1531 # Account Access removal
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is DELETE
  • event.category is one of ADMINISTRATOR_MANAGEMENT, ROLE_MANAGEMENT

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "DELETE",
    "adminid": "admin@16991311.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "ADMINISTRATOR_MANAGEMENT",
    "clientip": "123.123.123.123",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {},
    "preaction": {
      "adminScope": {
        "scopeEntities": [],
        "scopeGroupMemberEntities": [],
        "type": "ORGANIZATION"
      },
      "disabled": false,
      "email": "johndoe@company.com",
      "id": 19752821,
      "isExecMobileAppEnabled": true,
      "isPasswordLoginAllowed": true,
      "loginName": "johndoe@dev-company.com",
      "pwdLastModifiedTime": 1729634767,
      "role": {
        "deleted": false,
        "extensions": {
          "adminRank": "0",
          "roleType": "EXEC_INSIGHT_AND_ORG_ADMIN"
        },
        "id": 24354,
        "isNameL10nTag": true,
        "name": "Super Admin"
      },
      "userName": "johndoe1123"
    },
    "recordid": "327",
    "resource": "johndoe1123",
    "result": "SUCCESS",
    "subcategory": "ADMINISTRATOR_ADMIN_USER",
    "time": "2024-10-22 22:09:01.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Additional Cloud Roles

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when an additional cloud role was created.

MITRE ATT&CK coverage

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    event_data = event.get("event", {})
    return (
        event_data.get("action", "ACTION_NOT_FOUND") == "CREATE"
        and event_data.get("category", "CATEGORY_NOT_FOUND") == "ROLE_MANAGEMENT"
    )


def title(event):
    return (
        f"[Zscaler.ZIA]: Additional cloud role was created by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Additional.Cloud.Roles
Description: This rule detects when an additional cloud role was created.
DisplayName: ZIA Additional Cloud Roles
Runbook: Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.
Reference: https://help.zscaler.com/zia/about-role-management
Enabled: true
Filename: zia_additional_cloud_roles.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0003:T1098.003 # Persistence: Additional Cloud Roles
    - TA0004:T1098.003 # Priv Escalation: Additional Cloud Roles
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is CREATE
  • event.category is ROLE_MANAGEMENT

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "CREATE",
    "adminid": "admin@16991311.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "ROLE_MANAGEMENT",
    "clientip": "123.123.123.123",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {
      "adminAcctAccess": "READ_WRITE",
      "alertingAccess": "READ_WRITE",
      "analysisAccess": "READ_ONLY",
      "dashboardAccess": "READ_WRITE",
      "deviceInfoAccess": "READ_ONLY",
      "id": 32780,
      "logsLimit": "Unrestricted",
      "name": "mega admin",
      "permissions": [
        "ADVANCED_SETTINGS",
        "COMPLY",
        "FIREWALL_DNS",
        "NSS_CONFIGURATION",
        "SECURE",
        "SSL_POLICY",
        "VZEN_CONFIGURATION",
        "PARTNER_INTEGRATION",
        "REMOTE_ASSISTANCE_MANAGEMENT",
        "LOCATIONS",
        "VPN_CREDENTIALS",
        "HOSTED_PAC_FILES",
        "EZ_AGENT_CONFIGURATIONS",
        "SECURE_AGENT_NOTIFICATIONS",
        "PROXY_GATEWAY",
        "STATIC_IPS",
        "GRE_TUNNELS",
        "SUBCLOUDS",
        "AUTHENTICATION_SETTINGS",
        "USER_MANAGEMENT",
        "IDENTITY_PROXY_SETTINGS",
        "APIKEY_MANAGEMENT",
        "POLICY_RESOURCE_MANAGEMENT",
        "CLIENT_CONNECTOR_PORTAL",
        "CUSTOM_URL_CAT",
        "OVERRIDE_EXISTING_CAT",
        "TENANT_PROFILE_MANAGEMENT"
      ],
      "policyAccess": "READ_WRITE",
      "rank": 7,
      "reportAccess": "READ_WRITE",
      "reportTimeDuration": -1,
      "roleType": "EXEC_INSIGHT_AND_ORG_ADMIN",
      "usernameAccess": "READ_ONLY"
    },
    "preaction": {
      "adminAcctAccess": "READ_WRITE",
      "alertingAccess": "READ_WRITE",
      "analysisAccess": "READ_ONLY",
      "dashboardAccess": "READ_WRITE",
      "deviceInfoAccess": "READ_ONLY",
      "id": 0,
      "isAuditor": false,
      "isNonEditable": false,
      "logsLimit": "Unrestricted",
      "name": "mega admin",
      "permissions": [
        "ADVANCED_SETTINGS",
        "COMPLY",
        "FIREWALL_DNS",
        "NSS_CONFIGURATION",
        "SECURE",
        "SSL_POLICY",
        "VZEN_CONFIGURATION",
        "PARTNER_INTEGRATION",
        "REMOTE_ASSISTANCE_MANAGEMENT",
        "LOCATIONS",
        "VPN_CREDENTIALS",
        "HOSTED_PAC_FILES",
        "EZ_AGENT_CONFIGURATIONS",
        "SECURE_AGENT_NOTIFICATIONS",
        "PROXY_GATEWAY",
        "STATIC_IPS",
        "GRE_TUNNELS",
        "SUBCLOUDS",
        "AUTHENTICATION_SETTINGS",
        "USER_MANAGEMENT",
        "IDENTITY_PROXY_SETTINGS",
        "APIKEY_MANAGEMENT",
        "POLICY_RESOURCE_MANAGEMENT",
        "CLIENT_CONNECTOR_PORTAL",
        "CUSTOM_URL_CAT",
        "OVERRIDE_EXISTING_CAT",
        "TENANT_PROFILE_MANAGEMENT"
      ],
      "policyAccess": "READ_WRITE",
      "rank": 7,
      "reportAccess": "READ_WRITE",
      "reportTimeDuration": -1,
      "roleType": "EXEC_INSIGHT_AND_ORG_ADMIN",
      "usernameAccess": "READ_ONLY"
    },
    "recordid": "341",
    "resource": "mega admin",
    "result": "SUCCESS",
    "subcategory": "ADMINISTRATOR_ROLE",
    "time": "2024-10-22 22:19:57.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Backup Deleted

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when ZIA backup data was deleted.

MITRE ATT&CK coverage

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    action = event.deep_get("event", "action", default="ACTION_NOT_FOUND")
    category = event.deep_get("event", "category", default="CATEGORY_NOT_FOUND")
    if action == "DELETE" and category == "BACKUP_AND_RESTORE":
        return True
    return False


def title(event):
    return (
        f"[Zscaler.ZIA]: Backup was deleted by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Backup.Deleted
Description: This rule detects when ZIA backup data was deleted.
DisplayName: ZIA Backup Deleted
Runbook: Verify that this change was planned. If not, make sure to restore the backup.
Reference: https://help.zscaler.com/zia/about-backup-and-restore
Enabled: true
Filename: zia_backup_deleted.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0005:T1562.008 # Disable or Modify Cloud Logs
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is DELETE
  • event.category is BACKUP_AND_RESTORE

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Verify that this change was planned. If not, make sure to restore the backup.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "DELETE",
    "adminid": "admin@test.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "BACKUP_AND_RESTORE",
    "clientip": "1.2.3.4",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {},
    "preaction": {
      "adminLogin": "admin@test.zscalerbeta.net",
      "goldenRestorePoint": false,
      "id": 163372,
      "name": "test-restore-2",
      "time": 1730737925000
    },
    "recordid": "366",
    "resource": "test-restore-2",
    "result": "SUCCESS",
    "subcategory": "BACKUP_AND_RESTORE",
    "time": "2024-11-04 16:32:18.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Cloud Account Created

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when new cloud account was created.

MITRE ATT&CK coverage

TacticTechniques
Persistence

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    action = event.deep_get("event", "action", default="ACTION_NOT_FOUND")
    category = event.deep_get("event", "category", default="CATEGORY_NOT_FOUND")
    role_name = event.deep_get(
        "event", "postaction", "role", "name", default="<ROLE_NAME_NOT_FOUND>"
    ).lower()
    if (
        action == "CREATE"
        and category == "ADMINISTRATOR_MANAGEMENT"
        and ("admin" in role_name or "audit" in role_name)
    ):
        return True
    return False


def title(event):
    return (
        f"[Zscaler.ZIA]: New admin role was created by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Cloud.Account.Created
Description: This rule detects when new cloud account was created.
DisplayName: ZIA Cloud Account Created
Runbook: Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.
Reference: https://help.zscaler.com/zia/choosing-provisioning-and-authentication-methods
Enabled: true
Filename: zia_create_cloud_account.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0003:T1136.003 # Persistence: Create Cloud Account
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is CREATE
  • event.category is ADMINISTRATOR_MANAGEMENT
  • any of:
    • event.postaction.role.name contains admin
    • event.postaction.role.name contains audit

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "CREATE",
    "adminid": "admin@16991311.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "ADMINISTRATOR_MANAGEMENT",
    "clientip": "123.123.123.123",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {
      "adminScope": {
        "scopeEntities": [],
        "scopeGroupMemberEntities": [],
        "type": "ORGANIZATION"
      },
      "disabled": false,
      "email": "ajohndoe@company.com",
      "id": 19752821,
      "isExecMobileAppEnabled": true,
      "isPasswordLoginAllowed": true,
      "loginName": "johndoe@dev-company.com",
      "pwdLastModifiedTime": 1729634767,
      "role": {
        "deleted": false,
        "extensions": {
          "adminRank": "0",
          "roleType": "EXEC_INSIGHT_AND_ORG_ADMIN"
        },
        "id": 24354,
        "isNameL10nTag": true,
        "name": "Super Admin"
      },
      "userName": "johndoe1123"
    },
    "preaction": {
      "adminScope": {
        "scopeEntities": [],
        "scopeGroupMemberEntities": [],
        "type": "ORGANIZATION"
      },
      "disabled": false,
      "email": "johndoe@company.com",
      "id": 0,
      "isAuditor": false,
      "isDefaultAdmin": false,
      "isExecMobileAppEnabled": true,
      "isPasswordExpired": false,
      "isPasswordLoginAllowed": true,
      "loginName": "johndoe@dev-company.com",
      "newLocationCreateAllowed": false,
      "password": "*****",
      "pwdLastModifiedTime": 0,
      "role": {
        "deleted": false,
        "id": 24354,
        "isNameL10nTag": false,
        "name": "Super Admin"
      },
      "userName": "johndoe1123"
    },
    "recordid": "326",
    "resource": "johndoe1123",
    "result": "SUCCESS",
    "subcategory": "ADMINISTRATOR_ADMIN_USER",
    "time": "2024-10-22 22:06:04.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Golden Restore Point Dropped

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when ZIA goldenRestorePoint was dropped. It means that some piece of information that was impossible to delete before, now is deletable

MITRE ATT&CK coverage

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    action = event.deep_get("event", "action", default="ACTION_NOT_FOUND")
    category = event.deep_get("event", "category", default="CATEGORY_NOT_FOUND")
    golden_restore_point_pre = event.deep_get(
        "event",
        "preaction",
        "goldenRestorePoint",
        default="<PRE_RESTORE_POINT_NOT_FOUND>",
    )
    golden_restore_point_post = event.deep_get(
        "event",
        "postaction",
        "goldenRestorePoint",
        default="<POPT_RESTORE_POINT_NOT_FOUND>",
    )
    if (
        action == "UPDATE"
        and category == "BACKUP_AND_RESTORE"
        and golden_restore_point_pre is True
        and golden_restore_point_post is False
    ):
        return True
    return False


def title(event):
    return (
        f"[Zscaler.ZIA]: goldenRestorePoint was dropped by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Golden.Restore.Point.Dropped
Description: This rule detects when ZIA goldenRestorePoint was dropped. 
  It means that some piece of information that was impossible to delete before, now is deletable
DisplayName: ZIA Golden Restore Point Dropped
Runbook: Verify that this change was planned. If not, revert the change.
Reference: https://help.zscaler.com/zia/about-backup-and-restore
Enabled: true
Filename: zia_golden_restore_point_dropped.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0005:T1562.008 # Disable or Modify Cloud Logs
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is UPDATE
  • event.category is BACKUP_AND_RESTORE
  • event.preaction.goldenRestorePoint is true
  • event.postaction.goldenRestorePoint is false

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Verify that this change was planned. If not, revert the change.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "UPDATE",
    "adminid": "admin@test.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "BACKUP_AND_RESTORE",
    "clientip": "1.2.3.4",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {
      "adminLogin": "admin@test.zscalerbeta.net",
      "goldenRestorePoint": false,
      "id": 163371,
      "name": "test-restore",
      "time": 1730737915000
    },
    "preaction": {
      "adminLogin": "admin@test.zscalerbeta.net",
      "goldenRestorePoint": true,
      "id": 163371,
      "name": "test-restore",
      "time": 1730737915000
    },
    "recordid": "367",
    "resource": "test-restore",
    "result": "SUCCESS",
    "subcategory": "BACKUP_AND_RESTORE",
    "time": "2024-11-04 16:32:28.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Insecure Password Settings

#
Severity
medium
Group by
event.adminid
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when password settings are insecure.

MITRE ATT&CK coverage

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    auth_frequency = event.deep_get(
        "event",
        "postaction",
        "authFrequency",
        default="<AUTH_FREQUENCY_NOT_FOUND>",
    )
    password_expiry = event.deep_get(
        "event",
        "postaction",
        "passwordExpiry",
        default="<PASSWORD_EXPIRY_NOT_FOUND>",
    )
    password_strength = event.deep_get(
        "event",
        "postaction",
        "passwordStrength",
        default="<PASSWORD_STRENGTH_NOT_FOUND>",
    )
    if (
        auth_frequency == "PERMANENT_COOKIE"
        or password_expiry == "NEVER"  # nosec bandit B105
        or password_strength == "NONE"  # nosec bandit B105
    ):
        return True
    return False


def dedup(event):
    return event.deep_get("event", "adminid", default="<ADMIN_ID_NOT_FOUND>")


def title(event):
    return (
        f"[Zscaler.ZIA]: Password settings are insecure for admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Insecure.Password.Settings
Description: This rule detects when password settings are insecure.
DisplayName: ZIA Insecure Password Settings
Runbook: Set the secure password configurations.
Reference: https://help.zscaler.com/zia/customizing-your-admin-account-settings
Enabled: true
Filename: zia_insecure_password_settings.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0005:T1556.009 # Defense Evasion: Modify Authentication Process: Conditional Access Policies
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • any of:
    • event.postaction.authFrequency is PERMANENT_COOKIE
    • event.postaction.passwordExpiry is NEVER
    • event.postaction.passwordStrength is NONE

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Set the secure password configurations.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "UPDATE",
    "adminid": "admin@test.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "AUTHENTICATION_SETTINGS",
    "clientip": "1.2.3.4",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {
      "authFrequency": "PERMANENT_COOKIE",
      "autoProvision": false,
      "directorySyncMigrateToScimEnabled": false,
      "kerberosEnabled": false,
      "mobileAdminSamlIdpEnabled": false,
      "oneTimeAuth": "OTP_DISABLED",
      "orgAuthType": "SAFECHANNEL_DIR",
      "passwordExpiry": "NEVER",
      "passwordStrength": "NONE",
      "samlEnabled": false
    },
    "preaction": {
      "authFrequency": "DAILY_COOKIE",
      "autoProvision": false,
      "directorySyncMigrateToScimEnabled": false,
      "kerberosEnabled": false,
      "mobileAdminSamlIdpEnabled": false,
      "oneTimeAuth": "OTP_DISABLED",
      "orgAuthType": "SAFECHANNEL_DIR",
      "passwordExpiry": "NEVER",
      "passwordStrength": "NONE",
      "samlEnabled": false
    },
    "recordid": "356",
    "resource": "None",
    "result": "SUCCESS",
    "subcategory": "AUTH_SETTINGS_PROFILE",
    "time": "2024-11-04 16:29:24.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Log Streaming Disabled

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when ZIA log streaming was disabled.

MITRE ATT&CK coverage

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    action = event.deep_get("event", "action", default="ACTION_NOT_FOUND")
    category = event.deep_get("event", "category", default="CATEGORY_NOT_FOUND")
    if action == "DELETE" and category == "NSS":
        return True
    return False


def title(event):
    cloud_connection_url = event.deep_get(
        "event",
        "preaction",
        "cloudNssSiemConfiguration",
        "connectionURL",
        default="<CLOUD_CONNECTION_URL_NOT_FOUND>",
    )
    return (
        f"[Zscaler.ZIA]: Log streaming for location [{cloud_connection_url}] "
        f"was deleted by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Log.Streaming.Disabled
Description: This rule detects when ZIA log streaming was disabled.
DisplayName: ZIA Log Streaming Disabled
Runbook: Verify that this change was planned. If not, make sure to restore previous settings.
Reference: https://help.zscaler.com/zia/about-nss-feeds
Enabled: true
Filename: zia_log_streaming_disabled.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0005:T1562.008 # Disable or Modify Cloud Logs
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is DELETE
  • event.category is NSS

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction
connectionURLevent.preaction.cloudNssSiemConfiguration.connectionURL

Response runbook

Verify that this change was planned. If not, make sure to restore previous settings.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "DELETE",
    "adminid": "admin@test.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "NSS",
    "clientip": "1.2.3.4",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {},
    "preaction": {
      "cloudNss": true,
      "cloudNssSiemConfiguration": {
        "connectionHeaders": [
          "123:123"
        ],
        "connectionURL": "https://logs.company.net/http/a7adc684-f65c-42af-9519-0a0786656f20",
        "lastSuccessFullTest": 0,
        "maxBatchSize": 512,
        "nssType": "NSS_FOR_WEB",
        "oAuthAuthentication": false,
        "siemType": "OTHER",
        "testConnectivityCode": 0
      },
      "customEscapedCharacter": [
        "ASCII_44",
        "ASCII_92",
        "ASCII_34"
      ],
      "duplicateLogs": 0,
      "epsRateLimit": 0,
      "feedOutputFormat": "\\{ \"sourcetype\" : \"zscalernss-web\", \"event\" : \\{\"datetime\":\"%d{yy}-%02d{mth}-%02d{dd} %02d{hh}:%02d{mm}:%02d{ss}\",\"reason\":\"%s{reason}\",\"event_id\":\"%d{recordid}\",\"protocol\":\"%s{proto}\",\"action\":\"%s{action}\",\"transactionsize\":\"%d{totalsize}\",\"responsesize\":\"%d{respsize}\",\"requestsize\":\"%d{reqsize}\",\"urlcategory\":\"%s{urlcat}\",\"serverip\":\"%s{sip}\",\"requestmethod\":\"%s{reqmethod}\",\"refererURL\":\"%s{ereferer}\",\"useragent\":\"%s{eua}\",\"product\":\"NSS\",\"location\":\"%s{elocation}\",\"ClientIP\":\"%s{cip}\",\"status\":\"%s{respcode}\",\"user\":\"%s{elogin}\",\"url\":\"%s{eurl}\",\"vendor\":\"Zscaler\",\"hostname\":\"%s{ehost}\",\"clientpublicIP\":\"%s{cintip}\",\"threatcategory\":\"%s{malwarecat}\",\"threatname\":\"%s{threatname}\",\"filetype\":\"%s{filetype}\",\"appname\":\"%s{appname}\",\"app_status\":\"%s{app_status}\",\"pagerisk\":\"%d{riskscore}\",\"threatseverity\":\"%s{threatseverity}\",\"department\":\"%s{edepartment}\",\"urlsupercategory\":\"%s{urlsupercat}\",\"appclass\":\"%s{appclass}\",\"dlpengine\":\"%s{dlpeng}\",\"urlclass\":\"%s{urlclass}\",\"threatclass\":\"%s{malwareclass}\",\"dlpdictionaries\":\"%s{dlpdict}\",\"fileclass\":\"%s{fileclass}\",\"bwthrottle\":\"%s{bwthrottle}\",\"contenttype\":\"%s{contenttype}\",\"unscannabletype\":\"%s{unscannabletype}\",\"deviceowner\":\"%s{deviceowner}\",\"devicehostname\":\"%s{devicehostname}\",\"keyprotectiontype\":\"%s{keyprotectiontype}\"\\}\\}\n",
      "feedStatus": "ENABLED",
      "id": 2898,
      "jsonArrayToggle": true,
      "name": "test-feed-2",
      "nssFeedType": "JSON",
      "nssFilter": {
        "securityFeedFilter": false
      },
      "nssLogType": "WEBLOG",
      "timeZone": "GMT",
      "userObfuscation": "DISABLED"
    },
    "recordid": "371",
    "resource": "test-feed-2",
    "result": "SUCCESS",
    "subcategory": "NSS_FEED",
    "time": "2024-11-04 16:34:34.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Logs Downloaded

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when ZIA Audit Logs were downloaded.

MITRE ATT&CK coverage

TacticTechniques
Discovery

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    action = event.deep_get("event", "action", default="ACTION_NOT_FOUND")
    category = event.deep_get("event", "category", default="CATEGORY_NOT_FOUND")
    if action == "DOWNLOAD" and category == "AUDIT_LOGS":
        return True
    return False


def title(event):
    return (
        f"[Zscaler.ZIA]: Audit logs were downloaded by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Logs.Downloaded
Description: This rule detects when ZIA Audit Logs were downloaded.
DisplayName: ZIA Logs Downloaded
Runbook: Verify that this change was planned. If not, make sure no sensitive information was leaked.
Reference: https://help.zscaler.com/zia/about-audit-logs
Enabled: true
Filename: zia_logs_downloaded.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0007:T1654 # Log Enumeration
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is DOWNLOAD
  • event.category is AUDIT_LOGS

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Verify that this change was planned. If not, make sure no sensitive information was leaked.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "DOWNLOAD",
    "adminid": "admin@test.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "AUDIT_LOGS",
    "clientip": "1.2.3.4",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {},
    "preaction": {},
    "recordid": "363",
    "resource": "None",
    "result": "SUCCESS",
    "subcategory": "AUDIT_LOGS",
    "time": "2024-11-04 16:31:24.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Password Expiration

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when password expiration was set/removed.

MITRE ATT&CK coverage

TacticTechniques
Discovery

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    action = event.deep_get("event", "action", default="ACTION_NOT_FOUND")
    category = event.deep_get("event", "category", default="CATEGORY_NOT_FOUND")
    password_exp_pre = event.deep_get(
        "event",
        "preaction",
        "passwordExpirationEnabled",
        default="",
    )
    password_exp_post = event.deep_get(
        "event",
        "postaction",
        "passwordExpirationEnabled",
        default="",
    )
    if action == "UPDATE" and category == "LOGIN" and password_exp_pre != password_exp_post:
        return True
    return False


def title(event):
    return (
        f"[Zscaler.ZIA]: Password expiration setting was changed by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Password.Expiration
Description: This rule detects when password expiration was set/removed.
DisplayName: ZIA Password Expiration
Runbook: Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.
Reference: https://help.zscaler.com/zia/configuring-password-expiration
Enabled: true
Filename: zia_password_expiration.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0007:T1201 # Password Policy Discovery
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is UPDATE
  • event.category is LOGIN
  • event.preaction.passwordExpirationEnabled differs from field event.postaction.passwordExpirationEnabled

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "UPDATE",
    "adminid": "admin@16991311.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "LOGIN",
    "clientip": "123.123.123.123",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {
      "passwordExpirationEnabled": true,
      "passwordExpiryDays": 180
    },
    "preaction": {
      "passwordExpirationEnabled": false,
      "passwordExpiryDays": 180
    },
    "recordid": "331",
    "resource": "None",
    "result": "SUCCESS",
    "subcategory": "PASSWORD_EXPIRY",
    "time": "2024-10-22 22:12:25.000000000"
  },
  "sourcetype": "zscalernss-audit"
}

ZIA Trust Modification

#
Severity
medium
Log types
Zscaler.ZIA.AdminAuditLog
Reference
help.zscaler.com
Source
github.com/panther-labs/panther-analysis

This rule detects when SAML authentication was enabled/disabled.

MITRE ATT&CK coverage

Detection logic

from panther_zscaler_helpers import zia_alert_context, zia_success


def rule(event):
    if not zia_success(event):
        return False
    action = event.deep_get("event", "action", default="ACTION_NOT_FOUND")
    category = event.deep_get("event", "category", default="CATEGORY_NOT_FOUND")
    saml_enabled_pre = event.deep_get("event", "preaction", "samlEnabled", default="")
    saml_enabled_post = event.deep_get("event", "postaction", "samlEnabled", default="")
    # Only alert if both fields are present and have different values
    if (
        action == "UPDATE"
        and category == "ADMINISTRATOR_MANAGEMENT"
        and saml_enabled_pre != ""
        and saml_enabled_post != ""
        and saml_enabled_pre != saml_enabled_post
    ):
        return True
    return False


def title(event):
    return (
        f"[Zscaler.ZIA]: SAML configuration was changed by admin with id "
        f"[{event.deep_get('event', 'adminid', default='<ADMIN_ID_NOT_FOUND>')}]"
    )


def alert_context(event):
    return zia_alert_context(event)

Rule specification

AnalysisType: rule
RuleID: ZIA.Trust.Modification
Description: This rule detects when SAML authentication was enabled/disabled.
DisplayName: ZIA Trust Modification
Runbook: Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.
Reference: https://help.zscaler.com/zia/configuring-saml
Enabled: true
Filename: zia_trust_modification.py
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0004:T1484.002 # Domain or Tenant Policy Modification: Trust Modification
LogTypes:
  - Zscaler.ZIA.AdminAuditLog
DedupPeriodMinutes: 60
Threshold: 1

Stages and Predicates

Fires on Zscaler.ZIA.AdminAuditLog events when all of the conditions below hold.

Condition

  • event.errorcode is None
  • event.result is SUCCESS
  • event.action is UPDATE
  • event.category is ADMINISTRATOR_MANAGEMENT
  • event.preaction.samlEnabled is not ""
  • event.postaction.samlEnabled is not ""
  • event.preaction.samlEnabled differs from field event.postaction.samlEnabled

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actionevent.action
admin_idevent.adminid
categoryevent.category
client_ipevent.clientip
preactionevent.preaction
postactionevent.postaction

Response runbook

Verify that this change was planned. If not, revert the change and ensure this doesn't happen again.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "event": {
    "action": "UPDATE",
    "adminid": "admin@16991311.zscalerbeta.net",
    "auditlogtype": "ZIA",
    "category": "ADMINISTRATOR_MANAGEMENT",
    "clientip": "123.123.123.123",
    "errorcode": "None",
    "interface": "UI",
    "postaction": {
      "certFilename": "abc.crt",
      "productId": 0,
      "samlEnabled": true
    },
    "preaction": {
      "productId": 0,
      "samlEnabled": false
    },
    "recordid": "332",
    "resource": "None",
    "result": "SUCCESS",
    "subcategory": "ADMINISTRATOR_SAML",
    "time": "2024-10-22 22:13:23.000000000"
  },
  "sourcetype": "zscalernss-audit"
}