Potential Okta Credential Stuffing (Single Source)

Detects potential credential stuffing attacks where a single source IP attempts authentication against many Okta user accounts with minimal attempts per user, indicating the use of breached credential lists.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2024/06/17"
  3integration = ["okta"]
  4maturity = "production"
  5updated_date = "2026/02/19"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Detects potential credential stuffing attacks where a single source IP attempts authentication against many Okta
 11user accounts with minimal attempts per user, indicating the use of breached credential lists.
 12"""
 13false_positives = [
 14    "Corporate proxy or VPN exit nodes may aggregate traffic from multiple legitimate users.",
 15    "Shared systems such as kiosks or conference room computers may have multiple users authenticating.",
 16]
 17from = "now-15m"
 18language = "esql"
 19license = "Elastic License v2"
 20name = "Potential Okta Credential Stuffing (Single Source)"
 21note = """## Triage and analysis
 22
 23### Investigating Potential Okta Credential Stuffing (Single Source)
 24
 25This rule identifies a single source IP attempting authentication against many user accounts with minimal attempts per user. This pattern indicates credential stuffing where attackers rapidly test breached username and password pairs.
 26
 27#### Possible investigation steps
 28- Identify the source IP and determine if it belongs to known proxy, VPN, or cloud infrastructure.
 29- Review the list of targeted user accounts and check if any authentications succeeded.
 30- Examine the user agent strings for signs of automation or scripting tools.
 31- Check if Okta flagged the source as a known threat or proxy.
 32- Determine if any targeted accounts have elevated privileges or access to sensitive systems.
 33- Review the geographic location and ASN of the source IP for anomalies.
 34
 35### False positive analysis
 36- Corporate proxies or VPN exit nodes may aggregate traffic from multiple legitimate users.
 37- Shared systems such as kiosks or conference room computers may have multiple users authenticating.
 38- Legitimate SSO integrations may generate multiple authentication attempts from a single source.
 39
 40### Response and remediation
 41- If attack is confirmed, block the source IP at the network perimeter.
 42- Reset passwords for any accounts that may have been compromised.
 43- Enable or strengthen MFA for targeted accounts.
 44- Review Okta sign-on policies to add additional friction for suspicious authentication patterns.
 45- If this is a known legitimate source, consider adding an exception for the IP or ASN.
 46"""
 47references = [
 48    "https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta",
 49    "https://www.okta.com/identity-101/brute-force/",
 50    "https://support.okta.com/help/s/article/How-does-the-Device-Token-work?language=en_US",
 51    "https://developer.okta.com/docs/reference/api/event-types/",
 52    "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy",
 53    "https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection",
 54    "https://www.okta.com/resources/whitepaper-how-adaptive-mfa-can-help-in-mitigating-brute-force-attacks/",
 55    "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security",
 56    "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta",
 57]
 58risk_score = 47
 59rule_id = "94e734c0-2cda-11ef-84e1-f661ea17fbce"
 60setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule."
 61severity = "medium"
 62tags = [
 63    "Domain: Identity",
 64    "Use Case: Identity and Access Audit",
 65    "Data Source: Okta",
 66    "Data Source: Okta System Logs",
 67    "Tactic: Credential Access",
 68    "Resources: Investigation Guide",
 69]
 70timestamp_override = "event.ingested"
 71type = "esql"
 72
 73query = '''
 74FROM logs-okta.system-* METADATA _id, _version, _index
 75| WHERE
 76    event.dataset == "okta.system"
 77    AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start")
 78    AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT")
 79    AND okta.actor.alternate_id IS NOT NULL
 80// Build user-source context as JSON for enrichment
 81| EVAL Esql.user_source_info = CONCAT(
 82    "{\"user\":\"", okta.actor.alternate_id,
 83    "\",\"ip\":\"", COALESCE(okta.client.ip::STRING, "unknown"),
 84    "\",\"user_agent\":\"", COALESCE(okta.client.user_agent.raw_user_agent, "unknown"), "\"}"
 85  )
 86// FIRST STATS: Aggregate by (IP, user) to get per-user attempt counts
 87// This prevents skew from outlier users with many attempts
 88| STATS
 89    Esql.user_attempts = COUNT(*),
 90    Esql.user_dt_hashes = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash),
 91    Esql.user_source_info = VALUES(Esql.user_source_info),
 92    Esql.user_agents_per_user = VALUES(okta.client.user_agent.raw_user_agent),
 93    Esql.devices_per_user = VALUES(okta.client.device),
 94    Esql.is_proxy = VALUES(okta.security_context.is_proxy),
 95    Esql.geo_country = VALUES(client.geo.country_name),
 96    Esql.geo_city = VALUES(client.geo.city_name),
 97    Esql.asn_number = VALUES(source.as.number),
 98    Esql.asn_org = VALUES(source.as.organization.name),
 99    Esql.threat_suspected = VALUES(okta.debug_context.debug_data.threat_suspected),
100    Esql.risk_level = VALUES(okta.debug_context.debug_data.risk_level),
101    Esql.risk_reasons = VALUES(okta.debug_context.debug_data.risk_reasons),
102    Esql.event_actions = VALUES(event.action),
103    Esql.first_seen_user = MIN(@timestamp),
104    Esql.last_seen_user = MAX(@timestamp)
105  BY okta.client.ip, okta.actor.alternate_id
106// SECOND STATS: Aggregate by IP to detect credential stuffing pattern
107// Now we can accurately measure the distribution of attempts across users
108| STATS
109    Esql.unique_users = COUNT(*),
110    Esql.total_attempts = SUM(Esql.user_attempts),
111    Esql.max_attempts_per_user = MAX(Esql.user_attempts),
112    Esql.min_attempts_per_user = MIN(Esql.user_attempts),
113    Esql.avg_attempts_per_user = AVG(Esql.user_attempts),
114    Esql.users_with_single_attempt = SUM(CASE(Esql.user_attempts == 1, 1, 0)),
115    Esql.users_with_few_attempts = SUM(CASE(Esql.user_attempts <= 2, 1, 0)),
116    Esql.first_seen = MIN(Esql.first_seen_user),
117    Esql.last_seen = MAX(Esql.last_seen_user),
118    Esql.target_users = VALUES(okta.actor.alternate_id),
119    Esql.user_source_mapping = VALUES(Esql.user_source_info),
120    Esql.event_action_values = VALUES(Esql.event_actions),
121    Esql.user_agent_values = VALUES(Esql.user_agents_per_user),
122    Esql.device_values = VALUES(Esql.devices_per_user),
123    Esql.is_proxy_values = VALUES(Esql.is_proxy),
124    Esql.geo_country_values = VALUES(Esql.geo_country),
125    Esql.geo_city_values = VALUES(Esql.geo_city),
126    Esql.source_asn_values = VALUES(Esql.asn_number),
127    Esql.source_asn_org_values = VALUES(Esql.asn_org),
128    Esql.threat_suspected_values = VALUES(Esql.threat_suspected),
129    Esql.risk_level_values = VALUES(Esql.risk_level),
130    Esql.risk_reasons_values = VALUES(Esql.risk_reasons)
131  BY okta.client.ip
132// Calculate stuffing signature: most users should have very few attempts
133| EVAL Esql.pct_users_few_attempts = Esql.users_with_few_attempts * 100.0 / Esql.unique_users
134// Credential stuffing: many users, most with 1-2 attempts each, low max per user
135// Stacked stats gives us accurate per-user distribution instead of skewed averages
136| WHERE
137    Esql.total_attempts >= 25
138    AND Esql.unique_users >= 15
139    AND Esql.max_attempts_per_user <= 2
140    AND Esql.pct_users_few_attempts >= 80.0
141| SORT Esql.unique_users DESC
142| KEEP Esql.*, okta.client.ip
143'''
144
145
146[[rule.threat]]
147framework = "MITRE ATT&CK"
148[[rule.threat.technique]]
149id = "T1110"
150name = "Brute Force"
151reference = "https://attack.mitre.org/techniques/T1110/"
152[[rule.threat.technique.subtechnique]]
153id = "T1110.004"
154name = "Credential Stuffing"
155reference = "https://attack.mitre.org/techniques/T1110/004/"
156
157
158[rule.threat.tactic]
159id = "TA0006"
160name = "Credential Access"
161reference = "https://attack.mitre.org/tactics/TA0006/"

Triage and analysis

Investigating Potential Okta Credential Stuffing (Single Source)

This rule identifies a single source IP attempting authentication against many user accounts with minimal attempts per user. This pattern indicates credential stuffing where attackers rapidly test breached username and password pairs.

Possible investigation steps

  • Identify the source IP and determine if it belongs to known proxy, VPN, or cloud infrastructure.
  • Review the list of targeted user accounts and check if any authentications succeeded.
  • Examine the user agent strings for signs of automation or scripting tools.
  • Check if Okta flagged the source as a known threat or proxy.
  • Determine if any targeted accounts have elevated privileges or access to sensitive systems.
  • Review the geographic location and ASN of the source IP for anomalies.

False positive analysis

  • Corporate proxies or VPN exit nodes may aggregate traffic from multiple legitimate users.
  • Shared systems such as kiosks or conference room computers may have multiple users authenticating.
  • Legitimate SSO integrations may generate multiple authentication attempts from a single source.

Response and remediation

  • If attack is confirmed, block the source IP at the network perimeter.
  • Reset passwords for any accounts that may have been compromised.
  • Enable or strengthen MFA for targeted accounts.
  • Review Okta sign-on policies to add additional friction for suspicious authentication patterns.
  • If this is a known legitimate source, consider adding an exception for the IP or ASN.

References

Related rules

to-top