From c7954465f397ffc8d406bee9da8779720b9bcf24 Mon Sep 17 00:00:00 2001 From: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:36:25 -0500 Subject: [PATCH] [Rule Tuning] Okta Credential Stuffing, Password Spraying, and Brute Force Detection Improvements (#5723) * [Rule Tuning] Okta Credential Stuffing, Password Spraying, and Brute Force Detection Improvements Fixes #5722 * adding adjustments * adjusts made; still testing * adjustments to logic; names; tags; standardization, etc. * added missing tag * updated reference rules --- ...for_multiple_users_from_single_source.toml | 143 ----------- ...kta_brute_force_device_token_rotation.toml | 135 ++++++++++ ..._access_okta_brute_force_multi_source.toml | 144 +++++++++++ ...okta_brute_force_or_password_spraying.toml | 96 -------- ...kta_credential_stuffing_single_source.toml | 162 ++++++++++++ ...e_device_token_hashes_for_single_user.toml | 146 ----------- ...cess_okta_password_spray_multi_source.toml | 150 +++++++++++ ...ess_okta_password_spray_single_source.toml | 170 +++++++++++++ ...cessful_login_after_credential_attack.toml | 233 ++++++++++++++++++ 9 files changed, 994 insertions(+), 385 deletions(-) delete mode 100644 rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml create mode 100644 rules/integrations/okta/credential_access_okta_brute_force_device_token_rotation.toml create mode 100644 rules/integrations/okta/credential_access_okta_brute_force_multi_source.toml delete mode 100644 rules/integrations/okta/credential_access_okta_brute_force_or_password_spraying.toml create mode 100644 rules/integrations/okta/credential_access_okta_credential_stuffing_single_source.toml delete mode 100644 rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml create mode 100644 rules/integrations/okta/credential_access_okta_password_spray_multi_source.toml create mode 100644 rules/integrations/okta/credential_access_okta_password_spray_single_source.toml create mode 100644 rules/integrations/okta/credential_access_okta_successful_login_after_credential_attack.toml diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml deleted file mode 100644 index a07b08b6b..000000000 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml +++ /dev/null @@ -1,143 +0,0 @@ -[metadata] -creation_date = "2024/06/17" -integration = ["okta"] -maturity = "production" -updated_date = "2025/09/25" - -[rule] -author = ["Elastic"] -description = """ -Detects when a certain threshold of Okta user authentication events are reported for multiple users from the same client -address. Adversaries may attempt to launch a credential stuffing or password spraying attack from the same device by -using a list of known usernames and passwords to gain unauthorized access to user accounts. -""" -false_positives = [ - "Users may share an endpoint related to work or personal use in which separate Okta accounts are used.", - "Shared systems such as Kiosks and conference room computers may be used by multiple users.", -] -from = "now-9m" -language = "esql" -license = "Elastic License v2" -name = "Multiple Okta User Authentication Events with Client Address" -note = """## Triage and analysis - -### Investigating Multiple Okta User Authentication Events with Client Address - -This rule detects when a certain threshold of Okta user authentication events are reported for multiple users from the same client address. Adversaries may attempt to launch a credential stuffing attack from the same device by using a list of known usernames and passwords to gain unauthorized access to user accounts. Note that Okta does not log unrecognized usernames supplied during authentication attempts, so this rule may not detect all credential stuffing attempts or may indicate a targeted attack. - -#### Possible investigation steps: -Since this is an ESQL rule, the `okta.actor.alternate_id` and `okta.client.ip` values can be used to pivot into the raw authentication events related to this activity. -- Identify the users involved in this action by examining the `okta.actor.id`, `okta.actor.type`, `okta.actor.alternate_id`, and `okta.actor.display_name` fields. -- Determine the device client used for these actions by analyzing `okta.client.ip`, `okta.client.user_agent.raw_user_agent`, `okta.client.zone`, `okta.client.device`, and `okta.client.id` fields. -- Review the `okta.security_context.is_proxy` field to determine if the device is a proxy. - - If the device is a proxy, this may indicate that a user is using a proxy to access multiple accounts for password spraying. -- With the list of `okta.actor.alternate_id` values, review `event.outcome` results to determine if the authentication was successful. - - If the authentication was successful for any user, pivoting to `event.action` values for those users may provide additional context. -- With Okta end users identified, review the `okta.debug_context.debug_data.dt_hash` field. - - Historical analysis should indicate if this device token hash is commonly associated with the user. -- Review the `okta.event_type` field to determine the type of authentication event that occurred. - - If the event type is `user.authentication.sso`, the user may have legitimately started a session via a proxy for security or privacy reasons. - - If the event type is `user.authentication.password`, the user may be using a proxy to access multiple accounts for password spraying. - - If the event type is `user.session.start`, the source may have attempted to establish a session via the Okta authentication API. -- Examine the `okta.outcome.result` field to determine if the authentication was successful. -- Review the past activities of the actor(s) involved in this action by checking their previous actions. -- Evaluate the actions that happened just before and after this event in the `okta.event_type` field to help understand the full context of the activity. - - This may help determine the authentication and authorization actions that occurred between the user, Okta and application. - -### False positive analysis: -- A user may have legitimately started a session via a proxy for security or privacy reasons. -- Users may share an endpoint related to work or personal use in which separate Okta accounts are used. - - Architecturally, this shared endpoint may leverage a proxy for security or privacy reasons. - - Shared systems such as Kiosks and conference room computers may be used by multiple users. - - Shared working spaces may have a single endpoint that is used by multiple users. - -### Response and remediation: -- Review the profile of the users involved in this action to determine if proxy usage may be expected. -- If the user is legitimate and the authentication behavior is not suspicious based on device analysis, no action is required. -- If the user is legitimate but the authentication behavior is suspicious, consider resetting passwords for the users involves and enabling multi-factor authentication (MFA). - - If MFA is already enabled, consider resetting MFA for the users. -- If any of the users are not legitimate, consider deactivating the user's account. -- Conduct a review of Okta policies and ensure they are in accordance with security best practices. -- Check with internal IT teams to determine if the accounts involved recently had MFA reset at the request of the user. - - If so, confirm with the user this was a legitimate request. - - If so and this was not a legitimate request, consider deactivating the user's account temporarily. - - Reset passwords and reset MFA for the user. -- If this is a false positive, consider adding the `okta.debug_context.debug_data.dt_hash` field to the `exceptions` list in the rule. - - This will prevent future occurrences of this event for this device from triggering the rule. - - Alternatively adding `okta.client.ip` or a CIDR range to the `exceptions` list can prevent future occurrences of this event from triggering the rule. - - This should be done with caution as it may prevent legitimate alerts from being generated. -""" -references = [ - "https://support.okta.com/help/s/article/How-does-the-Device-Token-work?language=en_US", - "https://developer.okta.com/docs/reference/api/event-types/", - "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", - "https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection", - "https://www.okta.com/resources/whitepaper-how-adaptive-mfa-can-help-in-mitigating-brute-force-attacks/", - "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", - "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", -] -risk_score = 21 -rule_id = "94e734c0-2cda-11ef-84e1-f661ea17fbce" -setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule." -severity = "low" -tags = [ - "Use Case: Identity and Access Audit", - "Data Source: Okta", - "Tactic: Credential Access", - "Resources: Investigation Guide", -] -timestamp_override = "event.ingested" -type = "esql" - -query = ''' -from logs-okta* -| where - event.dataset == "okta.system" and - (event.action == "user.session.start" or event.action like "user.authentication.*") and - okta.outcome.reason == "INVALID_CREDENTIALS" -| keep - okta.client.ip, - okta.actor.alternate_id, - okta.actor.id, - event.action, - okta.outcome.reason -| stats - Esql.okta_actor_id_count_distinct = count_distinct(okta.actor.id) - by - okta.client.ip, - okta.actor.alternate_id -| where - Esql.okta_actor_id_count_distinct > 5 -| sort - Esql.okta_actor_id_count_distinct desc -''' - - -[[rule.threat]] -framework = "MITRE ATT&CK" -[[rule.threat.technique]] -id = "T1110" -name = "Brute Force" -reference = "https://attack.mitre.org/techniques/T1110/" -[[rule.threat.technique.subtechnique]] -id = "T1110.003" -name = "Password Spraying" -reference = "https://attack.mitre.org/techniques/T1110/003/" - - -[[rule.threat.technique]] -id = "T1110" -name = "Brute Force" -reference = "https://attack.mitre.org/techniques/T1110/" -[[rule.threat.technique.subtechnique]] -id = "T1110.004" -name = "Credential Stuffing" -reference = "https://attack.mitre.org/techniques/T1110/004/" - - - -[rule.threat.tactic] -id = "TA0006" -name = "Credential Access" -reference = "https://attack.mitre.org/tactics/TA0006/" - diff --git a/rules/integrations/okta/credential_access_okta_brute_force_device_token_rotation.toml b/rules/integrations/okta/credential_access_okta_brute_force_device_token_rotation.toml new file mode 100644 index 000000000..72b54d820 --- /dev/null +++ b/rules/integrations/okta/credential_access_okta_brute_force_device_token_rotation.toml @@ -0,0 +1,135 @@ +[metadata] +creation_date = "2024/06/17" +integration = ["okta"] +maturity = "production" +updated_date = "2026/02/19" + +[rule] +author = ["Elastic"] +description = """ +Detects potential brute force attacks against a single Okta user account where excessive unique device token hashes +are generated, indicating automated tooling that fails to persist browser cookies between attempts. +""" +false_positives = [ + "A user experiencing login issues may generate multiple device tokens through repeated legitimate attempts.", + "Automated testing or monitoring tools that do not persist cookies may trigger this rule.", +] +from = "now-30m" +language = "esql" +license = "Elastic License v2" +name = "Potential Okta Brute Force (Device Token Rotation)" +note = """## Triage and analysis + +### Investigating Potential Okta Brute Force (Device Token Rotation) + +This rule identifies excessive unique device token hashes generated for a single user account, indicating automated brute force tooling that fails to persist browser cookies between authentication attempts. + +#### Possible investigation steps +- Identify the targeted user account and determine if it has elevated privileges or sensitive access. +- Review the source IP and check if it belongs to known proxy, VPN, or cloud infrastructure. +- Examine user agent strings for signs of automation, scripting tools, or inconsistent browser fingerprints. +- Check if Okta flagged the source as a known threat or proxy. +- Determine if any authentication attempts succeeded following the failed attempts. +- Review the user's recent activity for signs of account compromise. + +### False positive analysis +- Users experiencing login issues may generate multiple device tokens through repeated legitimate attempts. +- Automated testing or monitoring tools that do not persist cookies may trigger this rule. +- Browser extensions or privacy tools that clear cookies between requests may cause device token rotation. + +### Response and remediation +- If attack is confirmed, reset the user's password immediately. +- Block the source IP at the network perimeter. +- Review and potentially reset MFA for the targeted account. +- Monitor for any successful authentication that may indicate compromise. +- Contact the user to verify if they experienced legitimate login issues. +""" +references = [ + "https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta", + "https://www.okta.com/identity-101/brute-force/", + "https://support.okta.com/help/s/article/How-does-the-Device-Token-work?language=en_US", + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", + "https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection", + "https://www.okta.com/resources/whitepaper-how-adaptive-mfa-can-help-in-mitigating-brute-force-attacks/", + "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", + "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", +] +risk_score = 21 +rule_id = "23f18264-2d6d-11ef-9413-f661ea17fbce" +setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule." +severity = "low" +tags = [ + "Domain: Identity", + "Use Case: Identity and Access Audit", + "Data Source: Okta", + "Tactic: Credential Access", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "esql" + +query = ''' +FROM logs-okta.system-* METADATA _id, _version, _index +| WHERE + event.dataset == "okta.system" + AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start") + AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT") + AND okta.actor.alternate_id IS NOT NULL + // Primary authn endpoint; sessions API provides additional coverage + AND ( + okta.debug_context.debug_data.request_uri == "/api/v1/authn" + OR okta.debug_context.debug_data.request_uri LIKE "/api/v1/sessions*" + ) +// Track whether each event has a device token +| EVAL has_dt_hash = CASE(okta.debug_context.debug_data.dt_hash IS NOT NULL, 1, 0) +// Aggregate by IP + user to detect single-user brute force +| STATS + Esql.unique_dt_hashes = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash), + Esql.total_attempts = COUNT(*), + Esql.attempts_with_dt = SUM(has_dt_hash), + Esql.unique_user_agents = COUNT_DISTINCT(okta.client.user_agent.raw_user_agent), + Esql.first_seen = MIN(@timestamp), + Esql.last_seen = MAX(@timestamp), + Esql.dt_hash_values = VALUES(okta.debug_context.debug_data.dt_hash), + Esql.event_action_values = VALUES(event.action), + Esql.user_agent_values = VALUES(okta.client.user_agent.raw_user_agent), + Esql.device_values = VALUES(okta.client.device), + Esql.is_proxy_values = VALUES(okta.security_context.is_proxy), + Esql.geo_country_values = VALUES(client.geo.country_name), + Esql.geo_city_values = VALUES(client.geo.city_name), + Esql.source_asn_values = VALUES(source.as.number), + Esql.source_asn_org_values = VALUES(source.as.organization.name), + Esql.threat_suspected_values = VALUES(okta.debug_context.debug_data.threat_suspected), + Esql.risk_level_values = VALUES(okta.debug_context.debug_data.risk_level), + Esql.risk_reasons_values = VALUES(okta.debug_context.debug_data.risk_reasons) + BY okta.client.ip, okta.actor.alternate_id +// Calculate automation detection metrics (float-safe division) +| EVAL Esql.dt_coverage = Esql.attempts_with_dt * 1.0 / Esql.total_attempts, + Esql.dt_per_attempt = Esql.unique_dt_hashes * 1.0 / Esql.total_attempts +// Detection branches: +// A) Many unique DT hashes relative to attempts = tooling generating new tokens per attempt +// B) High attempts + very low DT coverage = cookie-less automation (no DT sent at all) +// C) Multiple user agents for same user = evasion or automation +| WHERE + (Esql.unique_dt_hashes >= 7 AND Esql.total_attempts >= 10 AND Esql.dt_per_attempt >= 0.5) + OR (Esql.total_attempts >= 12 AND Esql.dt_coverage < 0.15) + OR (Esql.total_attempts >= 10 AND Esql.unique_user_agents >= 5) +| SORT Esql.total_attempts DESC +| KEEP Esql.*, okta.client.ip, okta.actor.alternate_id +''' + + +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1110" +name = "Brute Force" +reference = "https://attack.mitre.org/techniques/T1110/" + + +[rule.threat.tactic] +id = "TA0006" +name = "Credential Access" +reference = "https://attack.mitre.org/tactics/TA0006/" + diff --git a/rules/integrations/okta/credential_access_okta_brute_force_multi_source.toml b/rules/integrations/okta/credential_access_okta_brute_force_multi_source.toml new file mode 100644 index 000000000..fa50b1ab8 --- /dev/null +++ b/rules/integrations/okta/credential_access_okta_brute_force_multi_source.toml @@ -0,0 +1,144 @@ +[metadata] +creation_date = "2026/02/19" +integration = ["okta"] +maturity = "production" +updated_date = "2026/02/19" + +[rule] +author = ["Elastic"] +description = """ +Detects potential brute force attacks against a single Okta user account from multiple source IPs, indicating +attackers rotating through proxy infrastructure to evade IP-based detection. +""" +false_positives = [ + "Users with legitimate multi-location access (mobile + home + office) experiencing concurrent login issues.", + "Shared service accounts accessed from multiple legitimate infrastructure IPs.", +] +from = "now-30m" +language = "esql" +license = "Elastic License v2" +name = "Potential Okta Brute Force (Multi-Source)" +note = """## Triage and analysis + +### Investigating Potential Okta Brute Force (Multi-Source) + +This rule identifies a single user account receiving failed authentication attempts from multiple unique source IPs. This pattern indicates attackers rotating through proxy infrastructure to evade IP-based detection while targeting a specific account. + +#### Possible investigation steps +- Identify the targeted user account and determine if it has elevated privileges or sensitive access. +- Review the geographic distribution of source IPs for anomalies such as multiple countries or unusual locations. +- Examine the ASN ownership of source IPs for signs of proxy, VPN, or cloud infrastructure. +- Check if Okta flagged any of the sources as known threats or proxies. +- Determine if any authentication attempts succeeded following the failed attempts. +- Review the user's recent activity for signs of account compromise. + +### False positive analysis +- Users traveling internationally with mobile devices may generate failed attempts from multiple locations. +- Service accounts accessed from distributed legitimate infrastructure may trigger this rule. +- Corporate VPN exit nodes spread across regions could appear as multiple IPs for a single user. + +### Response and remediation +- If attack is confirmed, reset the user's password immediately. +- Review and potentially reset MFA for the targeted account. +- Block attacking IP addresses at the network perimeter. +- Consider implementing geo-restrictions for the targeted account if dispersed access is not expected. +- Monitor for any successful authentication that may indicate compromise. +""" +references = [ + "https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta", + "https://www.okta.com/identity-101/brute-force/", + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", + "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", + "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", +] +risk_score = 47 +rule_id = "5889760c-9858-4b4b-879c-e299df493295" +setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule." +severity = "medium" +tags = [ + "Domain: Identity", + "Use Case: Identity and Access Audit", + "Use Case: Threat Detection", + "Data Source: Okta", + "Data Source: Okta System Logs", + "Tactic: Credential Access", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "esql" + +query = ''' +FROM logs-okta.system-* METADATA _id, _version, _index +| WHERE event.dataset == "okta.system" + AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start") + AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT") + AND okta.actor.alternate_id IS NOT NULL + +// Create source mapping for analyst context +| EVAL Esql.source_info = CONCAT( + "{\"ip\":\"", COALESCE(okta.client.ip::STRING, "unknown"), + "\",\"country\":\"", COALESCE(client.geo.country_name, "unknown"), + "\",\"asn\":\"", COALESCE(source.as.organization.name, "unknown"), + "\",\"user_agent\":\"", COALESCE(okta.client.user_agent.raw_user_agent, "unknown"), "\"}" + ) + +| STATS + Esql.unique_source_ips = COUNT_DISTINCT(okta.client.ip), + Esql.total_attempts = COUNT(*), + Esql.unique_user_agents = COUNT_DISTINCT(okta.client.user_agent.raw_user_agent), + Esql.unique_dt_hashes = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash), + Esql.unique_asns = COUNT_DISTINCT(source.as.number), + Esql.unique_countries = COUNT_DISTINCT(client.geo.country_name), + Esql.first_seen = MIN(@timestamp), + Esql.last_seen = MAX(@timestamp), + Esql.source_ip_values = VALUES(okta.client.ip), + Esql.source_mapping = VALUES(Esql.source_info), + Esql.event_action_values = VALUES(event.action), + Esql.user_agent_values = VALUES(okta.client.user_agent.raw_user_agent), + Esql.device_values = VALUES(okta.client.device), + Esql.is_proxy_values = VALUES(okta.security_context.is_proxy), + Esql.geo_country_values = VALUES(client.geo.country_name), + Esql.geo_city_values = VALUES(client.geo.city_name), + Esql.source_asn_values = VALUES(source.as.number), + Esql.source_asn_org_values = VALUES(source.as.organization.name), + Esql.threat_suspected_values = VALUES(okta.debug_context.debug_data.threat_suspected), + Esql.risk_level_values = VALUES(okta.debug_context.debug_data.risk_level), + Esql.risk_reasons_values = VALUES(okta.debug_context.debug_data.risk_reasons) + BY okta.actor.alternate_id + +| EVAL + Esql.attempts_per_ip = Esql.total_attempts * 1.0 / Esql.unique_source_ips, + Esql.duration_seconds = DATE_DIFF("seconds", Esql.first_seen, Esql.last_seen) + +| WHERE + Esql.unique_source_ips >= 5 + AND Esql.total_attempts >= 10 + AND ( + Esql.unique_countries >= 2 OR + Esql.unique_asns >= 3 OR + Esql.unique_source_ips >= 8 OR + Esql.unique_user_agents >= 3 + ) + +| SORT Esql.unique_source_ips DESC +| KEEP Esql.*, okta.actor.alternate_id +''' + + +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1110" +name = "Brute Force" +reference = "https://attack.mitre.org/techniques/T1110/" +[[rule.threat.technique.subtechnique]] +id = "T1110.001" +name = "Password Guessing" +reference = "https://attack.mitre.org/techniques/T1110/001/" + + +[rule.threat.tactic] +id = "TA0006" +name = "Credential Access" +reference = "https://attack.mitre.org/tactics/TA0006/" diff --git a/rules/integrations/okta/credential_access_okta_brute_force_or_password_spraying.toml b/rules/integrations/okta/credential_access_okta_brute_force_or_password_spraying.toml deleted file mode 100644 index 4c511098a..000000000 --- a/rules/integrations/okta/credential_access_okta_brute_force_or_password_spraying.toml +++ /dev/null @@ -1,96 +0,0 @@ -[metadata] -creation_date = "2020/07/16" -integration = ["okta"] -maturity = "production" -updated_date = "2025/07/02" - -[rule] -author = ["Elastic"] -description = """ -Identifies a high number of failed Okta user authentication attempts from a single IP address, which could be indicative -of a brute force or password spraying attack. An adversary may attempt a brute force or password spraying attack to -obtain unauthorized access to user accounts. -""" -false_positives = [ - """ - Automated processes that attempt to authenticate using expired credentials and unbounded retries may lead to false - positives. - """, -] -index = ["filebeat-*", "logs-okta*"] -language = "kuery" -license = "Elastic License v2" -name = "Okta Brute Force or Password Spraying Attack" -note = """## Triage and analysis - -### Investigating Okta Brute Force or Password Spraying Attack - -This rule alerts when a high number of failed Okta user authentication attempts occur from a single IP address. This could be indicative of a brute force or password spraying attack, where an adversary may attempt to gain unauthorized access to user accounts by guessing the passwords. - -#### Possible investigation steps: - -- Review the `source.ip` field to identify the IP address from which the high volume of failed login attempts originated. -- Look into the `event.outcome` field to verify that these are indeed failed authentication attempts. -- Determine the `user.name` or `user.email` related to these failed login attempts. If the attempts are spread across multiple accounts, it might indicate a password spraying attack. -- Check the timeline of the events. Are the failed attempts spread out evenly, or are there burst periods, which might indicate an automated tool? -- Determine the geographical location of the source IP. Is this location consistent with the user's typical login location? -- Analyze any previous successful logins from this IP. Was this IP previously associated with successful logins? - -### False positive analysis: - -- A single user or automated process that attempts to authenticate using expired or wrong credentials multiple times may trigger a false positive. -- Analyze the behavior of the source IP. If the IP is associated with legitimate users or services, it may be a false positive. - -### Response and remediation: - -- If you identify unauthorized access attempts, consider blocking the source IP at the firewall level. -- Notify the users who are targeted by the attack. Ask them to change their passwords and ensure they use unique, complex passwords. -- Enhance monitoring on the affected user accounts for any suspicious activity. -- If the attack is persistent, consider implementing CAPTCHA or account lockouts after a certain number of failed login attempts. -- If the attack is persistent, consider implementing multi-factor authentication (MFA) for the affected user accounts. -- Review and update your security policies based on the findings from the incident. - -## Setup - -The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.""" -references = [ - "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/", - "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", - "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", - "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", -] -risk_score = 47 -rule_id = "42bf698b-4738-445b-8231-c834ddefd8a0" -severity = "medium" -tags = [ - "Use Case: Identity and Access Audit", - "Tactic: Credential Access", - "Data Source: Okta", - "Resources: Investigation Guide", -] -timestamp_override = "event.ingested" -type = "threshold" - -query = ''' -event.dataset:okta.system and event.category:authentication and event.outcome:failure -''' - - -[[rule.threat]] -framework = "MITRE ATT&CK" -[[rule.threat.technique]] -id = "T1110" -name = "Brute Force" -reference = "https://attack.mitre.org/techniques/T1110/" - - -[rule.threat.tactic] -id = "TA0006" -name = "Credential Access" -reference = "https://attack.mitre.org/tactics/TA0006/" - -[rule.threshold] -field = ["source.ip"] -value = 25 - diff --git a/rules/integrations/okta/credential_access_okta_credential_stuffing_single_source.toml b/rules/integrations/okta/credential_access_okta_credential_stuffing_single_source.toml new file mode 100644 index 000000000..c25881880 --- /dev/null +++ b/rules/integrations/okta/credential_access_okta_credential_stuffing_single_source.toml @@ -0,0 +1,162 @@ +[metadata] +creation_date = "2024/06/17" +integration = ["okta"] +maturity = "production" +updated_date = "2026/02/19" + +[rule] +author = ["Elastic"] +description = """ +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. +""" +false_positives = [ + "Corporate proxy 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.", +] +from = "now-15m" +language = "esql" +license = "Elastic License v2" +name = "Potential Okta Credential Stuffing (Single Source)" +note = """## 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 = [ + "https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta", + "https://www.okta.com/identity-101/brute-force/", + "https://support.okta.com/help/s/article/How-does-the-Device-Token-work?language=en_US", + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", + "https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection", + "https://www.okta.com/resources/whitepaper-how-adaptive-mfa-can-help-in-mitigating-brute-force-attacks/", + "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", + "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", +] +risk_score = 47 +rule_id = "94e734c0-2cda-11ef-84e1-f661ea17fbce" +setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule." +severity = "medium" +tags = [ + "Domain: Identity", + "Use Case: Identity and Access Audit", + "Data Source: Okta", + "Data Source: Okta System Logs", + "Tactic: Credential Access", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "esql" + +query = ''' +FROM logs-okta.system-* METADATA _id, _version, _index +| WHERE + event.dataset == "okta.system" + AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start") + AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT") + AND okta.actor.alternate_id IS NOT NULL +// Build user-source context as JSON for enrichment +| EVAL Esql.user_source_info = CONCAT( + "{\"user\":\"", okta.actor.alternate_id, + "\",\"ip\":\"", COALESCE(okta.client.ip::STRING, "unknown"), + "\",\"user_agent\":\"", COALESCE(okta.client.user_agent.raw_user_agent, "unknown"), "\"}" + ) +// FIRST STATS: Aggregate by (IP, user) to get per-user attempt counts +// This prevents skew from outlier users with many attempts +| STATS + Esql.user_attempts = COUNT(*), + Esql.user_dt_hashes = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash), + Esql.user_source_info = VALUES(Esql.user_source_info), + Esql.user_agents_per_user = VALUES(okta.client.user_agent.raw_user_agent), + Esql.devices_per_user = VALUES(okta.client.device), + Esql.is_proxy = VALUES(okta.security_context.is_proxy), + Esql.geo_country = VALUES(client.geo.country_name), + Esql.geo_city = VALUES(client.geo.city_name), + Esql.asn_number = VALUES(source.as.number), + Esql.asn_org = VALUES(source.as.organization.name), + Esql.threat_suspected = VALUES(okta.debug_context.debug_data.threat_suspected), + Esql.risk_level = VALUES(okta.debug_context.debug_data.risk_level), + Esql.risk_reasons = VALUES(okta.debug_context.debug_data.risk_reasons), + Esql.event_actions = VALUES(event.action), + Esql.first_seen_user = MIN(@timestamp), + Esql.last_seen_user = MAX(@timestamp) + BY okta.client.ip, okta.actor.alternate_id +// SECOND STATS: Aggregate by IP to detect credential stuffing pattern +// Now we can accurately measure the distribution of attempts across users +| STATS + Esql.unique_users = COUNT(*), + Esql.total_attempts = SUM(Esql.user_attempts), + Esql.max_attempts_per_user = MAX(Esql.user_attempts), + Esql.min_attempts_per_user = MIN(Esql.user_attempts), + Esql.avg_attempts_per_user = AVG(Esql.user_attempts), + Esql.users_with_single_attempt = SUM(CASE(Esql.user_attempts == 1, 1, 0)), + Esql.users_with_few_attempts = SUM(CASE(Esql.user_attempts <= 2, 1, 0)), + Esql.first_seen = MIN(Esql.first_seen_user), + Esql.last_seen = MAX(Esql.last_seen_user), + Esql.target_users = VALUES(okta.actor.alternate_id), + Esql.user_source_mapping = VALUES(Esql.user_source_info), + Esql.event_action_values = VALUES(Esql.event_actions), + Esql.user_agent_values = VALUES(Esql.user_agents_per_user), + Esql.device_values = VALUES(Esql.devices_per_user), + Esql.is_proxy_values = VALUES(Esql.is_proxy), + Esql.geo_country_values = VALUES(Esql.geo_country), + Esql.geo_city_values = VALUES(Esql.geo_city), + Esql.source_asn_values = VALUES(Esql.asn_number), + Esql.source_asn_org_values = VALUES(Esql.asn_org), + Esql.threat_suspected_values = VALUES(Esql.threat_suspected), + Esql.risk_level_values = VALUES(Esql.risk_level), + Esql.risk_reasons_values = VALUES(Esql.risk_reasons) + BY okta.client.ip +// Calculate stuffing signature: most users should have very few attempts +| EVAL Esql.pct_users_few_attempts = Esql.users_with_few_attempts * 100.0 / Esql.unique_users +// Credential stuffing: many users, most with 1-2 attempts each, low max per user +// Stacked stats gives us accurate per-user distribution instead of skewed averages +| WHERE + Esql.total_attempts >= 25 + AND Esql.unique_users >= 15 + AND Esql.max_attempts_per_user <= 2 + AND Esql.pct_users_few_attempts >= 80.0 +| SORT Esql.unique_users DESC +| KEEP Esql.*, okta.client.ip +''' + + +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1110" +name = "Brute Force" +reference = "https://attack.mitre.org/techniques/T1110/" +[[rule.threat.technique.subtechnique]] +id = "T1110.004" +name = "Credential Stuffing" +reference = "https://attack.mitre.org/techniques/T1110/004/" + + +[rule.threat.tactic] +id = "TA0006" +name = "Credential Access" +reference = "https://attack.mitre.org/tactics/TA0006/" + diff --git a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml deleted file mode 100644 index e3d2fcd32..000000000 --- a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml +++ /dev/null @@ -1,146 +0,0 @@ -[metadata] -creation_date = "2024/06/17" -integration = ["okta"] -maturity = "production" -updated_date = "2025/09/25" - -[rule] -author = ["Elastic"] -description = """ -Detects when an Okta client address has a certain threshold of Okta user authentication events with multiple device -token hashes generated for single user authentication. Adversaries may attempt to launch a credential stuffing or -password spraying attack from the same device by using a list of known usernames and passwords to gain unauthorized -access to user accounts. -""" -false_positives = [ - "Users may share an endpoint related to work or personal use in which separate Okta accounts are used.", - "Shared systems such as Kiosks and conference room computers may be used by multiple users.", -] -from = "now-9m" -language = "esql" -license = "Elastic License v2" -name = "High Number of Okta Device Token Cookies Generated for Authentication" -note = """## Triage and analysis - -### Investigating High Number of Okta Device Token Cookies Generated for Authentication - -This rule detects when a certain threshold of Okta user authentication events are reported for multiple users from the same client address. Adversaries may attempt to launch a credential stuffing attack from the same device by using a list of known usernames and passwords to gain unauthorized access to user accounts. Note that Okta does not log unrecognized usernames supplied during authentication attempts, so this rule may not detect all credential stuffing attempts or may indicate a targeted attack. - -#### Possible investigation steps: -- Since this is an ESQL rule, the `okta.actor.alternate_id` and `okta.client.ip` values can be used to pivot into the raw authentication events related to this activity. -- Identify the users involved in this action by examining the `okta.actor.id`, `okta.actor.type`, `okta.actor.alternate_id`, and `okta.actor.display_name` fields. -- Determine the device client used for these actions by analyzing `okta.client.ip`, `okta.client.user_agent.raw_user_agent`, `okta.client.zone`, `okta.client.device`, and `okta.client.id` fields. -- Review the `okta.security_context.is_proxy` field to determine if the device is a proxy. - - If the device is a proxy, this may indicate that a user is using a proxy to access multiple accounts for password spraying. -- With the list of `okta.actor.alternate_id` values, review `event.outcome` results to determine if the authentication was successful. - - If the authentication was successful for any user, pivoting to `event.action` values for those users may provide additional context. -- With Okta end users identified, review the `okta.debug_context.debug_data.dt_hash` field. - - Historical analysis should indicate if this device token hash is commonly associated with the user. -- Review the `okta.event_type` field to determine the type of authentication event that occurred. - - If the event type is `user.authentication.sso`, the user may have legitimately started a session via a proxy for security or privacy reasons. - - If the event type is `user.authentication.password`, the user may be using a proxy to access multiple accounts for password spraying. - - If the event type is `user.session.start`, the source may have attempted to establish a session via the Okta authentication API. -- Examine the `okta.outcome.result` field to determine if the authentication was successful. -- Review the past activities of the actor(s) involved in this action by checking their previous actions. -- Evaluate the actions that happened just before and after this event in the `okta.event_type` field to help understand the full context of the activity. - - This may help determine the authentication and authorization actions that occurred between the user, Okta and application. - -### False positive analysis: -- A user may have legitimately started a session via a proxy for security or privacy reasons. -- Users may share an endpoint related to work or personal use in which separate Okta accounts are used. - - Architecturally, this shared endpoint may leverage a proxy for security or privacy reasons. - - Shared systems such as Kiosks and conference room computers may be used by multiple users. - - Shared working spaces may have a single endpoint that is used by multiple users. - -### Response and remediation: -- Review the profile of the users involved in this action to determine if proxy usage may be expected. -- If the user is legitimate and the authentication behavior is not suspicious based on device analysis, no action is required. -- If the user is legitimate but the authentication behavior is suspicious, consider resetting passwords for the users involves and enabling multi-factor authentication (MFA). - - If MFA is already enabled, consider resetting MFA for the users. -- If any of the users are not legitimate, consider deactivating the user's account. -- Conduct a review of Okta policies and ensure they are in accordance with security best practices. -- Check with internal IT teams to determine if the accounts involved recently had MFA reset at the request of the user. - - If so, confirm with the user this was a legitimate request. - - If so and this was not a legitimate request, consider deactivating the user's account temporarily. - - Reset passwords and reset MFA for the user. -- If this is a false positive, consider adding the `okta.debug_context.debug_data.dt_hash` field to the `exceptions` list in the rule. - - This will prevent future occurrences of this event for this device from triggering the rule. - - Alternatively adding `okta.client.ip` or a CIDR range to the `exceptions` list can prevent future occurrences of this event from triggering the rule. - - This should be done with caution as it may prevent legitimate alerts from being generated. -""" -references = [ - "https://support.okta.com/help/s/article/How-does-the-Device-Token-work?language=en_US", - "https://developer.okta.com/docs/reference/api/event-types/", - "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", - "https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection", - "https://www.okta.com/resources/whitepaper-how-adaptive-mfa-can-help-in-mitigating-brute-force-attacks/", - "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", - "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", -] -risk_score = 21 -rule_id = "23f18264-2d6d-11ef-9413-f661ea17fbce" -setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule." -severity = "low" -tags = [ - "Use Case: Identity and Access Audit", - "Data Source: Okta", - "Tactic: Credential Access", - "Resources: Investigation Guide", -] -timestamp_override = "event.ingested" -type = "esql" - -query = ''' -from logs-okta* -| where - event.dataset == "okta.system" and - (event.action like "user.authentication.*" or event.action == "user.session.start") and - okta.debug_context.debug_data.request_uri == "/api/v1/authn" and - okta.outcome.reason == "INVALID_CREDENTIALS" -| keep - event.action, - okta.debug_context.debug_data.dt_hash, - okta.client.ip, - okta.actor.alternate_id, - okta.debug_context.debug_data.request_uri, - okta.outcome.reason -| stats - Esql.okta_debug_context_debug_data_dt_hash_count_distinct = count_distinct(okta.debug_context.debug_data.dt_hash) - by - okta.client.ip, - okta.actor.alternate_id -| where - Esql.okta_debug_context_debug_data_dt_hash_count_distinct >= 30 -| sort - Esql.okta_debug_context_debug_data_dt_hash_count_distinct desc -''' - - -[[rule.threat]] -framework = "MITRE ATT&CK" -[[rule.threat.technique]] -id = "T1110" -name = "Brute Force" -reference = "https://attack.mitre.org/techniques/T1110/" -[[rule.threat.technique.subtechnique]] -id = "T1110.003" -name = "Password Spraying" -reference = "https://attack.mitre.org/techniques/T1110/003/" - - -[[rule.threat.technique]] -id = "T1110" -name = "Brute Force" -reference = "https://attack.mitre.org/techniques/T1110/" -[[rule.threat.technique.subtechnique]] -id = "T1110.004" -name = "Credential Stuffing" -reference = "https://attack.mitre.org/techniques/T1110/004/" - - - -[rule.threat.tactic] -id = "TA0006" -name = "Credential Access" -reference = "https://attack.mitre.org/tactics/TA0006/" - diff --git a/rules/integrations/okta/credential_access_okta_password_spray_multi_source.toml b/rules/integrations/okta/credential_access_okta_password_spray_multi_source.toml new file mode 100644 index 000000000..ff8620c52 --- /dev/null +++ b/rules/integrations/okta/credential_access_okta_password_spray_multi_source.toml @@ -0,0 +1,150 @@ +[metadata] +creation_date = "2026/02/19" +integration = ["okta"] +maturity = "production" +updated_date = "2026/02/19" + +[rule] +author = ["Elastic"] +description = """ +Detects potential password spray attacks where multiple source IPs target multiple Okta user accounts within a +time window, indicating coordinated attacks using IP rotation to evade single-source detection. +""" +false_positives = [ + "Large enterprises with many users experiencing simultaneous password issues during credential rotation events.", + "Automated monitoring or penetration testing tools scanning from multiple IPs.", +] +from = "now-1h" +interval = "15m" +language = "esql" +license = "Elastic License v2" +name = "Potential Okta Password Spray (Multi-Source)" +note = """## Triage and analysis + +### Investigating Potential Okta Password Spray (Multi-Source) + +This rule identifies coordinated password spray attacks where multiple source IPs target multiple user accounts within a time window. This pattern indicates attackers using IP rotation to evade single-source detection while spraying passwords across the organization. + +#### Possible investigation steps +- Review the list of targeted user accounts and check if any authentications succeeded. +- Examine the source IPs and their ASN ownership for signs of proxy, VPN, or cloud infrastructure. +- Check if Okta flagged any of the sources as known threats or proxies. +- Analyze the attempts-per-user ratio to confirm spray behavior versus brute force. +- Review the geographic distribution of source IPs for coordination patterns. +- Cross-reference with successful authentication events to identify potential compromises. + +### False positive analysis +- Organization-wide password rotation or expiration events may cause widespread authentication failures. +- Misconfigured SSO or SAML integrations can cause batch failures from legitimate infrastructure. +- Penetration testing should be coordinated and whitelisted in advance. + +### Response and remediation +- If attack is confirmed, notify affected users and enforce password resets for potentially compromised accounts. +- Block attacking IP ranges at the network perimeter. +- Enable or strengthen MFA for targeted accounts. +- Review Okta sign-on policies to add additional friction for suspicious authentication patterns. +- Consider temporary lockdowns for highly targeted accounts. +""" +references = [ + "https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta", + "https://www.okta.com/identity-101/brute-force/", + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", + "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", + "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", +] +risk_score = 47 +rule_id = "2d3c27d5-d133-4152-8102-8d051619ec4a" +setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule." +severity = "medium" +tags = [ + "Domain: Identity", + "Use Case: Identity and Access Audit", + "Use Case: Threat Detection", + "Data Source: Okta", + "Data Source: Okta System Logs", + "Tactic: Credential Access", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "esql" + +query = ''' +FROM logs-okta.system-* METADATA _id, _version, _index +| WHERE + event.dataset == "okta.system" + AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start") + AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT") + AND okta.actor.alternate_id IS NOT NULL + +// Bucket into 15-minute windows and create user-source mapping for context +| EVAL + Esql.time_bucket = DATE_TRUNC(15 minutes, @timestamp), + Esql.user_source_info = CONCAT( + "{\"user\":\"", okta.actor.alternate_id, + "\",\"ip\":\"", COALESCE(okta.client.ip::STRING, "unknown"), + "\",\"user_agent\":\"", COALESCE(okta.client.user_agent.raw_user_agent, "unknown"), "\"}" + ) + +// Aggregate across entire tenant per time bucket to detect distributed spray +| STATS + Esql.unique_users = COUNT_DISTINCT(okta.actor.alternate_id), + Esql.unique_source_ips = COUNT_DISTINCT(okta.client.ip), + Esql.total_attempts = COUNT(*), + Esql.unique_user_agents = COUNT_DISTINCT(okta.client.user_agent.raw_user_agent), + Esql.unique_asns = COUNT_DISTINCT(source.as.number), + Esql.unique_countries = COUNT_DISTINCT(client.geo.country_name), + Esql.first_seen = MIN(@timestamp), + Esql.last_seen = MAX(@timestamp), + Esql.target_users = VALUES(okta.actor.alternate_id), + Esql.source_ip_values = VALUES(okta.client.ip), + Esql.user_source_mapping = VALUES(Esql.user_source_info), + Esql.event_action_values = VALUES(event.action), + Esql.user_agent_values = VALUES(okta.client.user_agent.raw_user_agent), + Esql.device_values = VALUES(okta.client.device), + Esql.is_proxy_values = VALUES(okta.security_context.is_proxy), + Esql.geo_country_values = VALUES(client.geo.country_name), + Esql.geo_city_values = VALUES(client.geo.city_name), + Esql.source_asn_values = VALUES(source.as.number), + Esql.source_asn_org_values = VALUES(source.as.organization.name), + Esql.threat_suspected_values = VALUES(okta.debug_context.debug_data.threat_suspected), + Esql.risk_level_values = VALUES(okta.debug_context.debug_data.risk_level), + Esql.risk_reasons_values = VALUES(okta.debug_context.debug_data.risk_reasons) + BY Esql.time_bucket + +// Calculate spray metrics +| EVAL + Esql.attempts_per_user = Esql.total_attempts * 1.0 / Esql.unique_users, + Esql.attempts_per_ip = Esql.total_attempts * 1.0 / Esql.unique_source_ips, + Esql.users_per_ip = Esql.unique_users * 1.0 / Esql.unique_source_ips + +// Distributed spray: many IPs, many users, moderate spread across both +// Key differentiator: attacks come from multiple IPs (evading per-IP rules) +| WHERE + Esql.unique_source_ips >= 5 + AND Esql.unique_users >= 8 + AND Esql.total_attempts >= 25 + AND Esql.attempts_per_user <= 5.0 + AND Esql.users_per_ip >= 1.0 + +| SORT Esql.total_attempts DESC +| KEEP Esql.* +''' + + +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1110" +name = "Brute Force" +reference = "https://attack.mitre.org/techniques/T1110/" +[[rule.threat.technique.subtechnique]] +id = "T1110.003" +name = "Password Spraying" +reference = "https://attack.mitre.org/techniques/T1110/003/" + + +[rule.threat.tactic] +id = "TA0006" +name = "Credential Access" +reference = "https://attack.mitre.org/tactics/TA0006/" diff --git a/rules/integrations/okta/credential_access_okta_password_spray_single_source.toml b/rules/integrations/okta/credential_access_okta_password_spray_single_source.toml new file mode 100644 index 000000000..15eb692f8 --- /dev/null +++ b/rules/integrations/okta/credential_access_okta_password_spray_single_source.toml @@ -0,0 +1,170 @@ +[metadata] +creation_date = "2020/07/16" +integration = ["okta"] +maturity = "production" +updated_date = "2026/02/19" + +[rule] +author = ["Elastic"] +description = """ +Detects potential password spray attacks where a single source IP attempts authentication against multiple Okta +user accounts with repeated attempts per user, indicating common password guessing paced to avoid lockouts. +""" +false_positives = [ + "Corporate proxy or VPN exit nodes may aggregate traffic from multiple legitimate users with login issues.", + "Automated processes or misconfigured applications retrying authentication may trigger this rule.", +] +from = "now-1h" +interval = "15m" +language = "esql" +license = "Elastic License v2" +name = "Potential Okta Password Spray (Single Source)" +note = """## Triage and analysis + +### Investigating Potential Okta Password Spray (Single Source) + +This rule identifies a single source IP attempting authentication against multiple user accounts with repeated attempts per user over time. This pattern indicates password spraying where attackers try common passwords while pacing attempts to avoid lockouts. + +#### 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. +- Analyze the timing of attempts to determine if they are paced to avoid lockout thresholds. +- Check if Okta flagged the source as a known threat or proxy. +- Examine user agent strings for signs of automation or consistent tooling across attempts. +- 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 with login issues. +- Automated processes or misconfigured applications retrying authentication may trigger this rule. +- Password rotation events may cause legitimate widespread authentication failures. + +### Response and remediation +- If attack is confirmed, block the source IP at the network perimeter. +- Notify targeted users and enforce password resets for accounts that may have been compromised. +- Enable or strengthen MFA for targeted accounts. +- Consider implementing CAPTCHA or additional friction for suspicious authentication patterns. +- Review Okta sign-on policies to ensure lockout thresholds are appropriately configured. +""" +references = [ + "https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta", + "https://www.okta.com/identity-101/brute-force/", + "https://developer.okta.com/docs/reference/api/system-log/", + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", + "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", + "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", +] +risk_score = 47 +rule_id = "42bf698b-4738-445b-8231-c834ddefd8a0" +severity = "medium" +tags = [ + "Domain: Identity", + "Use Case: Identity and Access Audit", + "Tactic: Credential Access", + "Data Source: Okta", + "Data Source: Okta System Logs", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "esql" + +query = ''' +FROM logs-okta.system-* METADATA _id, _version, _index +| WHERE + event.dataset == "okta.system" + AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start") + AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT") + AND okta.actor.alternate_id IS NOT NULL +// Build user-source context as JSON for enrichment +| EVAL Esql.user_source_info = CONCAT( + "{\"user\":\"", okta.actor.alternate_id, + "\",\"ip\":\"", COALESCE(okta.client.ip::STRING, "unknown"), + "\",\"user_agent\":\"", COALESCE(okta.client.user_agent.raw_user_agent, "unknown"), "\"}" + ) +// FIRST STATS: Aggregate by (IP, user) to get per-user attempt counts +// This prevents skew from outlier users with many attempts +| STATS + Esql.user_attempts = COUNT(*), + Esql.user_source_info = VALUES(Esql.user_source_info), + Esql.user_agents_per_user = VALUES(okta.client.user_agent.raw_user_agent), + Esql.devices_per_user = VALUES(okta.client.device), + Esql.is_proxy = VALUES(okta.security_context.is_proxy), + Esql.geo_country = VALUES(client.geo.country_name), + Esql.geo_city = VALUES(client.geo.city_name), + Esql.asn_number = VALUES(source.as.number), + Esql.asn_org = VALUES(source.as.organization.name), + Esql.threat_suspected = VALUES(okta.debug_context.debug_data.threat_suspected), + Esql.risk_level = VALUES(okta.debug_context.debug_data.risk_level), + Esql.event_actions = VALUES(event.action), + Esql.first_seen_user = MIN(@timestamp), + Esql.last_seen_user = MAX(@timestamp) + BY okta.client.ip, okta.actor.alternate_id +// SECOND STATS: Aggregate by IP to detect password spray pattern +// Now we can accurately measure the distribution of attempts across users +| STATS + Esql.unique_users = COUNT(*), + Esql.total_attempts = SUM(Esql.user_attempts), + Esql.max_attempts_per_user = MAX(Esql.user_attempts), + Esql.min_attempts_per_user = MIN(Esql.user_attempts), + Esql.avg_attempts_per_user = AVG(Esql.user_attempts), + // Spray band: 2-6 attempts per user (deliberate slow spray below lockout) + Esql.users_in_spray_band = SUM(CASE(Esql.user_attempts >= 2 AND Esql.user_attempts <= 6, 1, 0)), + // Also track users with only 1 attempt (stuffing-like) for differentiation + Esql.users_with_single_attempt = SUM(CASE(Esql.user_attempts == 1, 1, 0)), + Esql.first_seen = MIN(Esql.first_seen_user), + Esql.last_seen = MAX(Esql.last_seen_user), + Esql.target_users = VALUES(okta.actor.alternate_id), + Esql.user_source_mapping = VALUES(Esql.user_source_info), + Esql.event_action_values = VALUES(Esql.event_actions), + Esql.user_agent_values = VALUES(Esql.user_agents_per_user), + Esql.device_values = VALUES(Esql.devices_per_user), + Esql.is_proxy_values = VALUES(Esql.is_proxy), + Esql.geo_country_values = VALUES(Esql.geo_country), + Esql.geo_city_values = VALUES(Esql.geo_city), + Esql.source_asn_values = VALUES(Esql.asn_number), + Esql.source_asn_org_values = VALUES(Esql.asn_org), + Esql.threat_suspected_values = VALUES(Esql.threat_suspected), + Esql.risk_level_values = VALUES(Esql.risk_level) + BY okta.client.ip +// Calculate spray signature metrics +| EVAL + // Percentage of users in the spray band (2-6 attempts) + Esql.pct_users_in_spray_band = Esql.users_in_spray_band * 100.0 / Esql.unique_users, + // Attack duration in minutes (spray is paced, not bursty) + Esql.attack_duration_minutes = DATE_DIFF("minute", Esql.first_seen, Esql.last_seen) +// Password spraying detection logic: +// - Many users targeted (>= 5) +// - Hard cap below Okta lockout threshold (max <= 8 attempts per user) +// - Majority of users in spray band (2-6 attempts) (at least 60%) +// - Attack is paced over time (>= 5 minutes) (not a 10-second burst like stuffing) +// - Minimum total attempts to reduce noise +// Note: For IP rotation attacks, see "Distributed Password Spray Attack in Okta" rule +| WHERE + Esql.unique_users >= 5 + AND Esql.total_attempts >= 15 + AND Esql.max_attempts_per_user <= 8 + AND Esql.max_attempts_per_user >= 2 + AND Esql.pct_users_in_spray_band >= 60.0 + AND Esql.attack_duration_minutes >= 5 +| SORT Esql.total_attempts DESC +| KEEP Esql.*, okta.client.ip +''' + + +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1110" +name = "Brute Force" +reference = "https://attack.mitre.org/techniques/T1110/" +[[rule.threat.technique.subtechnique]] +id = "T1110.003" +name = "Password Spraying" +reference = "https://attack.mitre.org/techniques/T1110/003/" + + +[rule.threat.tactic] +id = "TA0006" +name = "Credential Access" +reference = "https://attack.mitre.org/tactics/TA0006/" + diff --git a/rules/integrations/okta/credential_access_okta_successful_login_after_credential_attack.toml b/rules/integrations/okta/credential_access_okta_successful_login_after_credential_attack.toml new file mode 100644 index 000000000..e060a3abc --- /dev/null +++ b/rules/integrations/okta/credential_access_okta_successful_login_after_credential_attack.toml @@ -0,0 +1,233 @@ +[metadata] +creation_date = "2026/02/12" +integration = ["okta"] +maturity = "production" +updated_date = "2026/02/19" + +[rule] +author = ["Elastic"] +description = """ +Correlates Okta credential attack alerts with subsequent successful authentication for the same user account, +identifying potential compromise following brute force, password spray, or credential stuffing attempts. +""" +false_positives = [ + "A user experiencing legitimate login issues (forgotten password, typos) may trigger credential attack alerts before successfully authenticating.", + "Automated password reset flows where a user fails multiple times then succeeds after resetting their password.", +] +from = "now-6h" +interval = "30m" +language = "esql" +license = "Elastic License v2" +name = "Okta Successful Login After Credential Attack" +note = """## Triage and analysis + +### Investigating Okta Successful Login After Credential Attack + +This rule correlates credential attack alerts with subsequent successful authentication for the same user account. The correlation is user-centric, capturing IP rotation scenarios where attackers may login from a different IP after obtaining credentials. + +#### Possible investigation steps +- Identify the user account and review the timeline between the attack and successful login. +- Compare the attack source IPs versus the login source IP to identify potential IP rotation. +- Review the original credential attack alert to understand the scope and nature of the attack. +- Check the authentication method used and whether MFA was required and satisfied. +- Review the session activity following the successful login for signs of account takeover. +- Verify with the user if the login was legitimate. + +### False positive analysis +- Users experiencing legitimate login issues may trigger attack alerts before successfully authenticating. +- Automated password reset flows where a user fails multiple times then succeeds after resetting may trigger this rule. +- The rule correlates on user identity only, so it fires when a user is targeted and later logs in, even if from different IPs. + +### Response and remediation +- If compromise is suspected, reset the user's password and revoke all active sessions. +- Reset MFA if the attacker may have enrolled their own device. +- Block the source IP at the network perimeter. +- Review the user's recent activity for signs of lateral movement or data access. +- Check for persistence mechanisms such as new OAuth apps, API tokens, or enrolled devices. +""" +references = [ + "https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta", + "https://www.okta.com/identity-101/brute-force/", + "https://developer.okta.com/docs/reference/api/system-log/", + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy", + "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security", + "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta", +] +risk_score = 73 +rule_id = "50742e15-c5ef-49c8-9a2d-31221d45af58" +setup = """## Setup + +This rule requires the following: +1. The Okta Fleet integration, Filebeat module, or similarly structured data for Okta System Logs. +2. The correlated credential attack detection rules must be enabled (at least one): + - Potential Okta Credential Stuffing (Single Source) (94e734c0-2cda-11ef-84e1-f661ea17fbce) + - Potential Okta Password Spray (Single Source) (42bf698b-4738-445b-8231-c834ddefd8a0) + - Potential Okta Brute Force (Device Token Rotation) (23f18264-2d6d-11ef-9413-f661ea17fbce) + - Potential Okta Brute Force (Multi-Source) (5889760c-9858-4b4b-879c-e299df493295) + - Potential Okta Password Spray (Multi-Source) (2d3c27d5-d133-4152-8102-8d051619ec4a) +3. Alerts from these rules must be written to the `.alerts-security.*` indices. + +The rule queries both alert indices and Okta log indices to correlate attack alerts with successful logins.""" +severity = "high" +tags = [ + "Domain: Identity", + "Use Case: Identity and Access Audit", + "Use Case: Threat Detection", + "Data Source: Okta", + "Data Source: Okta System Logs", + "Tactic: Credential Access", + "Tactic: Initial Access", + "Resources: Investigation Guide", + "Rule Type: Higher-Order Rule", +] +timestamp_override = "event.ingested" +type = "esql" + +query = ''' +FROM .alerts-security.*, logs-okta.system-* METADATA _id, _version, _index +// Filter for credential attack alerts OR successful Okta authentications +| WHERE + ( + // Credential attack alerts from the five correlated rules + kibana.alert.rule.rule_id IN ( + "94e734c0-2cda-11ef-84e1-f661ea17fbce", // Credential Stuffing + "42bf698b-4738-445b-8231-c834ddefd8a0", // Password Spraying + "23f18264-2d6d-11ef-9413-f661ea17fbce", // DT Brute Force + "5889760c-9858-4b4b-879c-e299df493295", // Distributed Brute Force + "2d3c27d5-d133-4152-8102-8d051619ec4a" // Distributed Spray + ) + ) + OR ( + // Successful Okta authentication events + event.dataset == "okta.system" + AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start") + AND okta.outcome.result == "SUCCESS" + AND okta.actor.alternate_id IS NOT NULL + ) +// correlation - alerts may store user/IP in different fields than raw logs +| EVAL + Esql.user = COALESCE(okta.actor.alternate_id, user.name, user.email), + Esql.source_ip = COALESCE(okta.client.ip, client.ip, source.ip) +// Must have user identity to correlate +| WHERE Esql.user IS NOT NULL +// Classify events and capture timestamps/IPs by event type +| EVAL + Esql.is_attack_alert = CASE( + kibana.alert.rule.rule_id IN ( + "94e734c0-2cda-11ef-84e1-f661ea17fbce", + "42bf698b-4738-445b-8231-c834ddefd8a0", + "23f18264-2d6d-11ef-9413-f661ea17fbce", + "5889760c-9858-4b4b-879c-e299df493295", + "2d3c27d5-d133-4152-8102-8d051619ec4a" + ), 1, 0 + ), + Esql.is_success_login = CASE( + event.dataset == "okta.system" + AND okta.outcome.result == "SUCCESS", 1, 0 + ), + Esql.attack_ip = CASE( + kibana.alert.rule.rule_id IN ( + "94e734c0-2cda-11ef-84e1-f661ea17fbce", + "42bf698b-4738-445b-8231-c834ddefd8a0", + "23f18264-2d6d-11ef-9413-f661ea17fbce", + "5889760c-9858-4b4b-879c-e299df493295", + "2d3c27d5-d133-4152-8102-8d051619ec4a" + ), Esql.source_ip, null + ), + Esql.login_ip = CASE( + event.dataset == "okta.system" + AND okta.outcome.result == "SUCCESS", Esql.source_ip, null + ), + Esql.attack_ts = CASE( + kibana.alert.rule.rule_id IN ( + "94e734c0-2cda-11ef-84e1-f661ea17fbce", + "42bf698b-4738-445b-8231-c834ddefd8a0", + "23f18264-2d6d-11ef-9413-f661ea17fbce", + "5889760c-9858-4b4b-879c-e299df493295", + "2d3c27d5-d133-4152-8102-8d051619ec4a" + ), @timestamp, null + ), + Esql.login_ts = CASE( + event.dataset == "okta.system" + AND okta.outcome.result == "SUCCESS", @timestamp, null + ) +// Aggregate by user (catches IP rotation: spray from IP A, login from IP B) +| STATS + Esql.attack_count = SUM(Esql.is_attack_alert), + Esql.login_count = SUM(Esql.is_success_login), + Esql.earliest_attack = MIN(Esql.attack_ts), + Esql.latest_attack = MAX(Esql.attack_ts), + Esql.earliest_login = MIN(Esql.login_ts), + Esql.latest_login = MAX(Esql.login_ts), + Esql.attack_source_ips = VALUES(Esql.attack_ip), + Esql.login_source_ips = VALUES(Esql.login_ip), + Esql.all_source_ips = VALUES(Esql.source_ip), + Esql.alert_rule_ids = VALUES(kibana.alert.rule.rule_id), + Esql.alert_rule_names = VALUES(kibana.alert.rule.name), + Esql.event_action_values = VALUES(event.action), + Esql.geo_country_values = VALUES(client.geo.country_name), + Esql.geo_city_values = VALUES(client.geo.city_name), + Esql.source_asn_values = VALUES(source.as.number), + Esql.source_asn_org_values = VALUES(source.as.organization.name), + Esql.user_agent_values = VALUES(okta.client.user_agent.raw_user_agent), + Esql.device_values = VALUES(okta.client.device), + Esql.is_proxy_values = VALUES(okta.security_context.is_proxy) + BY Esql.user +// Calculate time gap between latest attack and earliest subsequent login +| EVAL Esql.attack_to_login_minutes = DATE_DIFF("minute", Esql.latest_attack, Esql.earliest_login) +// Correlation: attack BEFORE login + success within reasonable window (3 hours) +| WHERE + Esql.attack_count > 0 + AND Esql.login_count > 0 + AND Esql.latest_attack < Esql.earliest_login + AND Esql.attack_to_login_minutes <= 180 +| SORT Esql.login_count DESC +| KEEP Esql.* +''' + + +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1110" +name = "Brute Force" +reference = "https://attack.mitre.org/techniques/T1110/" +[[rule.threat.technique.subtechnique]] +id = "T1110.001" +name = "Password Guessing" +reference = "https://attack.mitre.org/techniques/T1110/001/" + +[[rule.threat.technique.subtechnique]] +id = "T1110.003" +name = "Password Spraying" +reference = "https://attack.mitre.org/techniques/T1110/003/" + +[[rule.threat.technique.subtechnique]] +id = "T1110.004" +name = "Credential Stuffing" +reference = "https://attack.mitre.org/techniques/T1110/004/" + + + +[rule.threat.tactic] +id = "TA0006" +name = "Credential Access" +reference = "https://attack.mitre.org/tactics/TA0006/" +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1078" +name = "Valid Accounts" +reference = "https://attack.mitre.org/techniques/T1078/" +[[rule.threat.technique.subtechnique]] +id = "T1078.004" +name = "Cloud Accounts" +reference = "https://attack.mitre.org/techniques/T1078/004/" + + + +[rule.threat.tactic] +id = "TA0001" +name = "Initial Access" +reference = "https://attack.mitre.org/tactics/TA0001/"