From a6bd8dfc553189be5e0711b85bd7c2a526f0433e Mon Sep 17 00:00:00 2001 From: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:36:44 -0400 Subject: [PATCH] [New Hunt] Add Initial Okta Hunting Queries (#4064) * adding new Okta hunting queries * query format changes * adding docs * added query for mfa bombing * adding remainder hunting queries * adjusted incorrect hunt * updated queries * updated queries based on Samir's feedback * removed failed login eval * updated docs (cherry picked from commit 9181c0058663e6e4fbf1f7fec1d91b8852909108) --- hunting/index.md | 15 +++++ ...edrock_ignore_previous_prompt_detection.md | 4 +- ...gher_than_average_failed_authentication.md | 58 ++++++++++++++++++ ...ial_access_mfa_bombing_push_notications.md | 46 +++++++++++++++ ...ss_password_spraying_from_repeat_source.md | 53 +++++++++++++++++ ...t_password_requests_for_different_users.md | 48 +++++++++++++++ ...s_token_retrieval_via_public_client_app.md | 51 ++++++++++++++++ ...cation_sso_authentication_repeat_source.md | 54 +++++++++++++++++ ...eported_for_oauth_access_tokens_granted.md | 56 ++++++++++++++++++ ...uth_access_token_granted_by_application.md | 54 +++++++++++++++++ ...gher_than_average_failed_authentication.md | 59 +++++++++++++++++++ ...gher_than_average_failed_authentication.md | 56 ++++++++++++++++++ ...nitial_access_impossible_travel_sign_on.md | 48 +++++++++++++++ ...ss_password_spraying_from_repeat_source.md | 53 +++++++++++++++++ ..._multi_factor_push_notification_bombing.md | 46 +++++++++++++++ ...ce_rare_domain_with_user_authentication.md | 48 +++++++++++++++ ...tence_rare_tld_with_user_authentication.md | 48 +++++++++++++++ ...l_access_mfa_bombing_push_notications.toml | 34 +++++++++++ ...password_requests_for_different_users.toml | 34 +++++++++++ ...token_retrieval_via_public_client_app.toml | 37 ++++++++++++ ...tion_sso_authentication_repeat_source.toml | 40 +++++++++++++ ...orted_for_oauth_access_tokens_granted.toml | 42 +++++++++++++ ...h_access_token_granted_by_application.toml | 41 +++++++++++++ ...er_than_average_failed_authentication.toml | 42 +++++++++++++ ...tial_access_impossible_travel_sign_on.toml | 34 +++++++++++ ..._password_spraying_from_repeat_source.toml | 39 ++++++++++++ ...ulti_factor_push_notification_bombing.toml | 32 ++++++++++ ..._rare_domain_with_user_authentication.toml | 34 +++++++++++ 28 files changed, 1204 insertions(+), 2 deletions(-) create mode 100644 hunting/okta/docs/docs/credential_access_hgher_than_average_failed_authentication.md create mode 100644 hunting/okta/docs/docs/credential_access_mfa_bombing_push_notications.md create mode 100644 hunting/okta/docs/docs/credential_access_password_spraying_from_repeat_source.md create mode 100644 hunting/okta/docs/docs/credential_access_rapid_reset_password_requests_for_different_users.md create mode 100644 hunting/okta/docs/docs/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.md create mode 100644 hunting/okta/docs/docs/defense_evasion_multiple_application_sso_authentication_repeat_source.md create mode 100644 hunting/okta/docs/docs/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.md create mode 100644 hunting/okta/docs/docs/defense_evasion_rare_oauth_access_token_granted_by_application.md create mode 100644 hunting/okta/docs/docs/initial_access_hgher_than_average_failed_authentication.md create mode 100644 hunting/okta/docs/docs/initial_access_higher_than_average_failed_authentication.md create mode 100644 hunting/okta/docs/docs/initial_access_impossible_travel_sign_on.md create mode 100644 hunting/okta/docs/docs/initial_access_password_spraying_from_repeat_source.md create mode 100644 hunting/okta/docs/docs/persistence_multi_factor_push_notification_bombing.md create mode 100644 hunting/okta/docs/docs/persistence_rare_domain_with_user_authentication.md create mode 100644 hunting/okta/docs/docs/persistence_rare_tld_with_user_authentication.md create mode 100644 hunting/okta/docs/queries/credential_access_mfa_bombing_push_notications.toml create mode 100644 hunting/okta/docs/queries/credential_access_rapid_reset_password_requests_for_different_users.toml create mode 100644 hunting/okta/docs/queries/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.toml create mode 100644 hunting/okta/docs/queries/defense_evasion_multiple_application_sso_authentication_repeat_source.toml create mode 100644 hunting/okta/docs/queries/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.toml create mode 100644 hunting/okta/docs/queries/defense_evasion_rare_oauth_access_token_granted_by_application.toml create mode 100644 hunting/okta/docs/queries/initial_access_higher_than_average_failed_authentication.toml create mode 100644 hunting/okta/docs/queries/initial_access_impossible_travel_sign_on.toml create mode 100644 hunting/okta/docs/queries/initial_access_password_spraying_from_repeat_source.toml create mode 100644 hunting/okta/docs/queries/persistence_multi_factor_push_notification_bombing.toml create mode 100644 hunting/okta/docs/queries/persistence_rare_domain_with_user_authentication.toml diff --git a/hunting/index.md b/hunting/index.md index 0d83e1173a2..8962a3f40de 100644 --- a/hunting/index.md +++ b/hunting/index.md @@ -22,6 +22,20 @@ Here are the queries currently available: - [STS Suspicious Federated Temporary Credential Request](./aws/docs/sts_suspicious_federated_temporary_credential_request.md) (ES|QL) +## docs +- [Rapid MFA Deny Push Notifications (MFA Bombing)](./okta/docs/docs/credential_access_mfa_bombing_push_notications.md) (ES|QL) +- [Rapid Reset Password Requests for Different Users](./okta/docs/docs/credential_access_rapid_reset_password_requests_for_different_users.md) (ES|QL) +- [Failed OAuth Access Token Retrieval via Public Client App](./okta/docs/docs/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.md) (ES|QL) +- [Multiple Application SSO Authentication from the Same Source](./okta/docs/docs/defense_evasion_multiple_application_sso_authentication_repeat_source.md) (ES|QL) +- [OAuth Access Token Granted for Public Client App from Multiple Client Addresses](./okta/docs/docs/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.md) (ES|QL) +- [Rare Occurrence of OAuth Access Token Granted to Public Client App](./okta/docs/docs/defense_evasion_rare_oauth_access_token_granted_by_application.md) (ES|QL) +- [Identify High Average of Failed Daily Authentication Attempts](./okta/docs/docs/initial_access_higher_than_average_failed_authentication.md) (ES|QL) +- [Successful Impossible Travel Sign-On Events](./okta/docs/docs/initial_access_impossible_travel_sign_on.md) (ES|QL) +- [Password Spraying from Repeat Source](./okta/docs/docs/initial_access_password_spraying_from_repeat_source.md) (ES|QL) +- [Multi-Factor Authentication (MFA) Push Notification Bombing](./okta/docs/docs/persistence_multi_factor_push_notification_bombing.md) (ES|QL) +- [Rare Occurrence of Domain with User Authentication Events](./okta/docs/docs/persistence_rare_domain_with_user_authentication.md) (ES|QL) + + ## linux - [Network Connections with Low Occurrence Frequency for Unique Agent ID](./linux/docs/command_and_control_via_network_connections_with_low_occurrence_frequency_for_unique_agents.md) (ES|QL) - [Unusual File Downloads from Source Addresses](./linux/docs/command_and_control_via_unusual_file_downloads_from_source_addresses.md) (ES|QL) @@ -58,6 +72,7 @@ Here are the queries currently available: ## llm - [AWS Bedrock LLM Denial-of-Service or Resource Exhaustion](./llm/docs/aws_bedrock_dos_resource_exhaustion_detection.md) (ES|QL) +- [AWS Bedrock LLM Ignore Previous Prompt Detection](./llm/docs/aws_bedrock_ignore_previous_prompt_detection.md) (ES|QL) - [AWS Bedrock LLM Latency Anomalies](./llm/docs/aws_bedrock_latency_anomalies_detection.md) (ES|QL) - [AWS Bedrock LLM Sensitive Content Refusals](./llm/docs/aws_bedrock_sensitive_content_refusal_detection.md) (ES|QL) diff --git a/hunting/llm/docs/aws_bedrock_ignore_previous_prompt_detection.md b/hunting/llm/docs/aws_bedrock_ignore_previous_prompt_detection.md index aa41c2f459e..7e4def5c2af 100644 --- a/hunting/llm/docs/aws_bedrock_ignore_previous_prompt_detection.md +++ b/hunting/llm/docs/aws_bedrock_ignore_previous_prompt_detection.md @@ -9,7 +9,7 @@ - **UUID:** `131e5887-463a-46a1-a44e-b96361bc6cbc` - **Integration:** [aws_bedrock.invocation](https://docs.elastic.co/integrations/aws_bedrock) - **Language:** `[ES|QL]` -- **Source File:** [AWS Bedrock LLM Sensitive Content Refusals](../queries/aws_bedrock_ignore_previous_prompt_detection.toml) +- **Source File:** [AWS Bedrock LLM Ignore Previous Prompt Detection](../queries/aws_bedrock_ignore_previous_prompt_detection.toml) ## Query @@ -33,7 +33,7 @@ from logs-aws_bedrock.invocation-* ## Notes -- Examine flagged interactions for patterns or anomalies in user requests that may indicate malicious intent to expose LLM vulnerabilities. +- Examine flagged interactions for patterns or anomalies in user requests that may indicate malicious intent to expose LLM vulnerabilities - Regularly review and update the phrases that trigger ignore previous prompt attacks to adapt to new ethical guidelines and compliance requirements. - Ensure that data logs contain enough detail to provide context around the refusal, which will aid in subsequent investigations by security teams. diff --git a/hunting/okta/docs/docs/credential_access_hgher_than_average_failed_authentication.md b/hunting/okta/docs/docs/credential_access_hgher_than_average_failed_authentication.md new file mode 100644 index 00000000000..64d277d83fc --- /dev/null +++ b/hunting/okta/docs/docs/credential_access_hgher_than_average_failed_authentication.md @@ -0,0 +1,58 @@ +# Identify High Average of Failed Daily Authentication Attempts + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when the average number of failed daily authentication attempts is higher than normal in Okta. Adversaries may attempt to brute force user credentials to gain unauthorized access to accounts. This query calculates the average number of daily failed authentication attempts for each user and identifies when the average is higher than normal. + +- **UUID:** `c8a35a26-71f1-11ef-9c4e-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Identify High Average of Failed Daily Authentication Attempts](../queries/credential_access_hgher_than_average_failed_authentication.toml) + +## Query + +```sql +from logs-okta* +| where @timestamp > NOW() - 7 day + +// truncate the timestamp to daily intervals +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + + // filter for invalid credential authentication events + event.action == "user.session.start" + and okta.outcome.result == "FAILURE" + and okta.outcome.reason == "INVALID_CREDENTIALS" + +// add a count of 1 for each failed login +| eval failed_login_count = 1 + +| stats + // count the number of daily failed logins for each day and user + failed_daily_logins = count(*) by target_time_window, okta.actor.alternate_id + +| stats + // calculate the average number of daily failed logins for each day + avg_daily_logins = avg(failed_daily_logins) by target_time_window + +// sort the results by the average number of daily failed logins in descending order +| sort avg_daily_logins desc +``` + +## Notes + +- Pivot to users by only keeping the first stats statement where `okta.actor.alternate_id` is the targeted accounts. +- Pivot for successful logins from the same source IP by searching for `event.action` equal to `user.session.start` or `user.authentication.verify` where the outcome is `SUCCESS`. +- User agents can be used to identify anomalous behavior, such as a user agent that is not associated with a known application or user. +- Another `WHERE` count can be added to the query if activity has been baseline to filter out known behavior. + +## MITRE ATT&CK Techniques + +- [T1078.004](https://attack.mitre.org/techniques/T1078/004) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/credential_access_mfa_bombing_push_notications.md b/hunting/okta/docs/docs/credential_access_mfa_bombing_push_notications.md new file mode 100644 index 00000000000..c139616e400 --- /dev/null +++ b/hunting/okta/docs/docs/credential_access_mfa_bombing_push_notications.md @@ -0,0 +1,46 @@ +# Rapid MFA Deny Push Notifications (MFA Bombing) + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies MFA bombing attacks in Okta. Adversaries may attempt to flood a user with multiple MFA push notifications to disrupt operations or gain unauthorized access to accounts. This query identifies when a user has more than 5 MFA deny push notifications in a 10 minute window. + +- **UUID:** `223451b0-6eca-11ef-a070-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Rapid MFA Deny Push Notifications (MFA Bombing)](../queries/credential_access_mfa_bombing_push_notications.toml) + +## Query + +```sql +from logs-okta* +| where @timestamp > NOW() - 7 day + +// Truncate the timestamp to 10 minute windows +| eval target_time_window = DATE_TRUNC(10 minutes, @timestamp) + +// Filter for MFA deny push notifications +| where event.action == "user.mfa.okta_verify.deny_push" + +// Count the number of MFA deny push notifications for each user in each 10 minute window +| stats deny_push_count = count(*) by target_time_window, okta.actor.alternate_id + +// Filter for users with more than 5 MFA deny push notifications +| where deny_push_count >= 5 +``` + +## Notes + +- `okta.actor.alternate_id` is the targeted user account. +- Pivot and search for `event.action` is `user.authentication.auth_via_mfa` to determine if the target user accepted the MFA push notification. +- If a MFA bombing attack is suspected, both username and password are required prior to MFA push notifications. Thus the credentials are likely compromised. + +## MITRE ATT&CK Techniques + +- [T1621](https://attack.mitre.org/techniques/T1621) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/credential_access_password_spraying_from_repeat_source.md b/hunting/okta/docs/docs/credential_access_password_spraying_from_repeat_source.md new file mode 100644 index 00000000000..1a43784c574 --- /dev/null +++ b/hunting/okta/docs/docs/credential_access_password_spraying_from_repeat_source.md @@ -0,0 +1,53 @@ +# Password Spraying from Repeat Source + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies password spraying attacks in Okta where the same source IP attempts to authenticate to multiple accounts with invalid credentials. Adversaries may attempt to use a single source IP to avoid detection and bypass account lockout policies. + +- **UUID:** `1c2d2b08-71ee-11ef-952e-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Password Spraying from Repeat Source](../queries/credential_access_password_spraying_from_repeat_source.toml) + +## Query + +```sql +from logs-okta* +| where @timestamp > NOW() - 7 day + +// truncate the timestamp to daily intervals +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + +// filter for invalid credential events + event.action == "user.session.start" + and okta.outcome.result == "FAILURE" + and okta.outcome.reason == "INVALID_CREDENTIALS" +| stats + // count the distinct number of targeted accounts + target_count = count_distinct(okta.actor.alternate_id), + + // count the number of invalid credential events for each source IP + source_count = count(*) by target_time_window, okta.client.ip + +// filter for source IPs with more than 5 invalid credential events +| where target_count >= 5 +``` + +## Notes + +- `okta.actor.alternate_id` are the targeted accounts. +- Pivot for successful logins from the same source IP by searching for `event.action` equal to `user.session.start` or `user.authentication.verify` where the outcome is `SUCCESS`. +- User agents can be used to identify anomalous behavior, such as a user agent that is not associated with a known application or user. +- The `target_count` can be adjusted depending on the organization's account lockout policy or baselined behavior. + +## MITRE ATT&CK Techniques + +- [T1078.004](https://attack.mitre.org/techniques/T1078/004) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/credential_access_rapid_reset_password_requests_for_different_users.md b/hunting/okta/docs/docs/credential_access_rapid_reset_password_requests_for_different_users.md new file mode 100644 index 00000000000..099a1ec2172 --- /dev/null +++ b/hunting/okta/docs/docs/credential_access_rapid_reset_password_requests_for_different_users.md @@ -0,0 +1,48 @@ +# Rapid Reset Password Requests for Different Users + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies rapid reset password requests for different users in Okta. Adversaries may attempt to reset passwords for multiple users in rapid succession to gain unauthorized access to accounts or disrupt operations. This query identifies when the source user is different from the target user in reset password events and filters for users with more than 15 reset password attempts. + +- **UUID:** `c784106e-6ae8-11ef-919d-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Rapid Reset Password Requests for Different Users](../queries/credential_access_rapid_reset_password_requests_for_different_users.toml) + +## Query + +```sql +from logs-okta.system* +| where @timestamp > NOW() - 7 day + +// Filter for reset password events where the source user is different from the target user +| where event.dataset == "okta.system" and event.action == "user.account.reset_password" and source.user.full_name != user.target.full_name + +// Extract relevant fields +| keep @timestamp, okta.actor.alternate_id, okta.debug_context.debug_data.dt_hash, user.target.full_name, okta.outcome.result + +// Count the number of reset password attempts for each user +| stats + user_count = count_distinct(user.target.full_name), + reset_counts = by okta.actor.alternate_id, source.user.full_name, okta.debug_context.debug_data.dt_hash + +// Filter for more than 10 unique users and more than 15 reset password attempts by the source +| where user_count > 10 and reset_counts > 15 +``` + +## Notes + +- `okta.actor.alternate_id` is the potentially compromised account +- An API access token may have been compromised, where okta.actor.alternate_id reflects the owner +- To identify a list of tokens this user created, search for the `okta.actor.alternate_id` where `event.action` is `system.api_token*` which may require a larger time window + +## MITRE ATT&CK Techniques + +- [T1098.001](https://attack.mitre.org/techniques/T1098/001) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.md b/hunting/okta/docs/docs/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.md new file mode 100644 index 00000000000..cf66d18a3c8 --- /dev/null +++ b/hunting/okta/docs/docs/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.md @@ -0,0 +1,51 @@ +# Failed OAuth Access Token Retrieval via Public Client App + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when a public client app fails to retrieve an OAuth access token using client credentials because of an uauthorized scope. Adversaries may attempt to retrieve access tokens using client credentials to bypass user authentication and access resources. This query identifies when a public client app fails to retrieve an access token using client credentials and scopes that are not implicitly granted. + +- **UUID:** `0b936024-71d9-11ef-a9be-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Failed OAuth Access Token Retrieval via Public Client App](../queries/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.toml) + +## Query + +```sql +from logs-okta.system* +| where @timestamp > NOW() - 7 day +| where + event.dataset == "okta.system" + + // filter on failed access token grant requests where source is a public client app + and event.action == "app.oauth2.as.token.grant" + and okta.actor.type == "PublicClientApp" + and okta.outcome.result == "FAILURE" + + // filter out known Okta and Datadog actors + and not ( + okta.actor.display_name LIKE "Okta%" + or okta.actor.display_name LIKE "Datadog%" + ) + + // filter for scopes that are not implicitly granted + and okta.outcome.reason == "no_matching_scope" +``` + +## Notes + +- Review `okta.debug_context.debug_data.flattened.grantType` to identify if the grant type is `client_credentials` +- Ignore `okta.debug_context.debug_data.flattened.requestedScopes` values that indicate read-only access +- Review `okta.actor.display_name` to identify the public client app that attempted to retrieve the access token. This may help identify the compromised client credentials. +- Pivot for successful access token retrieval by the same public client app by searching `event.action` equal to `app.oauth2.as.token.grant.access_token` where the display name is the same. + +## MITRE ATT&CK Techniques + +- [T1550.001](https://attack.mitre.org/techniques/T1550/001) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/defense_evasion_multiple_application_sso_authentication_repeat_source.md b/hunting/okta/docs/docs/defense_evasion_multiple_application_sso_authentication_repeat_source.md new file mode 100644 index 00000000000..b130b5e7e8b --- /dev/null +++ b/hunting/okta/docs/docs/defense_evasion_multiple_application_sso_authentication_repeat_source.md @@ -0,0 +1,54 @@ +# Multiple Application SSO Authentication from the Same Source + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when a user authenticates to multiple applications using Single Sign-On (SSO) from the same source. Adversaries may attempt to authenticate to multiple applications using SSO to gain unauthorised access to sensitive data or resources. Adversaries also rely on refresh tokens to maintain access to applications and services. This query identifies when a source IP authenticates to more than 15 applications using SSO within a 5-minute window. + +- **UUID:** `03bce3b0-6ded-11ef-9282-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Multiple Application SSO Authentication from the Same Source](../queries/defense_evasion_multiple_application_sso_authentication_repeat_source.toml) + +## Query + +```sql +from logs-okta* +| eval target_time_window = DATE_TRUNC(5 minutes, @timestamp) + +// truncate the timestamp to a 5-minute window +| where @timestamp > now() - 7 day + +// filter for SSO authentication events where the authentication step is 0 +// filter on request URI string '/app/' to identify applications for a user +| where + event.action == "user.authentication.sso" + and okta.authentication_context.authentication_step == 0 + and okta.debug_context.debug_data.request_uri RLIKE "(.*)/app/(.*)" + +// dissect the request URI to extract the target application +| dissect okta.debug_context.debug_data.request_uri"%{?}/app/%{target_application}/" + +// count the number of unique applications per source IP and user in a 5-minute window +| stats application_count = count_distinct(target_application), window_count = count(*) by target_time_window, source.ip, okta.actor.alternate_id + +// filter for at least 15 distinct applications authenticated from a single source IP +| where application_count > 15 +``` + +## Notes + +- `okta.debug_context.debug_data.dt_hash` field can be used to identify the device token hash used for authentication. This can be used to pivot for additional activity from the same device. +- `okta.debug_context.debug_data.flattened` contains additional information such as request ID, trace ID, sign-on mode and more to review for anomalies in the authentication flow. +- `okta.request.ip_chain` can be used to understand more about the source address, which is potentially useful for profiling. +- If `okta.security_context.is_proxy` is `true`, then an adversary may be attempting to mask their true source behind a proxy or VPN. + +## MITRE ATT&CK Techniques + +- [T1550.001](https://attack.mitre.org/techniques/T1550/001) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.md b/hunting/okta/docs/docs/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.md new file mode 100644 index 00000000000..de748630203 --- /dev/null +++ b/hunting/okta/docs/docs/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.md @@ -0,0 +1,56 @@ +# OAuth Access Token Granted for Public Client App from Multiple Client Addresses + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when a public client app successfully retrieves an OAuth access token using client credentials from multiple client addresses. For public client applications in Okta that leverage OAuth, client credentials can be used to retrieve access tokens without user consent. Unsecured credentials may be compromised by an adversary who may use them to request an access token on behalf of the public client app. + +- **UUID:** `38d82c2c-71d9-11ef-a9be-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [OAuth Access Token Granted for Public Client App from Multiple Client Addresses](../queries/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.toml) + +## Query + +```sql +from logs-okta.system* +| where @timestamp > NOW() - 21 day + +// truncate the timestamp to 1 day +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + + // filter for successful OAuth access token grant requests + event.action == "app.oauth2.as.token.grant.access_token" + and event.outcome == "success" + and event.dataset == "okta.system" + + // filter for public client apps + and okta.actor.type == "PublicClientApp" + + // ignore Elastic Okta integration and DataDog actors + and not (okta.actor.display_name LIKE "Okta*" or okta.actor.display_name LIKE "Datadog*") + +// count the number of access tokens granted by the same public client app in a day +| stats token_granted_count = count(*), unique_client_ip = count_distinct(okta.client.ip) by target_time_window, okta.actor.display_name + +// filter where access tokens were granted on the same day but client addresses are different +| where unique_client_ip >= 2 and token_granted_count >= 2 +``` + +## Notes + +- Review `okta.debug_context.debug_data.flattened.grantType` to identify if the grant type is `client_credentials` +- Ignore `okta.debug_context.debug_data.flattened.requestedScopes` values that indicate read-only access +- Review `okta.actor.display_name` to identify the public client app that attempted to retrieve the access token. This may help identify the compromised client credentials. +- Filter on the public client app and aggregate by `event.action` to determine what actions were taken by the public client app after the access token was granted. + +## MITRE ATT&CK Techniques + +- [T1550.001](https://attack.mitre.org/techniques/T1550/001) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/defense_evasion_rare_oauth_access_token_granted_by_application.md b/hunting/okta/docs/docs/defense_evasion_rare_oauth_access_token_granted_by_application.md new file mode 100644 index 00000000000..3d4088ba4bb --- /dev/null +++ b/hunting/okta/docs/docs/defense_evasion_rare_oauth_access_token_granted_by_application.md @@ -0,0 +1,54 @@ +# Rare Occurrence of OAuth Access Token Granted to Public Client App + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies a rare occurrence of a public client app successfully retrieves an OAuth access token using client credentials as the grant type within the last 14 days. Public client applications in Okta that leverage OAuth, client credentials can be used to retrieve access tokens without user consent. Unsecured credentials may be compromised by an adversary whom may use them to request an access token on behalf of the public client app. + +- **UUID:** `11666aa0-71d9-11ef-a9be-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Rare Occurrence of OAuth Access Token Granted to Public Client App](../queries/defense_evasion_rare_oauth_access_token_granted_by_application.toml) + +## Query + +```sql +from logs-okta.system* +| where @timestamp > NOW() - 14 day +| where + + // filter for successful OAuth access token grant requests + event.action == "app.oauth2.as.token.grant.access_token" + and event.outcome == "success" + and event.dataset == "okta.system" + + // filter for public client apps + and okta.actor.type == "PublicClientApp" + + // ignore Elastic Okta integration and DataDog actors + and not okta.client.user_agent.raw_user_agent == "Okta-Integrations" + and not (okta.actor.display_name LIKE "Okta%" or okta.actor.display_name LIKE "Datadog%") + +// count the number of access tokens granted by the same public client app +| stats token_granted_count = count(*) by okta.actor.display_name + +// filter where the public client app has only been granted an access token once in the last 14 days +| where token_granted_count == 1 +``` + +## Notes + +- Review `okta.debug_context.debug_data.flattened.grantType` to identify if the grant type is `client_credentials` +- Ignore `okta.debug_context.debug_data.flattened.requestedScopes` values that indicate read-only access +- Review `okta.actor.display_name` to identify the public client app that attempted to retrieve the access token. This may help identify the compromised client credentials. +- False-positives may exist if the public client app is new or has not been used in the last 14 days. + +## MITRE ATT&CK Techniques + +- [T1550.001](https://attack.mitre.org/techniques/T1550/001) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/initial_access_hgher_than_average_failed_authentication.md b/hunting/okta/docs/docs/initial_access_hgher_than_average_failed_authentication.md new file mode 100644 index 00000000000..bf30579e23b --- /dev/null +++ b/hunting/okta/docs/docs/initial_access_hgher_than_average_failed_authentication.md @@ -0,0 +1,59 @@ +# Identify High Average of Failed Daily Authentication Attempts + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when the average number of failed daily authentication attempts is higher than normal in Okta. Adversaries may attempt to brute force user credentials to gain unauthorized access to accounts. This query calculates the average number of daily failed authentication attempts for each user and identifies when the average is higher than normal. + +- **UUID:** `c8a35a26-71f1-11ef-9c4e-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Identify High Average of Failed Daily Authentication Attempts](../queries/initial_access_hgher_than_average_failed_authentication.toml) + +## Query + +```sql +from logs-okta* +| where @timestamp > NOW() - 7 day + +// truncate the timestamp to daily intervals +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + + // filter for invalid credential authentication events + event.action == "user.session.start" + and okta.outcome.result == "FAILURE" + and okta.outcome.reason == "INVALID_CREDENTIALS" + and okta.actor.type == "User" + +// add a count of 1 for each failed login +| eval failed_login_count = 1 + +| stats + // count the number of daily failed logins for each day and user + failed_daily_logins = count(*) by target_time_window, okta.actor.alternate_id + +| stats + // calculate the average number of daily failed logins for each day + avg_daily_logins = avg(failed_daily_logins) by target_time_window + +// sort the results by the average number of daily failed logins in descending order +| sort avg_daily_logins desc +``` + +## Notes + +- Pivot to users by only keeping the first stats statement where `okta.actor.alternate_id` is the targeted accounts. +- Pivot for successful logins from the same source IP by searching for `event.action` equal to `user.session.start` or `user.authentication.verify` where the outcome is `SUCCESS`. +- User agents can be used to identify anomalous behavior, such as a user agent that is not associated with a known application or user. +- Another `WHERE` count can be added to the query if activity has been baseline to filter out known behavior. + +## MITRE ATT&CK Techniques + +- [T1078.004](https://attack.mitre.org/techniques/T1078/004) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/initial_access_higher_than_average_failed_authentication.md b/hunting/okta/docs/docs/initial_access_higher_than_average_failed_authentication.md new file mode 100644 index 00000000000..f081c334257 --- /dev/null +++ b/hunting/okta/docs/docs/initial_access_higher_than_average_failed_authentication.md @@ -0,0 +1,56 @@ +# Identify High Average of Failed Daily Authentication Attempts + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when the average number of failed daily authentication attempts is higher than normal in Okta. Adversaries may attempt to brute force user credentials to gain unauthorized access to accounts. This query calculates the average number of daily failed authentication attempts for each user and identifies when the average is higher than normal. + +- **UUID:** `c8a35a26-71f1-11ef-9c4e-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Identify High Average of Failed Daily Authentication Attempts](../queries/initial_access_higher_than_average_failed_authentication.toml) + +## Query + +```sql +from logs-okta* +| where @timestamp > NOW() - 7 day + +// truncate the timestamp to daily intervals +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + + // filter for invalid credential authentication events + event.action == "user.session.start" + and okta.outcome.result == "FAILURE" + and okta.outcome.reason == "INVALID_CREDENTIALS" + and okta.actor.type == "User" + +| stats + // count the number of daily failed logins for each day and user + failed_daily_logins = count(*) by target_time_window, okta.actor.alternate_id + +| stats + // calculate the average number of daily failed logins for each day + avg_daily_logins = avg(failed_daily_logins) by target_time_window + +// sort the results by the average number of daily failed logins in descending order +| sort avg_daily_logins desc +``` + +## Notes + +- Pivot to users by only keeping the first stats statement where `okta.actor.alternate_id` is the targeted accounts. +- Pivot for successful logins from the same source IP by searching for `event.action` equal to `user.session.start` or `user.authentication.verify` where the outcome is `SUCCESS`. +- User agents can be used to identify anomalous behavior, such as a user agent that is not associated with a known application or user. +- Another `WHERE` count can be added to the query if activity has been baseline to filter out known behavior. + +## MITRE ATT&CK Techniques + +- [T1078.004](https://attack.mitre.org/techniques/T1078/004) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/initial_access_impossible_travel_sign_on.md b/hunting/okta/docs/docs/initial_access_impossible_travel_sign_on.md new file mode 100644 index 00000000000..a6a3044bc58 --- /dev/null +++ b/hunting/okta/docs/docs/initial_access_impossible_travel_sign_on.md @@ -0,0 +1,48 @@ +# Successful Impossible Travel Sign-On Events + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when a user successfully signs on from more than one country in a 15 minute interval. Adversaries may compromise authentication credentials for users or clients and attempt to authenticate from a separate country that the user has not previously authenticated from. + +- **UUID:** `31585786-71f4-11ef-9e99-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Successful Impossible Travel Sign-On Events](../queries/initial_access_impossible_travel_sign_on.toml) + +## Query + +```sql +from logs-okta.system* +| where @timestamp > NOW() - 7 day +| where event.dataset == "okta.system" + + // filter on successful sign-on events only + and okta.event_type == "policy.evaluate_sign_on" + and okta.outcome.result in ("ALLOW", "SUCCESS") + +// Truncate the timestamp to 1 hour intervals +| eval time_window = DATE_TRUNC(1 hours, @timestamp) + +// Count the number of successful sign-on events for each user every 15 minutes +| stats country_count = count_distinct(client.geo.country_name) by okta.actor.alternate_id, time_window + +// Filter for users who sign on from more than one country in a 15 minute interval +| where country_count >= 2 +``` + +## Notes + +- `okta.actor.alternate_id` would be target of the threat adversary +- Pivoting into a potential compromise requires an additional search for `okta.outcome.result` being `SUCCESS` for any `user.authentication*` value for `okta.event_type` +- Pivot to any additional Okta logs after authentication to determine if activity is still being reported by separate countries. + +## MITRE ATT&CK Techniques + +- [T1078.004](https://attack.mitre.org/techniques/T1078/004) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/initial_access_password_spraying_from_repeat_source.md b/hunting/okta/docs/docs/initial_access_password_spraying_from_repeat_source.md new file mode 100644 index 00000000000..8ffe4c3d953 --- /dev/null +++ b/hunting/okta/docs/docs/initial_access_password_spraying_from_repeat_source.md @@ -0,0 +1,53 @@ +# Password Spraying from Repeat Source + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies password spraying attacks in Okta where the same source IP attempts to authenticate to multiple accounts with invalid credentials. Adversaries may attempt to use a single source IP to avoid detection and bypass account lockout policies. + +- **UUID:** `1c2d2b08-71ee-11ef-952e-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Password Spraying from Repeat Source](../queries/initial_access_password_spraying_from_repeat_source.toml) + +## Query + +```sql +from logs-okta* +| where @timestamp > NOW() - 7 day + +// truncate the timestamp to daily intervals +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + +// filter for invalid credential events + event.action == "user.session.start" + and okta.outcome.result == "FAILURE" + and okta.outcome.reason == "INVALID_CREDENTIALS" +| stats + // count the distinct number of targeted accounts + target_count = count_distinct(okta.actor.alternate_id), + + // count the number of invalid credential events for each source IP + source_count = count(*) by target_time_window, okta.client.ip + +// filter for source IPs with more than 5 invalid credential events +| where target_count >= 10 +``` + +## Notes + +- `okta.actor.alternate_id` are the targeted accounts. +- Pivot for successful logins from the same source IP by searching for `event.action` equal to `user.session.start` or `user.authentication.verify` where the outcome is `SUCCESS`. +- User agents can be used to identify anomalous behavior, such as a user agent that is not associated with a known application or user. +- The `target_count` can be adjusted depending on the organization's account lockout policy or baselined behavior. + +## MITRE ATT&CK Techniques + +- [T1078.004](https://attack.mitre.org/techniques/T1078/004) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/persistence_multi_factor_push_notification_bombing.md b/hunting/okta/docs/docs/persistence_multi_factor_push_notification_bombing.md new file mode 100644 index 00000000000..17a207e23b3 --- /dev/null +++ b/hunting/okta/docs/docs/persistence_multi_factor_push_notification_bombing.md @@ -0,0 +1,46 @@ +# Multi-Factor Authentication (MFA) Push Notification Bombing + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when a user denies multiple push notifications for multi-factor authentication (MFA) in rapid succession. Adversaries may attempt to deny push notifications to flood the target user's device with notifications, causing the user to ignore legitimate notifications or potentially disable MFA. This query identifies when a user denies more than 5 push notifications in a single hour. + +- **UUID:** `7c51fe3e-6ae9-11ef-919d-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Multi-Factor Authentication (MFA) Push Notification Bombing](../queries/persistence_multi_factor_push_notification_bombing.toml) + +## Query + +```sql +from logs-okta.system* +| where @timestamp > NOW() - 7 day + +// Filter for deny push notifications for multi-factor authentication +| where event.dataset == "okta.system" and event.action == "user.mfa.okta_verify.deny_push" + +// Truncate the timestamp to hourly intervals +| eval hourly_count = date_trunc(1 hour, event.ingested) + +// Count the number of deny push notifications for each user every hour +| stats hourly_denies = count(*) by okta.actor.alternate_id, hourly_count + +// Filter for users who deny more than 5 push notifications in a single hour +| where hourly_denies > 5 +``` + +## Notes + +- `okta.actor.alternate_id` would be target of the threat adversary +- Pivoting into a potential compromise requires an additional search for `okta.outcome.result` being `SUCCESS` for any `user.authentication*` value for `okta.event_type` +- For a smaller window (rapid denies), reduce from 1 hour to 30 minutes or lower + +## MITRE ATT&CK Techniques + +- [T1556.006](https://attack.mitre.org/techniques/T1556/006) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/persistence_rare_domain_with_user_authentication.md b/hunting/okta/docs/docs/persistence_rare_domain_with_user_authentication.md new file mode 100644 index 00000000000..072d98a6e2b --- /dev/null +++ b/hunting/okta/docs/docs/persistence_rare_domain_with_user_authentication.md @@ -0,0 +1,48 @@ +# Rare Occurrence of Domain with User Authentication Events + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies rare occurrences of user authentication events for an Okta user whose registered user account email address has a domain that is not commonly seen in the organization. Adversaries may use compromised credentials or tokens to create a new user account with a domain that is not commonly seen in the organization because they do not have access to a valid email address within that domain. + +- **UUID:** `f3bc68f4-71e9-11ef-952e-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Rare Occurrence of Domain with User Authentication Events](../queries/persistence_rare_domain_with_user_authentication.toml) + +## Query + +```sql +from logs-okta* +| where @timestamp > NOW() - 7 day +| where + // Filter for user authentication events + okta.actor.alternate_id is not null + and event.action LIKE "user.authentication*" + +// Extract the top-level domain (TLD) from the user's email address +| dissect okta.actor.alternate_id "%{}@%{tld}" + +// Count the number of user authentication events for each TLD +| stats tld_auth_counts = count(*) by tld + +// Filter for TLDs with less than or equal to 5 user authentication events +| where tld_auth_counts <= 5 + +// Sort the results by the number of user authentication events in ascending order +| sort tld_auth_counts asc +``` + +## Notes + +- Pivot into potential compromised accounts by searching for the `okta.actor.alternate_id` in `okta.target` where `event.action` is `user.lifecycle.create`. This would identify when the user account was created. The `okta.actor.alternate_id` of this event will also be the potential compromised account. + +## MITRE ATT&CK Techniques + +- [T1078.004](https://attack.mitre.org/techniques/T1078/004) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/docs/persistence_rare_tld_with_user_authentication.md b/hunting/okta/docs/docs/persistence_rare_tld_with_user_authentication.md new file mode 100644 index 00000000000..220b5d138b2 --- /dev/null +++ b/hunting/okta/docs/docs/persistence_rare_tld_with_user_authentication.md @@ -0,0 +1,48 @@ +# Rare Occurrence of Top-Level Domain (TLD) with User Authentication Events + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunting query identifies when a top-level domain (TLD) has a rare occurrence of user authentication events in Okta. Adversaries may leverage compromised Okta accounts or tokens with admin privileges to create new users that are registered with an adversary-controlled email address. + +- **UUID:** `f3bc68f4-71e9-11ef-952e-f661ea17fbcc` +- **Integration:** [okta](https://docs.elastic.co/integrations/okta) +- **Language:** `[ES|QL]` +- **Source File:** [Rare Occurrence of Top-Level Domain (TLD) with User Authentication Events](../queries/persistence_rare_tld_with_user_authentication.toml) + +## Query + +```sql +from logs-okta* +| where @timestamp > NOW() - 7 day +| where + // Filter for user authentication events + okta.actor.alternate_id is not null + and event.action LIKE "user.authentication*" + +// Extract the top-level domain (TLD) from the user's email address +| dissect okta.actor.alternate_id "%{}@%{tld}" + +// Count the number of user authentication events for each TLD +| stats tld_auth_counts = count(*) by tld + +// Filter for TLDs with less than or equal to 5 user authentication events +| where tld_auth_counts <= 5 + +// Sort the results by the number of user authentication events in ascending order +| sort tld_auth_counts asc +``` + +## Notes + +- Pivot into potential compromised accounts by searching for the `okta.actor.alternate_id` in `okta.target` where `event.action` is `user.lifecycle.create`. This would identify when the user account was created. The `okta.actor.alternate_id` of this event will also be the potential compromised account. + +## MITRE ATT&CK Techniques + +- [T1078.004](https://attack.mitre.org/techniques/T1078/004) + +## License + +- `Elastic License v2` diff --git a/hunting/okta/docs/queries/credential_access_mfa_bombing_push_notications.toml b/hunting/okta/docs/queries/credential_access_mfa_bombing_push_notications.toml new file mode 100644 index 00000000000..c680cd00393 --- /dev/null +++ b/hunting/okta/docs/queries/credential_access_mfa_bombing_push_notications.toml @@ -0,0 +1,34 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies MFA bombing attacks in Okta. Adversaries may attempt to flood a user with multiple MFA push notifications to disrupt operations or gain unauthorized access to accounts. This query identifies when a user has more than 5 MFA deny push notifications in a 10 minute window. +""" +integration = ["okta"] +uuid = "223451b0-6eca-11ef-a070-f661ea17fbcc" +name = "Rapid MFA Deny Push Notifications (MFA Bombing)" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "`okta.actor.alternate_id` is the targeted user account.", + "Pivot and search for `event.action` is `user.authentication.auth_via_mfa` to determine if the target user accepted the MFA push notification.", + "If a MFA bombing attack is suspected, both username and password are required prior to MFA push notifications. Thus the credentials are likely compromised.", +] +mitre = ['T1621'] +query = [ +""" +from logs-okta* +| where @timestamp > NOW() - 7 day + +// Truncate the timestamp to 10 minute windows +| eval target_time_window = DATE_TRUNC(10 minutes, @timestamp) + +// Filter for MFA deny push notifications +| where event.action == "user.mfa.okta_verify.deny_push" + +// Count the number of MFA deny push notifications for each user in each 10 minute window +| stats deny_push_count = count(*) by target_time_window, okta.actor.alternate_id + +// Filter for users with more than 5 MFA deny push notifications +| where deny_push_count >= 5 +""" +] \ No newline at end of file diff --git a/hunting/okta/docs/queries/credential_access_rapid_reset_password_requests_for_different_users.toml b/hunting/okta/docs/queries/credential_access_rapid_reset_password_requests_for_different_users.toml new file mode 100644 index 00000000000..f74cf1453ac --- /dev/null +++ b/hunting/okta/docs/queries/credential_access_rapid_reset_password_requests_for_different_users.toml @@ -0,0 +1,34 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies rapid reset password requests for different users in Okta. Adversaries may attempt to reset passwords for multiple users in rapid succession to gain unauthorized access to accounts or disrupt operations. This query identifies when the source user is different from the target user in reset password events and filters for users with more than 15 reset password attempts. +""" +integration = ["okta"] +uuid = "c784106e-6ae8-11ef-919d-f661ea17fbcc" +name = "Rapid Reset Password Requests for Different Users" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "`okta.actor.alternate_id` is the potentially compromised account", + "An API access token may have been compromised, where okta.actor.alternate_id reflects the owner", + "To identify a list of tokens this user created, search for the `okta.actor.alternate_id` where `event.action` is `system.api_token*` which may require a larger time window" +] +mitre = ['T1098.001'] +query = [''' +from logs-okta.system* +| where @timestamp > NOW() - 7 day + +// Filter for reset password events where the source user is different from the target user +| where event.dataset == "okta.system" and event.action == "user.account.reset_password" and source.user.full_name != user.target.full_name + +// Extract relevant fields +| keep @timestamp, okta.actor.alternate_id, okta.debug_context.debug_data.dt_hash, user.target.full_name, okta.outcome.result + +// Count the number of reset password attempts for each user +| stats + user_count = count_distinct(user.target.full_name), + reset_counts = by okta.actor.alternate_id, source.user.full_name, okta.debug_context.debug_data.dt_hash + +// Filter for more than 10 unique users and more than 15 reset password attempts by the source +| where user_count > 10 and reset_counts > 15 +'''] \ No newline at end of file diff --git a/hunting/okta/docs/queries/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.toml b/hunting/okta/docs/queries/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.toml new file mode 100644 index 00000000000..b9e1607765f --- /dev/null +++ b/hunting/okta/docs/queries/defense_evasion_failed_oauth_access_token_retrieval_via_public_client_app.toml @@ -0,0 +1,37 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies when a public client app fails to retrieve an OAuth access token using client credentials because of an uauthorized scope. Adversaries may attempt to retrieve access tokens using client credentials to bypass user authentication and access resources. This query identifies when a public client app fails to retrieve an access token using client credentials and scopes that are not implicitly granted. +""" +integration = ["okta"] +uuid = "0b936024-71d9-11ef-a9be-f661ea17fbcc" +name = "Failed OAuth Access Token Retrieval via Public Client App" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "Review `okta.debug_context.debug_data.flattened.grantType` to identify if the grant type is `client_credentials`", + "Ignore `okta.debug_context.debug_data.flattened.requestedScopes` values that indicate read-only access", + "Review `okta.actor.display_name` to identify the public client app that attempted to retrieve the access token. This may help identify the compromised client credentials.", + "Pivot for successful access token retrieval by the same public client app by searching `event.action` equal to `app.oauth2.as.token.grant.access_token` where the display name is the same." +] +mitre = ['T1550.001'] +query = [''' +from logs-okta.system* +| where @timestamp > NOW() - 7 day +| where + event.dataset == "okta.system" + + // filter on failed access token grant requests where source is a public client app + and event.action == "app.oauth2.as.token.grant" + and okta.actor.type == "PublicClientApp" + and okta.outcome.result == "FAILURE" + + // filter out known Okta and Datadog actors + and not ( + okta.actor.display_name LIKE "Okta%" + or okta.actor.display_name LIKE "Datadog%" + ) + + // filter for scopes that are not implicitly granted + and okta.outcome.reason == "no_matching_scope" +'''] \ No newline at end of file diff --git a/hunting/okta/docs/queries/defense_evasion_multiple_application_sso_authentication_repeat_source.toml b/hunting/okta/docs/queries/defense_evasion_multiple_application_sso_authentication_repeat_source.toml new file mode 100644 index 00000000000..168413e7d8b --- /dev/null +++ b/hunting/okta/docs/queries/defense_evasion_multiple_application_sso_authentication_repeat_source.toml @@ -0,0 +1,40 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies when a user authenticates to multiple applications using Single Sign-On (SSO) from the same source. Adversaries may attempt to authenticate to multiple applications using SSO to gain unauthorised access to sensitive data or resources. Adversaries also rely on refresh tokens to maintain access to applications and services. This query identifies when a source IP authenticates to more than 15 applications using SSO within a 5-minute window. +""" +integration = ["okta"] +uuid = "03bce3b0-6ded-11ef-9282-f661ea17fbcc" +name = "Multiple Application SSO Authentication from the Same Source" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "`okta.debug_context.debug_data.dt_hash` field can be used to identify the device token hash used for authentication. This can be used to pivot for additional activity from the same device.", + "`okta.debug_context.debug_data.flattened` contains additional information such as request ID, trace ID, sign-on mode and more to review for anomalies in the authentication flow.", + "`okta.request.ip_chain` can be used to understand more about the source address, which is potentially useful for profiling.", + "If `okta.security_context.is_proxy` is `true`, then an adversary may be attempting to mask their true source behind a proxy or VPN.", +] +mitre = ['T1550.001'] +query = [''' +from logs-okta* +| eval target_time_window = DATE_TRUNC(5 minutes, @timestamp) + +// truncate the timestamp to a 5-minute window +| where @timestamp > now() - 7 day + +// filter for SSO authentication events where the authentication step is 0 +// filter on request URI string '/app/' to identify applications for a user +| where + event.action == "user.authentication.sso" + and okta.authentication_context.authentication_step == 0 + and okta.debug_context.debug_data.request_uri RLIKE "(.*)/app/(.*)" + +// dissect the request URI to extract the target application +| dissect okta.debug_context.debug_data.request_uri"%{?}/app/%{target_application}/" + +// count the number of unique applications per source IP and user in a 5-minute window +| stats application_count = count_distinct(target_application), window_count = count(*) by target_time_window, source.ip, okta.actor.alternate_id + +// filter for at least 15 distinct applications authenticated from a single source IP +| where application_count > 15 +'''] \ No newline at end of file diff --git a/hunting/okta/docs/queries/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.toml b/hunting/okta/docs/queries/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.toml new file mode 100644 index 00000000000..d1f76770846 --- /dev/null +++ b/hunting/okta/docs/queries/defense_evasion_multiple_client_sources_reported_for_oauth_access_tokens_granted.toml @@ -0,0 +1,42 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies when a public client app successfully retrieves an OAuth access token using client credentials from multiple client addresses. For public client applications in Okta that leverage OAuth, client credentials can be used to retrieve access tokens without user consent. Unsecured credentials may be compromised by an adversary who may use them to request an access token on behalf of the public client app. +""" +integration = ["okta"] +uuid = "38d82c2c-71d9-11ef-a9be-f661ea17fbcc" +name = "OAuth Access Token Granted for Public Client App from Multiple Client Addresses" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "Review `okta.debug_context.debug_data.flattened.grantType` to identify if the grant type is `client_credentials`", + "Ignore `okta.debug_context.debug_data.flattened.requestedScopes` values that indicate read-only access", + "Review `okta.actor.display_name` to identify the public client app that attempted to retrieve the access token. This may help identify the compromised client credentials.", + "Filter on the public client app and aggregate by `event.action` to determine what actions were taken by the public client app after the access token was granted." +] +mitre = ['T1550.001'] +query = [''' +from logs-okta.system* +| where @timestamp > NOW() - 21 day + +// truncate the timestamp to 1 day +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + + // filter for successful OAuth access token grant requests + event.action == "app.oauth2.as.token.grant.access_token" + and event.outcome == "success" + and event.dataset == "okta.system" + + // filter for public client apps + and okta.actor.type == "PublicClientApp" + + // ignore Elastic Okta integration and DataDog actors + and not (okta.actor.display_name LIKE "Okta*" or okta.actor.display_name LIKE "Datadog*") + +// count the number of access tokens granted by the same public client app in a day +| stats token_granted_count = count(*), unique_client_ip = count_distinct(okta.client.ip) by target_time_window, okta.actor.display_name + +// filter where access tokens were granted on the same day but client addresses are different +| where unique_client_ip >= 2 and token_granted_count >= 2 +'''] \ No newline at end of file diff --git a/hunting/okta/docs/queries/defense_evasion_rare_oauth_access_token_granted_by_application.toml b/hunting/okta/docs/queries/defense_evasion_rare_oauth_access_token_granted_by_application.toml new file mode 100644 index 00000000000..f01aa37cc81 --- /dev/null +++ b/hunting/okta/docs/queries/defense_evasion_rare_oauth_access_token_granted_by_application.toml @@ -0,0 +1,41 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies a rare occurrence of a public client app successfully retrieves an OAuth access token using client credentials as the grant type within the last 14 days. Public client applications in Okta that leverage OAuth, client credentials can be used to retrieve access tokens without user consent. Unsecured credentials may be compromised by an adversary whom may use them to request an access token on behalf of the public client app. +""" +integration = ["okta"] +uuid = "11666aa0-71d9-11ef-a9be-f661ea17fbcc" +name = "Rare Occurrence of OAuth Access Token Granted to Public Client App" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "Review `okta.debug_context.debug_data.flattened.grantType` to identify if the grant type is `client_credentials`", + "Ignore `okta.debug_context.debug_data.flattened.requestedScopes` values that indicate read-only access", + "Review `okta.actor.display_name` to identify the public client app that attempted to retrieve the access token. This may help identify the compromised client credentials.", + "False-positives may exist if the public client app is new or has not been used in the last 14 days." +] +mitre = ['T1550.001'] +query = [''' +from logs-okta.system* +| where @timestamp > NOW() - 14 day +| where + + // filter for successful OAuth access token grant requests + event.action == "app.oauth2.as.token.grant.access_token" + and event.outcome == "success" + and event.dataset == "okta.system" + + // filter for public client apps + and okta.actor.type == "PublicClientApp" + + // ignore Elastic Okta integration and DataDog actors + and not okta.client.user_agent.raw_user_agent == "Okta-Integrations" + and not (okta.actor.display_name LIKE "Okta%" or okta.actor.display_name LIKE "Datadog%") + +// count the number of access tokens granted by the same public client app +| stats token_granted_count = count(*) by okta.actor.display_name + +// filter where the public client app has only been granted an access token once in the last 14 days +| where token_granted_count == 1 +''' +] \ No newline at end of file diff --git a/hunting/okta/docs/queries/initial_access_higher_than_average_failed_authentication.toml b/hunting/okta/docs/queries/initial_access_higher_than_average_failed_authentication.toml new file mode 100644 index 00000000000..10c16c1aeca --- /dev/null +++ b/hunting/okta/docs/queries/initial_access_higher_than_average_failed_authentication.toml @@ -0,0 +1,42 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies when the average number of failed daily authentication attempts is higher than normal in Okta. Adversaries may attempt to brute force user credentials to gain unauthorized access to accounts. This query calculates the average number of daily failed authentication attempts for each user and identifies when the average is higher than normal. +""" +integration = ["okta"] +uuid = "c8a35a26-71f1-11ef-9c4e-f661ea17fbcc" +name = "Identify High Average of Failed Daily Authentication Attempts" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "Pivot to users by only keeping the first stats statement where `okta.actor.alternate_id` is the targeted accounts.", + "Pivot for successful logins from the same source IP by searching for `event.action` equal to `user.session.start` or `user.authentication.verify` where the outcome is `SUCCESS`.", + "User agents can be used to identify anomalous behavior, such as a user agent that is not associated with a known application or user.", + "Another `WHERE` count can be added to the query if activity has been baseline to filter out known behavior." +] +mitre = ['T1078.004'] +query = [''' +from logs-okta* +| where @timestamp > NOW() - 7 day + +// truncate the timestamp to daily intervals +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + + // filter for invalid credential authentication events + event.action == "user.session.start" + and okta.outcome.result == "FAILURE" + and okta.outcome.reason == "INVALID_CREDENTIALS" + and okta.actor.type == "User" + +| stats + // count the number of daily failed logins for each day and user + failed_daily_logins = count(*) by target_time_window, okta.actor.alternate_id + +| stats + // calculate the average number of daily failed logins for each day + avg_daily_logins = avg(failed_daily_logins) by target_time_window + +// sort the results by the average number of daily failed logins in descending order +| sort avg_daily_logins desc +'''] \ No newline at end of file diff --git a/hunting/okta/docs/queries/initial_access_impossible_travel_sign_on.toml b/hunting/okta/docs/queries/initial_access_impossible_travel_sign_on.toml new file mode 100644 index 00000000000..dee9a1dca7a --- /dev/null +++ b/hunting/okta/docs/queries/initial_access_impossible_travel_sign_on.toml @@ -0,0 +1,34 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies when a user successfully signs on from more than one country in a 15 minute interval. Adversaries may compromise authentication credentials for users or clients and attempt to authenticate from a separate country that the user has not previously authenticated from. +""" +integration = ["okta"] +uuid = "31585786-71f4-11ef-9e99-f661ea17fbcc" +name = "Successful Impossible Travel Sign-On Events" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "`okta.actor.alternate_id` would be target of the threat adversary", + "Pivoting into a potential compromise requires an additional search for `okta.outcome.result` being `SUCCESS` for any `user.authentication*` value for `okta.event_type`", + "Pivot to any additional Okta logs after authentication to determine if activity is still being reported by separate countries." +] +mitre = ['T1078.004'] +query = [''' +from logs-okta.system* +| where @timestamp > NOW() - 7 day +| where event.dataset == "okta.system" + + // filter on successful sign-on events only + and okta.event_type == "policy.evaluate_sign_on" + and okta.outcome.result in ("ALLOW", "SUCCESS") + +// Truncate the timestamp to 1 hour intervals +| eval time_window = DATE_TRUNC(1 hours, @timestamp) + +// Count the number of successful sign-on events for each user every 15 minutes +| stats country_count = count_distinct(client.geo.country_name) by okta.actor.alternate_id, time_window + +// Filter for users who sign on from more than one country in a 15 minute interval +| where country_count >= 2 +'''] \ No newline at end of file diff --git a/hunting/okta/docs/queries/initial_access_password_spraying_from_repeat_source.toml b/hunting/okta/docs/queries/initial_access_password_spraying_from_repeat_source.toml new file mode 100644 index 00000000000..2d1d636410a --- /dev/null +++ b/hunting/okta/docs/queries/initial_access_password_spraying_from_repeat_source.toml @@ -0,0 +1,39 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies password spraying attacks in Okta where the same source IP attempts to authenticate to multiple accounts with invalid credentials. Adversaries may attempt to use a single source IP to avoid detection and bypass account lockout policies. +""" +integration = ["okta"] +uuid = "1c2d2b08-71ee-11ef-952e-f661ea17fbcc" +name = "Password Spraying from Repeat Source" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "`okta.actor.alternate_id` are the targeted accounts.", + "Pivot for successful logins from the same source IP by searching for `event.action` equal to `user.session.start` or `user.authentication.verify` where the outcome is `SUCCESS`.", + "User agents can be used to identify anomalous behavior, such as a user agent that is not associated with a known application or user.", + "The `target_count` can be adjusted depending on the organization's account lockout policy or baselined behavior." +] +mitre = ['T1078.004'] +query = [''' +from logs-okta* +| where @timestamp > NOW() - 7 day + +// truncate the timestamp to daily intervals +| eval target_time_window = DATE_TRUNC(1 days, @timestamp) +| where + +// filter for invalid credential events + event.action == "user.session.start" + and okta.outcome.result == "FAILURE" + and okta.outcome.reason == "INVALID_CREDENTIALS" +| stats + // count the distinct number of targeted accounts + target_count = count_distinct(okta.actor.alternate_id), + + // count the number of invalid credential events for each source IP + source_count = count(*) by target_time_window, okta.client.ip + +// filter for source IPs with more than 5 invalid credential events +| where target_count >= 10 +'''] \ No newline at end of file diff --git a/hunting/okta/docs/queries/persistence_multi_factor_push_notification_bombing.toml b/hunting/okta/docs/queries/persistence_multi_factor_push_notification_bombing.toml new file mode 100644 index 00000000000..d67152cdd71 --- /dev/null +++ b/hunting/okta/docs/queries/persistence_multi_factor_push_notification_bombing.toml @@ -0,0 +1,32 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies when a user denies multiple push notifications for multi-factor authentication (MFA) in rapid succession. Adversaries may attempt to deny push notifications to flood the target user's device with notifications, causing the user to ignore legitimate notifications or potentially disable MFA. This query identifies when a user denies more than 5 push notifications in a single hour. +""" +integration = ["okta"] +uuid = "7c51fe3e-6ae9-11ef-919d-f661ea17fbcc" +name = "Multi-Factor Authentication (MFA) Push Notification Bombing" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "`okta.actor.alternate_id` would be target of the threat adversary", + "Pivoting into a potential compromise requires an additional search for `okta.outcome.result` being `SUCCESS` for any `user.authentication*` value for `okta.event_type`", + "For a smaller window (rapid denies), reduce from 1 hour to 30 minutes or lower" +] +mitre = ['T1556.006'] +query = [''' +from logs-okta.system* +| where @timestamp > NOW() - 7 day + +// Filter for deny push notifications for multi-factor authentication +| where event.dataset == "okta.system" and event.action == "user.mfa.okta_verify.deny_push" + +// Truncate the timestamp to hourly intervals +| eval hourly_count = date_trunc(1 hour, event.ingested) + +// Count the number of deny push notifications for each user every hour +| stats hourly_denies = count(*) by okta.actor.alternate_id, hourly_count + +// Filter for users who deny more than 5 push notifications in a single hour +| where hourly_denies > 5 +'''] \ No newline at end of file diff --git a/hunting/okta/docs/queries/persistence_rare_domain_with_user_authentication.toml b/hunting/okta/docs/queries/persistence_rare_domain_with_user_authentication.toml new file mode 100644 index 00000000000..44d595f3575 --- /dev/null +++ b/hunting/okta/docs/queries/persistence_rare_domain_with_user_authentication.toml @@ -0,0 +1,34 @@ +[hunt] +author = "Elastic" +description = """ +This hunting query identifies rare occurrences of user authentication events for an Okta user whose registered user account email address has a domain that is not commonly seen in the organization. Adversaries may use compromised credentials or tokens to create a new user account with a domain that is not commonly seen in the organization because they do not have access to a valid email address within that domain. +""" +integration = ["okta"] +uuid = "f3bc68f4-71e9-11ef-952e-f661ea17fbcc" +name = "Rare Occurrence of Domain with User Authentication Events" +language = ["ES|QL"] +license = "Elastic License v2" +notes = [ + "Pivot into potential compromised accounts by searching for the `okta.actor.alternate_id` in `okta.target` where `event.action` is `user.lifecycle.create`. This would identify when the user account was created. The `okta.actor.alternate_id` of this event will also be the potential compromised account.", +] +mitre = ['T1078.004'] +query = [''' +from logs-okta* +| where @timestamp > NOW() - 7 day +| where + // Filter for user authentication events + okta.actor.alternate_id is not null + and event.action LIKE "user.authentication*" + +// Extract the top-level domain (TLD) from the user's email address +| dissect okta.actor.alternate_id "%{}@%{tld}" + +// Count the number of user authentication events for each TLD +| stats tld_auth_counts = count(*) by tld + +// Filter for TLDs with less than or equal to 5 user authentication events +| where tld_auth_counts <= 5 + +// Sort the results by the number of user authentication events in ascending order +| sort tld_auth_counts asc +'''] \ No newline at end of file