Detection rules › Elastic

Okta Successful Login After Credential Attack

Status
production
Severity
high
Time window
6h
Group by
Esql.user
Author
Elastic
Source
github.com/elastic/detection-rules

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.

Known 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.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
OktaSystem Log event type user.authentication.auth: Authenticate user.
OktaSystem Log event type user.authentication.auth_identifier_not_in_policy: Authenticate a user with an identifier that's not in the User Profile Policy.
OktaSystem Log event type user.authentication.auth_unconfigured_identifier: Fired after a user authenticates via a directory instance that is not the highest priority profile source for the user.
OktaSystem Log event type user.authentication.auth_via_AD_agent: Authenticate user with AD agent.
OktaSystem Log event type user.authentication.auth_via_IDP: Authenticate user via IDP.
OktaSystem Log event type user.authentication.auth_via_LDAP_agent: Authenticate user via LDAP agent.
OktaSystem Log event type user.authentication.auth_via_inbound_SAML: Authenticate user via inbound SAML.
OktaSystem Log event type user.authentication.auth_via_inbound_delauth: Authenticate user via inbound delauth.
OktaSystem Log event type user.authentication.auth_via_iwa: Authenticate user via IWA.
OktaSystem Log event type user.authentication.auth_via_mfa: User authenticated via MFA
OktaSystem Log event type user.authentication.auth_via_radius: Authentication of user via Radius.
OktaSystem Log event type user.authentication.auth_via_richclient: Authentication of a user via Rich Client.
OktaSystem Log event type user.authentication.auth_via_social: User authenticated via social login
OktaSystem Log event type user.authentication.authenticate: Authentication via device trust certificate.
OktaSystem Log event type user.authentication.dsso_via_non_priority_source: Desktop Single Sign On (DSSO) authentication has been attempted using a profile source that is not the highest priority profile source for the given Okta user.
OktaSystem Log event type user.authentication.slo: User single logout out (SLO) from app.
OktaSystem Log event type user.authentication.sso: User SSO to application
OktaSystem Log event type user.authentication.universal_logout: This event is fired when an admin or system account triggers Universal Logout against an app instance.
OktaSystem Log event type user.authentication.universal_logout.scheduled: This event is fired when an admin manually triggers Universal Logout for a user.
OktaSystem Log event type user.authentication.verify: User identity verified
OktaSystem Log event type user.session.start: User logged in to Okta

Rules detecting the same action

These rules filter on the same operation.

Rule body

[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/"

Stages and Predicates

Stage 1: from

FROM .alerts-security.*, logs-okta.system-* METADATA _id, _version, _index

Stage 2: where

| WHERE
    (
        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"
        )
    )
    OR (
        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
    )

Stage 3: eval

| EVAL
    Esql.user = COALESCE(okta.actor.alternate_id, user.name, user.email),
    Esql.source_ip = COALESCE(okta.client.ip, client.ip, source.ip)

Stage 4: where

| WHERE Esql.user IS NOT NULL

Stage 5: eval

| 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
    )
Esql.attack_ip =
ifkibana.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
elsenull
Esql.attack_ts =
ifkibana.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
elsenull
Esql.is_attack_alert =
ifkibana.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
else0
Esql.is_success_login =
ifdata_stream.dataset == "okta.system" AND okta.outcome.result == "SUCCESS"1
else0
Esql.login_ip =
ifdata_stream.dataset == "okta.system" AND okta.outcome.result == "SUCCESS"Esql.source_ip
elsenull
Esql.login_ts =
ifdata_stream.dataset == "okta.system" AND okta.outcome.result == "SUCCESS"@timestamp
elsenull

Stage 6: stats

| 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

Stage 7: eval

| EVAL Esql.attack_to_login_minutes = DATE_DIFF("minute", Esql.latest_attack, Esql.earliest_login)

Stage 8: where

| WHERE
    Esql.attack_count > 0
    AND Esql.login_count > 0
    AND Esql.latest_attack < Esql.earliest_login
    AND Esql.attack_to_login_minutes <= 180

Stage 9: sort

| SORT Esql.login_count DESC

Stage 10: keep

| KEEP Esql.*

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
Esql.*KEEP Esql.*