Okta Successful Login After Credential Attack
ELASTIC detection rule by Elastic · Elastic License v2
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.
[metadata]
creation_date = "2026/02/12"
integration = ["okta"]
maturity = "production"
updated_date = "2026/04/10"
[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
data_stream.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(
data_stream.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(
data_stream.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(
data_stream.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/"