Detection rules › Splunk

Okta Risk Threshold Exceeded

Status
production
Severity
informational
Group by
All_Risk.risk_object, All_Risk.risk_object_type
Author
Michael Haag, Bhavin Patel, Splunk
Source
github.com/splunk/security_content

The following correlation identifies when a user exceeds a risk threshold based on multiple suspicious Okta activities. It leverages the Risk Framework from Enterprise Security, aggregating risk events from "Suspicious Okta Activity," "Okta Account Takeover," and "Okta MFA Exhaustion" analytic stories. This detection is significant as it highlights potentially compromised user accounts exhibiting multiple tactics, techniques, and procedures (TTPs) within a 24-hour period. If confirmed malicious, this activity could indicate a serious security breach, allowing attackers to gain unauthorized access, escalate privileges, or persist within the environment.

MITRE ATT&CK coverage

TacticTechniques
Initial AccessT1078 Valid Accounts
PersistenceT1078 Valid Accounts
Privilege EscalationT1078 Valid Accounts
StealthT1078 Valid Accounts
Credential AccessT1110 Brute Force

Rule body splunk

name: Okta Risk Threshold Exceeded
id: d8b967dd-657f-4d88-93b5-c588bcd7218c
version: 10
creation_date: '2022-09-29'
modification_date: '2026-05-13'
author: Michael Haag, Bhavin Patel, Splunk
status: production
type: Correlation
description: The following correlation identifies when a user exceeds a risk threshold based on multiple suspicious Okta activities. It leverages the Risk Framework from Enterprise Security, aggregating risk events from "Suspicious Okta Activity," "Okta Account Takeover," and "Okta MFA Exhaustion" analytic stories. This detection is significant as it highlights potentially compromised user accounts exhibiting multiple tactics, techniques, and procedures (TTPs) within a 24-hour period. If confirmed malicious, this activity could indicate a serious security breach, allowing attackers to gain unauthorized access, escalate privileges, or persist within the environment.
data_source:
    - Okta
search: |-
    | tstats `security_content_summariesonly` values(All_Risk.analyticstories) as analyticstories  sum(All_Risk.calculated_risk_score) as risk_score, count(All_Risk.calculated_risk_score) as risk_event_count,values(All_Risk.annotations.mitre_attack.mitre_tactic_id) as annotations.mitre_attack.mitre_tactic_id, dc(All_Risk.annotations.mitre_attack.mitre_tactic_id) as mitre_tactic_id_count, values(All_Risk.annotations.mitre_attack.mitre_technique_id) as annotations.mitre_attack.mitre_technique_id, dc(All_Risk.annotations.mitre_attack.mitre_technique_id) as mitre_technique_id_count, values(All_Risk.tag) as tag, values(source) as source, dc(source) as source_count FROM datamodel=Risk.All_Risk
      WHERE All_Risk.risk_object_type = user All_Risk.analyticstories IN ("Okta Account Takeover", "Suspicious Okta Activity","Okta MFA Exhaustion")
      BY All_Risk.risk_object,All_Risk.risk_object_type
    | `drop_dm_object_name("All_Risk")`
    | search mitre_technique_id_count > 5
    | `okta_risk_threshold_exceeded_filter`
how_to_implement: This search leverages the Risk Framework from Enterprise Security. Ensure that "Suspicious Okta Activity", "Okta Account Takeover", and "Okta MFA Exhaustion" analytic stories are enabled. TTPs may be set to finding for point detections; anomalies should not be findings but rather intermediate findings. The correlation relies on intermediate findings before generating a findings. Modify the value as needed.
known_false_positives: False positives will be limited to the number of events generated by the analytics tied to the stories. Analytics will need to be tested and tuned, and the risk score reduced as needed based on the organization.
references:
    - https://developer.okta.com/docs/reference/api/event-types
    - https://sec.okta.com/everythingisyes
drilldown_searches:
    - name: View the detection results for - "$risk_object$"
      search: '%original_detection_search% | search  risk_object = "$risk_object$"'
      earliest_offset: $info_min_time$
      latest_offset: $info_max_time$
    - name: View risk events for the last 7 days for - "$risk_object$"
      search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$risk_object$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`'
      earliest_offset: 7d
      latest_offset: "0"
finding:
    title: Multiple suspicious Okta risk events - $risk_object$
    entity:
        field: risk_object
        type: user
        score: 0
analytic_story:
    - Okta Account Takeover
    - Okta MFA Exhaustion
    - Suspicious Okta Activity
asset_type: Okta Tenant
mitre_attack_id:
    - T1078
    - T1110
product:
    - Splunk Enterprise
    - Splunk Enterprise Security
    - Splunk Cloud
category: application
security_domain: access
tests:
    - name: True Positive Test
      attack_data:
        - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/suspicious_behaviour/okta_account_takeover_risk_events/okta_risk.log
          source: risk_data
          sourcetype: stash
      test_type: unit

Stages and Predicates

Stage 1: tstats

| tstats `security_content_summariesonly` values(All_Risk.analyticstories) as analyticstories  sum(All_Risk.calculated_risk_score) as risk_score, count(All_Risk.calculated_risk_score) as risk_event_count,values(All_Risk.annotations.mitre_attack.mitre_tactic_id) as annotations.mitre_attack.mitre_tactic_id, dc(All_Risk.annotations.mitre_attack.mitre_tactic_id) as mitre_tactic_id_count, values(All_Risk.annotations.mitre_attack.mitre_technique_id) as annotations.mitre_attack.mitre_technique_id, dc(All_Risk.annotations.mitre_attack.mitre_technique_id) as mitre_technique_id_count, values(All_Risk.tag) as tag, values(source) as source, dc(source) as source_count FROM datamodel=Risk.All_Risk
  WHERE All_Risk.risk_object_type = user All_Risk.analyticstories IN ("Okta Account Takeover", "Suspicious Okta Activity","Okta MFA Exhaustion")
  BY All_Risk.risk_object,All_Risk.risk_object_type

Stage 2: search

| `drop_dm_object_name("All_Risk")`

Stage 3: search

| search mitre_technique_id_count > 5

Stage 4: search

| `okta_risk_threshold_exceeded_filter`

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
All_Risk.analyticstoriesin
  • "Okta Account Takeover"
  • "Okta MFA Exhaustion"
  • "Suspicious Okta Activity"
All_Risk.risk_object_typeeq
  • user
mitre_technique_id_countgt
  • 5