Snowflake Password Spray


Description

Detects password spraying attacks against Snowflake by tracking the number of distinct user accounts targeted by failed login attempts from the same source IP address within a short timeframe. Unlike brute force against a single account, password spraying distributes attempts across many accounts to evade lockout policies. This rule complements Snowflake.Stream.BruteForceByIp (same IP, any accounts) and Snowflake.PotentialBruteForceSuccess.Group (brute force followed by confirmed login).

Query · python

def rule(event):
    return (
        event.get("EVENT_TYPE") == "LOGIN"
        and event.get("IS_SUCCESS") == "NO"
        and event.get("ERROR_MESSAGE") != "OVERFLOW_FAILURE_EVENTS_ELIDED"
    )


def unique(event):
    return event.get("USER_NAME", "UNKNOWN_USER")


def dedup(event):
    return event.get("CLIENT_IP", "UNKNOWN_IP")


def title(event):
    client_ip = event.get("CLIENT_IP", "UNKNOWN_IP")
    return f"[Snowflake] Password spray detected from IP [{client_ip}] targeting multiple accounts"


def severity(event):
    # Downgrade JWT key mismatches to INFO as these are typically automation misconfiguration
    if event.get("ERROR_MESSAGE") == "JWT_TOKEN_INVALID_PUBLIC_KEY_FINGERPRINT_MISMATCH":
        return "INFO"
    return "DEFAULT"


def alert_context(event):
    return {
        "client_ip": event.get("CLIENT_IP", "UNKNOWN_IP"),
        "user_name": event.get("USER_NAME", "UNKNOWN_USER"),
        "client_type": event.get("REPORTED_CLIENT_TYPE"),
        "error_code": event.get("ERROR_CODE"),
        "error_message": event.get("ERROR_MESSAGE"),
    }

Analyst notes

  1. Review the distinct usernames targeted from the source IP and determine if any are privileged accounts (ACCOUNTADMIN, SYSADMIN, SECURITYADMIN) which would elevate the risk significantly
  2. Query Snowflake LoginHistory for all events with IS_SUCCESS = 'YES' from the same CLIENT_IP within the past 60 minutes to determine if any spray attempt succeeded — the alert context shows only the last triggering username, not all targeted accounts
  3. Check if the source IP belongs to known anonymization infrastructure (VPNs, Tor exit nodes, cloud provider ranges) and cross-reference against other Snowflake tenants if applicable
Raw source Snowflake Password Spray · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
RuleID: "Snowflake.Stream.PasswordSpray"
DisplayName: "Snowflake Password Spray"
Filename: snowflake_stream_password_spray.py
Enabled: true
Status: Experimental
LogTypes:
  - Snowflake.LoginHistory
Severity: Medium
Threshold: 5
DedupPeriodMinutes: 60
Description: >
  Detects password spraying attacks against Snowflake by tracking the number of distinct
  user accounts targeted by failed login attempts from the same source IP address within
  a short timeframe. Unlike brute force against a single account, password spraying
  distributes attempts across many accounts to evade lockout policies. This rule
  complements Snowflake.Stream.BruteForceByIp (same IP, any accounts) and
  Snowflake.PotentialBruteForceSuccess.Group (brute force followed by confirmed login).
Runbook: |
  1. Review the distinct usernames targeted from the source IP and determine if any are privileged accounts (ACCOUNTADMIN, SYSADMIN, SECURITYADMIN) which would elevate the risk significantly
  2. Query Snowflake LoginHistory for all events with IS_SUCCESS = 'YES' from the same CLIENT_IP within the past 60 minutes to determine if any spray attempt succeeded — the alert context shows only the last triggering username, not all targeted accounts
  3. Check if the source IP belongs to known anonymization infrastructure (VPNs, Tor exit nodes, cloud provider ranges) and cross-reference against other Snowflake tenants if applicable
Reference: https://docs.snowflake.com/en/sql-reference/account-usage/login_history
Tags:
  - Snowflake
  - Credential Access
  - Brute Force
Reports:
  MITRE ATT&CK:
    - TA0006:T1110.003 # Credential Access: Password Spraying
Tests:
  - Name: Match - Failed Login
    ExpectedResult: true
    Log:
      {
        "p_event_time": "2024-10-08 14:38:46.061000000",
        "p_log_type": "Snowflake.LoginHistory",
        "CLIENT_IP": "1.2.3.4",
        "EVENT_ID": "393754014361778",
        "EVENT_TIMESTAMP": "2024-10-08 14:38:46.061000000",
        "EVENT_TYPE": "LOGIN",
        "FIRST_AUTHENTICATION_FACTOR": "PASSWORD",
        "IS_SUCCESS": "NO",
        "RELATED_EVENT_ID": "0",
        "REPORTED_CLIENT_TYPE": "OTHER",
        "REPORTED_CLIENT_VERSION": "1.11.1",
        "USER_NAME": "ckent@dailyplanet.org"
      }
  - Name: Match - Failed Login Different User Same IP
    ExpectedResult: true
    Log:
      {
        "p_event_time": "2024-10-08 14:39:00.000000000",
        "p_log_type": "Snowflake.LoginHistory",
        "CLIENT_IP": "1.2.3.4",
        "EVENT_ID": "393754014361779",
        "EVENT_TIMESTAMP": "2024-10-08 14:39:00.000000000",
        "EVENT_TYPE": "LOGIN",
        "FIRST_AUTHENTICATION_FACTOR": "PASSWORD",
        "IS_SUCCESS": "NO",
        "RELATED_EVENT_ID": "0",
        "REPORTED_CLIENT_TYPE": "OTHER",
        "REPORTED_CLIENT_VERSION": "1.11.1",
        "USER_NAME": "luthor@lexcorp.com"
      }
  - Name: No Match - Successful Login
    ExpectedResult: false
    Log:
      {
        "p_event_time": "2024-10-08 14:38:46.061000000",
        "p_log_type": "Snowflake.LoginHistory",
        "CLIENT_IP": "1.1.1.1",
        "EVENT_ID": "393754014361780",
        "EVENT_TIMESTAMP": "2024-10-08 14:38:46.061000000",
        "EVENT_TYPE": "LOGIN",
        "FIRST_AUTHENTICATION_FACTOR": "PASSWORD",
        "IS_SUCCESS": "YES",
        "RELATED_EVENT_ID": "0",
        "REPORTED_CLIENT_TYPE": "OTHER",
        "REPORTED_CLIENT_VERSION": "1.11.1",
        "USER_NAME": "ckent@dailyplanet.org"
      }
  - Name: No Match - Overflow Failure Event
    ExpectedResult: false
    Log:
      {
        "p_event_time": "2024-11-15 00:12:24.288000000",
        "p_log_type": "Snowflake.LoginHistory",
        "CLIENT_IP": "0.0.0.0",
        "ERROR_CODE": 390156,
        "ERROR_MESSAGE": "OVERFLOW_FAILURE_EVENTS_ELIDED",
        "EVENT_ID": "16592193114297018",
        "EVENT_TIMESTAMP": "2024-11-15 00:12:24.288000000",
        "EVENT_TYPE": "LOGIN",
        "IS_SUCCESS": "NO",
        "RELATED_EVENT_ID": "0",
        "REPORTED_CLIENT_TYPE": "OTHER",
        "REPORTED_CLIENT_VERSION": "0",
        "USER_NAME": "luthor@lexcorp.com"
      }
  - Name: Match - JWT Key Mismatch Downgraded to Info
    ExpectedResult: true
    Log:
      {
        "p_event_time": "2024-10-08 14:38:46.061000000",
        "p_log_type": "Snowflake.LoginHistory",
        "CLIENT_IP": "1.2.3.4",
        "ERROR_CODE": 394304,
        "ERROR_MESSAGE": "JWT_TOKEN_INVALID_PUBLIC_KEY_FINGERPRINT_MISMATCH",
        "EVENT_ID": "393754014361782",
        "EVENT_TIMESTAMP": "2024-10-08 14:38:46.061000000",
        "EVENT_TYPE": "LOGIN",
        "FIRST_AUTHENTICATION_FACTOR": "PASSWORD",
        "IS_SUCCESS": "NO",
        "RELATED_EVENT_ID": "0",
        "REPORTED_CLIENT_TYPE": "OTHER",
        "REPORTED_CLIENT_VERSION": "1.11.1",
        "USER_NAME": "brucewayne@waynecorp.com"
      }
  - Name: No Match - Non-Login Event
    ExpectedResult: false
    Log:
      {
        "p_event_time": "2024-10-08 14:38:46.061000000",
        "p_log_type": "Snowflake.LoginHistory",
        "CLIENT_IP": "1.2.3.4",
        "EVENT_ID": "393754014361781",
        "EVENT_TIMESTAMP": "2024-10-08 14:38:46.061000000",
        "EVENT_TYPE": "LOGOUT",
        "IS_SUCCESS": "YES",
        "RELATED_EVENT_ID": "0",
        "REPORTED_CLIENT_TYPE": "OTHER",
        "REPORTED_CLIENT_VERSION": "1.11.1",
        "USER_NAME": "ckent@dailyplanet.org"
      }


# ------ paired body: snowflake_stream_password_spray.py ------

def rule(event):
    return (
        event.get("EVENT_TYPE") == "LOGIN"
        and event.get("IS_SUCCESS") == "NO"
        and event.get("ERROR_MESSAGE") != "OVERFLOW_FAILURE_EVENTS_ELIDED"
    )


def unique(event):
    return event.get("USER_NAME", "UNKNOWN_USER")


def dedup(event):
    return event.get("CLIENT_IP", "UNKNOWN_IP")


def title(event):
    client_ip = event.get("CLIENT_IP", "UNKNOWN_IP")
    return f"[Snowflake] Password spray detected from IP [{client_ip}] targeting multiple accounts"


def severity(event):
    # Downgrade JWT key mismatches to INFO as these are typically automation misconfiguration
    if event.get("ERROR_MESSAGE") == "JWT_TOKEN_INVALID_PUBLIC_KEY_FINGERPRINT_MISMATCH":
        return "INFO"
    return "DEFAULT"


def alert_context(event):
    return {
        "client_ip": event.get("CLIENT_IP", "UNKNOWN_IP"),
        "user_name": event.get("USER_NAME", "UNKNOWN_USER"),
        "client_type": event.get("REPORTED_CLIENT_TYPE"),
        "error_code": event.get("ERROR_CODE"),
        "error_message": event.get("ERROR_MESSAGE"),
    }

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.