From 9e9214f6ebec94d1f46bc8ce8182fb981b737698 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 08:55:23 -0400 Subject: [PATCH 01/94] adjusted Potential Widespread Malware Infection Across Multiple Hosts --- .../execution_potential_widespread_malware_infection.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/cross-platform/execution_potential_widespread_malware_infection.toml b/rules/cross-platform/execution_potential_widespread_malware_infection.toml index f7ca76e64fa..e02e3f18476 100644 --- a/rules/cross-platform/execution_potential_widespread_malware_infection.toml +++ b/rules/cross-platform/execution_potential_widespread_malware_infection.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/05/08" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -67,8 +67,8 @@ query = ''' from logs-endpoint.alerts-* | where event.code in ("malicious_file", "memory_signature", "shellcode_thread") and rule.name is not null | keep host.id, rule.name, event.code -| stats hosts = count_distinct(host.id) by rule.name, event.code -| where hosts >= 3 +| stats esql.host.id.count_distinct = count_distinct(host.id) by rule.name, event.code +| where esql.host.id.count_distinct >= 3 ''' From 7b5855a19fa14fc43c30616b94290c6a6b5c9e9b Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:07:19 -0400 Subject: [PATCH 02/94] adjusted Microsoft Azure or Mail Sign-in from a Suspicious Source --- ..._access_azure_o365_with_network_alert.toml | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml index 329c2fd6ea7..1e29d51fff6 100644 --- a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml +++ b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/29" integration = ["azure", "o365"] maturity = "production" -updated_date = "2025/07/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -78,21 +78,47 @@ type = "esql" query = ''' FROM logs-*, .alerts-security.* -// query runs every 1 hour looking for activities occured during last 8 hours to match on disparate events +// query runs every 1 hour looking for activities occurred during last 8 hours to match on disparate events | where @timestamp > NOW() - 8 hours // filter for Azure or M365 sign-in and External Alerts with source.ip not null -| where TO_IP(source.ip) is not null and (event.dataset in ("o365.audit", "azure.signinlogs") or kibana.alert.rule.name == "External Alerts") and -// exclude private IP ranges - not CIDR_MATCH(TO_IP(source.ip), "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32", "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24","198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1","FE80::/10", "FF00::/8") +| where TO_IP(source.ip) is not null + and (event.dataset in ("o365.audit", "azure.signinlogs") or kibana.alert.rule.name == "External Alerts") + and not CIDR_MATCH( + TO_IP(source.ip), + "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.0.0/29", + "192.0.0.8/32", "192.0.0.9/32", "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", + "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", "224.0.0.0/4", + "100.64.0.0/10", "192.175.48.0/24", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", + "240.0.0.0/4", "::1", "FE80::/10", "FF00::/8" + ) + +// capture relevant raw fields | keep source.ip, event.action, event.outcome, event.dataset, kibana.alert.rule.name, event.category -// split alerts to 3 buckets - M365 mail access, azure sign-in and network related external alerts like NGFW and IDS -| eval mail_access_src_ip = case(event.dataset == "o365.audit" and event.action == "MailItemsAccessed" and event.outcome == "success", TO_IP(source.ip), null), - azure_src_ip = case(event.dataset == "azure.signinlogs" and event.outcome == "success", TO_IP(source.ip), null), - network_alert_src_ip = case(kibana.alert.rule.name == "External Alerts" and not event.dataset in ("o365.audit", "azure.signinlogs"), TO_IP(source.ip), null) -// aggregated alerts count by bucket and by source.ip -| stats total_alerts = count(*), is_mail_access = COUNT_DISTINCT(mail_access_src_ip), is_azure = COUNT_DISTINCT(azure_src_ip), unique_dataset = COUNT_DISTINCT(event.dataset),is_network_alert = COUNT_DISTINCT(network_alert_src_ip), datasets = VALUES(event.dataset), rules = VALUES(kibana.alert.rule.name), cat = VALUES(event.category) by source_ip = TO_IP(source.ip) -// filter for cases where there is a successful sign-in to azure or m365 mail and the source.ip is reported by a network external alert. -| where is_network_alert > 0 and unique_dataset >= 2 and (is_mail_access > 0 or is_azure > 0) and total_alerts <= 100 + +// classify each source IP based on alert type +| eval + esql.source.ip.mail_access.case = case(event.dataset == "o365.audit" and event.action == "MailItemsAccessed" and event.outcome == "success", TO_IP(source.ip), null), + esql.source.ip.azure_signin.case = case(event.dataset == "azure.signinlogs" and event.outcome == "success", TO_IP(source.ip), null), + esql.source.ip.network_alert.case = case(kibana.alert.rule.name == "External Alerts" and not event.dataset in ("o365.audit", "azure.signinlogs"), TO_IP(source.ip), null) + +// aggregate by source IP +| stats + esql.source.ip.alerts.count = count(*), + esql.source.ip.mail_access.case.count_distinct = COUNT_DISTINCT(esql.source.ip.mail_access.case), + esql.source.ip.azure_signin.case.count_distinct = COUNT_DISTINCT(esql.source.ip.azure_signin.case), + esql.source.ip.network_alert.case.count_distinct = COUNT_DISTINCT(esql.source.ip.network_alert.case), + esql.event.dataset.count_distinct = COUNT_DISTINCT(event.dataset), + esql.event.dataset.values = VALUES(event.dataset), + esql.kibana.alert.rule.name.values = VALUES(kibana.alert.rule.name), + esql.event.category.values = VALUES(event.category) + by esql.source.ip = TO_IP(source.ip) + +// correlation condition +| where + esql.source.ip.network_alert.case.count_distinct > 0 + and esql.event.dataset.count_distinct >= 2 + and (esql.source.ip.mail_access.case.count_distinct > 0 or esql.source.ip.azure_signin.case.count_distinct > 0) + and esql.source.ip.alerts.count <= 100 ''' From 7c66bb56dca97ae53f95ccf2d0fddfbe33b976a1 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:10:09 -0400 Subject: [PATCH 03/94] adjusted AWS EC2 Multi-Region DescribeInstances API Calls --- ...y_ec2_multi_region_describe_instances.toml | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml b/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml index b44db7e2dc0..15f0b19168c 100644 --- a/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml +++ b/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml @@ -2,7 +2,7 @@ creation_date = "2024/08/26" integration = ["aws"] maturity = "production" -updated_date = "2025/01/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -86,25 +86,30 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* +FROM logs-aws.cloudtrail-* // filter for DescribeInstances API calls -| where event.dataset == "aws.cloudtrail" and event.provider == "ec2.amazonaws.com" and event.action == "DescribeInstances" +| where event.dataset == "aws.cloudtrail" + and event.provider == "ec2.amazonaws.com" + and event.action == "DescribeInstances" // truncate the timestamp to a 30-second window -| eval target_time_window = DATE_TRUNC(30 seconds, @timestamp) +| eval esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) -// keep only the relevant fields -| keep target_time_window, aws.cloudtrail.user_identity.arn, cloud.region +// keep only the relevant raw fields +| keep esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn, cloud.region // count the number of unique regions and total API calls within the 30-second window -| stats region_count = count_distinct(cloud.region), window_count = count(*) by target_time_window, aws.cloudtrail.user_identity.arn +| stats + esql.cloud.region.count_distinct = COUNT_DISTINCT(cloud.region), + esql.event.count = COUNT(*) + by esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn // filter for resources making DescribeInstances API calls in more than 10 regions within the 30-second window -| where region_count >= 10 and window_count >= 10 +| where esql.cloud.region.count_distinct >= 10 and esql.event.count >= 10 -// sort the results by time windows in descending order -| sort target_time_window desc +// sort the results by time window in descending order +| sort esql.time_window.date_trunc desc ''' [rule.investigation_fields] From 9ae85680ab9fda070dbcac174314cea24556140b Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:17:33 -0400 Subject: [PATCH 04/94] adjusted AWS Discovery API Calls via CLI from a Single Resource --- ..._multiple_discovery_api_calls_via_cli.toml | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml index f4588bc7f12..b6aa9efd6e3 100644 --- a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml +++ b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml @@ -2,7 +2,7 @@ creation_date = "2024/11/04" integration = ["aws"] maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -80,14 +80,14 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail* +FROM logs-aws.cloudtrail* // create time window buckets of 10 seconds -| eval time_window = date_trunc(10 seconds, @timestamp) +| eval esql.time_window.date_trunc = DATE_TRUNC(10 seconds, @timestamp) | where event.dataset == "aws.cloudtrail" - // filter on CloudTrail audit logs for IAM, EC2, and S3 events only + // filter on CloudTrail audit logs for IAM, EC2, S3, etc. and event.provider in ( "iam.amazonaws.com", "ec2.amazonaws.com", @@ -97,8 +97,7 @@ from logs-aws.cloudtrail* "dynamodb.amazonaws.com", "kms.amazonaws.com", "cloudfront.amazonaws.com", - "elasticloadbalancing.amazonaws.com", - "cloudfront.amazonaws.com" + "elasticloadbalancing.amazonaws.com" ) // ignore AWS service actions @@ -112,24 +111,29 @@ from logs-aws.cloudtrail* // filter for Describe, Get, List, and Generate API calls | where true in ( - starts_with(event.action, "Describe"), - starts_with(event.action, "Get"), - starts_with(event.action, "List"), - starts_with(event.action, "Generate") + STARTS_WITH(event.action, "Describe"), + STARTS_WITH(event.action, "Get"), + STARTS_WITH(event.action, "List"), + STARTS_WITH(event.action, "Generate") ) + // extract owner, identity type, and actor from the ARN -| dissect aws.cloudtrail.user_identity.arn "%{}::%{owner}:%{identity_type}/%{actor}" -| where starts_with(actor, "AWSServiceRoleForConfig") != true -| keep @timestamp, time_window, event.action, aws.cloudtrail.user_identity.arn +| dissect aws.cloudtrail.user_identity.arn "%{}::%{esql.owner}:%{esql.identity.type}/%{esql.user.roles}" +| where STARTS_WITH(esql.user.roles, "AWSServiceRoleForConfig") != true + +// keep relevant fields (preserving ECS fields and computed time window) +| keep @timestamp, esql.time_window.date_trunc, event.action, aws.cloudtrail.user_identity.arn + +// count the number of unique API calls per time window and actor | stats - // count the number of unique API calls per time window and actor - unique_api_count = count_distinct(event.action) by time_window, aws.cloudtrail.user_identity.arn + esql.event.action.count_distinct = COUNT_DISTINCT(event.action) + by esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn -// filter for more than 5 unique API calls per time window -| where unique_api_count > 5 +// filter for more than 5 unique API calls per 10s window +| where esql.event.action.count_distinct > 5 // sort the results by the number of unique API calls in descending order -| sort unique_api_count desc +| sort esql.event.action.count_distinct desc ''' From 868ce2ea7d1d0e2085a4671c80ad6cd8b8072b05 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:20:10 -0400 Subject: [PATCH 05/94] adjusted AWS Service Quotas Multi-Region Requests --- ...s_multi_region_service_quota_requests.toml | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml index 3b171b77e62..779e70ba342 100644 --- a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml +++ b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/08/26" maturity = "production" -updated_date = "2025/01/15" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -35,31 +35,44 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* +FROM logs-aws.cloudtrail-* // filter for GetServiceQuota API calls -| where event.dataset == "aws.cloudtrail" and event.provider == "servicequotas.amazonaws.com" and event.action == "GetServiceQuota" +| where + event.dataset == "aws.cloudtrail" + and event.provider == "servicequotas.amazonaws.com" + and event.action == "GetServiceQuota" // truncate the timestamp to a 30-second window -| eval target_time_window = DATE_TRUNC(30 seconds, @timestamp) +| eval esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) -// pre-process the request parameters to extract the service code and quota code -| dissect aws.cloudtrail.request_parameters "{%{?service_code_key}=%{service_code}, %{?quota_code_key}=%{quota_code}}" +// dissect request parameters to extract service and quota code +| dissect aws.cloudtrail.request_parameters "{%{?esql.service_code.key}=%{esql.service_code}, %{?quota_code_key}=%{esql.quota_code}}" // filter for EC2 service quota L-1216C47A (vCPU on-demand instances) -| where service_code == "ec2" and quota_code == "L-1216C47A" +| where esql.service_code == "ec2" and esql.quota_code == "L-1216C47A" // keep only the relevant fields -| keep target_time_window, aws.cloudtrail.user_identity.arn, cloud.region, service_code, quota_code - -// count the number of unique regions and total API calls within the 30-second window -| stats region_count = count_distinct(cloud.region), window_count = count(*) by target_time_window, aws.cloudtrail.user_identity.arn - -// filter for resources making DescribeInstances API calls in more than 10 regions within the 30-second window -| where region_count >= 10 and window_count >= 10 - -// sort the results by time windows in descending order -| sort target_time_window desc +| keep + esql.time_window.date_trunc, + aws.cloudtrail.user_identity.arn, + cloud.region, + esql.service_code, + esql.quota_code + +// count the number of unique regions and total API calls within the time window +| stats + esql.cloud.region.count_distinct = COUNT_DISTINCT(cloud.region), + esql.event.count = COUNT(*) + by esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn + +// filter for API calls in more than 10 regions within the 30-second window +| where + esql.cloud.region.count_distinct >= 10 + and esql.event.count >= 10 + +// sort by time window descending +| sort esql.time_window.date_trunc desc ''' note = """## Triage and analysis From c321f3d4cf1e540c42fdf4f7f561c8facde8d02d Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:23:21 -0400 Subject: [PATCH 06/94] adjusted AWS EC2 EBS Snapshot Shared or Made Public --- ..._snapshot_shared_with_another_account.toml | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml index b90644c5578..d0ba3359d4a 100644 --- a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml +++ b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml @@ -2,7 +2,7 @@ creation_date = "2024/04/16" integration = ["aws"] maturity = "production" -updated_date = "2025/06/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -80,11 +80,32 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* metadata _id, _version, _index -| where event.provider == "ec2.amazonaws.com" and event.action == "ModifySnapshotAttribute" and event.outcome == "success" -| dissect aws.cloudtrail.request_parameters "{%{?snapshotId}=%{snapshotId},%{?attributeType}=%{attributeType},%{?createVolumePermission}={%{operationType}={%{?items}=[{%{?userId}=%{userId}}]}}}" -| where operationType == "add" and cloud.account.id != userId -| keep @timestamp, aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, snapshotId, attributeType, operationType, userId, source.address +FROM logs-aws.cloudtrail-* METADATA _id, _version, _index +| where + event.provider == "ec2.amazonaws.com" + and event.action == "ModifySnapshotAttribute" + and event.outcome == "success" + +// Extract snapshotId, attribute type, operation type, and userId +| dissect aws.cloudtrail.request_parameters + "{%{?snapshotId}=%{esql.snapshot_id},%{?attributeType}=%{esql.attribute_type},%{?createVolumePermission}={%{esql.operation_type}={%{?items}=[{%{?userId}=%{esql.user.id}}]}}}" + +// Check for snapshot permission added for another AWS account +| where + esql.operation_type == "add" + and cloud.account.id != esql.user.id + +// Keep ECS and derived fields +| keep + @timestamp, + aws.cloudtrail.user_identity.arn, + cloud.account.id, + event.action, + esql.snapshot_id, + esql.attribute_type, + esql.operation_type, + esql.user.id, + source.address ''' From 24c5b12c9d18a66e65f90dbaec7ee9658e221d3d Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:29:32 -0400 Subject: [PATCH 07/94] adjusted AWS S3 Bucket Enumeration or Brute Force --- ..._s3_bucket_enumeration_or_brute_force.toml | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml b/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml index 45a051a8d72..578323a5996 100644 --- a/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml +++ b/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/05/01" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -85,14 +85,30 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail* -| where event.provider == "s3.amazonaws.com" and aws.cloudtrail.error_code == "AccessDenied" -// keep only relevant fields -| keep tls.client.server_name, source.address, cloud.account.id -| stats failed_requests = count(*) by tls.client.server_name, source.address, cloud.account.id - // can modify the failed request count or tweak time window to fit environment - // can add `not cloud.account.id in (KNOWN)` or specify in exceptions -| where failed_requests > 40 +FROM logs-aws.cloudtrail* + +| where + event.provider == "s3.amazonaws.com" + and aws.cloudtrail.error_code == "AccessDenied" + and tls.client.server_name IS NOT NULL + and cloud.account.id IS NOT NULL + +// Keep only relevant ECS fields +| keep + tls.client.server_name, + source.address, + cloud.account.id + +// Count access denied requests per server_name, source, and account +| stats + esql.event.count = COUNT(*) + by + tls.client.server_name, + source.address, + cloud.account.id + +// Threshold: more than 40 denied requests +| where esql.event.count > 40 ''' From a86052fa289f1680ceb2ec77c6228fb40aaae9e8 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:32:26 -0400 Subject: [PATCH 08/94] adjusted AWS EC2 EBS Snapshot Access Removed --- ...mpact_ec2_ebs_snapshot_access_removed.toml | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml index dbffdf0db3e..5b3323b1eac 100644 --- a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml +++ b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml @@ -2,7 +2,7 @@ creation_date = "2024/06/02" integration = ["aws"] maturity = "production" -updated_date = "2024/06/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -34,7 +34,7 @@ Restricting snapshot access may help adversaries cover their tracks by making it - **Analyze the Source of the Request**: Investigate the `source.ip` and `source.geo` fields to determine the geographical origin of the request. An external or unexpected location might indicate compromised credentials or unauthorized access. - **Contextualize with Timestamp**: Use the `@timestamp` field to check when the change occurred. Modifications during non-business hours or outside regular maintenance windows might require further scrutiny. - **Correlate with Other Activities**: Search for related CloudTrail events before and after this change to see if the same actor or IP address engaged in other potentially suspicious activities. In particular, use the `snapshotId` to see if this snapshot was shared with an unauthorized account. -- **Review UserID**: Check the `userId` field to identify which user's permissions were removed. Verify if this account should be authorized to access the data or if the access removal is expected. +- **Review UserID**: Check the `userId` field to identify which user's permissions were removed. Verify if this account should be authorized to access the data or if the access removal is expected. ### False Positive Analysis: @@ -75,11 +75,32 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* metadata _id, _version, _index -| where event.provider == "ec2.amazonaws.com" and event.action == "ModifySnapshotAttribute" and event.outcome == "success" -| dissect aws.cloudtrail.request_parameters "{%{?snapshotId}=%{snapshotId},%{?attributeType}=%{attributeType},%{?createVolumePermission}={%{operationType}={%{?items}=[{%{?userId}=%{userId}}]}}}" -| where operationType == "remove" -| keep @timestamp, aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, snapshotId, attributeType, operationType, userId, source.address +FROM logs-aws.cloudtrail-* METADATA _id, _version, _index + +// Filter for successful snapshot modifications +| where + event.provider == "ec2.amazonaws.com" + and event.action == "ModifySnapshotAttribute" + and event.outcome == "success" + +// Dissect parameters to extract key fields +| dissect aws.cloudtrail.request_parameters + "{%{?snapshotId}=%{esql.snapshot.id},%{?attributeType}=%{esql.attribute_type},%{?createVolumePermission}={%{esql.operation_type}={%{?items}=[{%{?userId}=%{esql.user.id}}]}}}" + +// Match on snapshot permission **removal** +| where esql.operation_type == "remove" + +// Keep ECS and derived fields +| keep + @timestamp, + aws.cloudtrail.user_identity.arn, + cloud.account.id, + event.action, + esql.snapshot.id, + esql.attribute_type, + esql.operation_type, + esql.user.id, + source.address ''' From 32e86642490ddba61ff381bf5788a777e3c23a41 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:40:12 -0400 Subject: [PATCH 09/94] adjusted Potential AWS S3 Bucket Ransomware Note Uploaded --- ...object_uploaded_with_ransom_extension.toml | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml index c1ceaf67808..9cf1a6caee7 100644 --- a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml +++ b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml @@ -2,7 +2,7 @@ creation_date = "2024/04/17" integration = ["aws"] maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -81,29 +81,39 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* +FROM logs-aws.cloudtrail-* // any successful uploads via S3 API requests -| where event.dataset == "aws.cloudtrail" - and event.provider == "s3.amazonaws.com" - and event.action == "PutObject" - and event.outcome == "success" - -// abstract object name from API request parameters -| dissect aws.cloudtrail.request_parameters "%{?ignore_values}key=%{object_name}}" - -// regex on common ransomware note extensions -| where object_name rlike "(.*)(ransom|lock|crypt|enc|readme|how_to_decrypt|decrypt_instructions|recovery|datarescue)(.*)" - and not object_name rlike "(.*)(AWSLogs|CloudTrail|access-logs)(.*)" - -// keep relevant fields -| keep tls.client.server_name, aws.cloudtrail.user_identity.arn, object_name - -// aggregate by S3 bucket, resource and object name -| stats note_upload_count = count(*) by tls.client.server_name, aws.cloudtrail.user_identity.arn, object_name - -// filter for single occurrence to eliminate common upload operations -| where note_upload_count == 1 +| where + event.dataset == "aws.cloudtrail" + and event.provider == "s3.amazonaws.com" + and event.action == "PutObject" + and event.outcome == "success" + +// extract object key from API request parameters +| dissect aws.cloudtrail.request_parameters "%{?ignore_values}key=%{esql.object.key}}" + +// regex match against common ransomware naming patterns +| where + esql.object.key rlike "(.*)(ransom|lock|crypt|enc|readme|how_to_decrypt|decrypt_instructions|recovery|datarescue)(.*)" + and not esql.object.key rlike "(.*)(AWSLogs|CloudTrail|access-logs)(.*)" + +// keep relevant ECS and derived fields +| keep + tls.client.server_name, + aws.cloudtrail.user_identity.arn, + esql.object.key + +// aggregate by server name, actor, and object key +| stats + esql.event.count = COUNT(*) + by + tls.client.server_name, + aws.cloudtrail.user_identity.arn, + esql.object.key + +// filter for rare single uploads (likely test/detonation) +| where esql.event.count == 1 ''' From 019e153416cdb3d0973dc792cfc412d2aa0a0119 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:44:41 -0400 Subject: [PATCH 10/94] adjusted AWS S3 Object Encryption Using External KMS Key --- ...3_object_encryption_with_external_key.toml | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml index 58054fcd660..20900d327bc 100644 --- a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml +++ b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml @@ -2,7 +2,7 @@ creation_date = "2024/07/02" integration = ["aws"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -83,23 +83,32 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* metadata _id, _version, _index - -// any successful copy event -| where event.dataset == "aws.cloudtrail" - and event.provider == "s3.amazonaws.com" - and event.action == "CopyObject" - and event.outcome == "success" - -// abstract key account id, key id, encrypted object bucket name and object name -| dissect aws.cloudtrail.request_parameters "{%{?bucketName}=%{target.bucketName},%{?x-amz-server-side-encryption-aws-kms-key-id}=%{?arn}:%{?aws}:%{?kms}:%{?region}:%{key.account.id}:%{?key}/%{keyId},%{?Host}=%{?tls.client.server_name},%{?x-amz-server-side-encryption}=%{?server-side-encryption},%{?x-amz-copy-source}=%{?bucket.objectName},%{?key}=%{target.objectName}}" - -// filter for s3 objects whose account id is different from the encryption key's account id -// add exceptions based on key.account.id or keyId for known external accounts or encryption keys -| where cloud.account.id != key.account.id - -// keep relevant fields -| keep @timestamp, aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, target.bucketName, key.account.id, keyId, target.objectName +FROM logs-aws.cloudtrail-* METADATA _id, _version, _index + +// any successful S3 copy event +| where + event.dataset == "aws.cloudtrail" + and event.provider == "s3.amazonaws.com" + and event.action == "CopyObject" + and event.outcome == "success" + +// dissect request parameters to extract KMS key info and target object info +| dissect aws.cloudtrail.request_parameters + "{%{?bucketName}=%{esql.target.bucket.name},%{?x-amz-server-side-encryption-aws-kms-key-id}=%{?arn}:%{?aws}:%{?kms}:%{?region}:%{esql.kms.key.account.id}:%{?key}/%{esql.kms.key.id},%{?Host}=%{?tls.client.server.name},%{?x-amz-server-side-encryption}=%{?server_side_encryption},%{?x-amz-copy-source}=%{?bucket.object.name},%{?key}=%{esql.target.object.key}}" + +// detect cross-account key usage +| where cloud.account.id != esql.kms.key.account.id + +// keep ECS and dissected fields +| keep + @timestamp, + aws.cloudtrail.user_identity.arn, + cloud.account.id, + event.action, + esql.target.bucket.name, + esql.kms.key.account.id, + esql.kms.key.id, + esql.target.object.key ''' From 2244489ef5470bc3b15d47676b8d8a95b5610275 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:47:18 -0400 Subject: [PATCH 11/94] adjusted AWS S3 Static Site JavaScript File Uploaded --- ...mpact_s3_static_site_js_file_uploaded.toml | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml index 686b020d583..058a607bc72 100644 --- a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml +++ b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/15" integration = ["aws"] maturity = "production" -updated_date = "2025/04/15" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -70,42 +70,45 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail* metadata _id, _version, _index -| where +FROM logs-aws.cloudtrail* METADATA _id, _version, _index - // filter on CloudTrail logs for S3 PutObject actions +| where + // S3 object read/write activity event.dataset == "aws.cloudtrail" and event.provider == "s3.amazonaws.com" - and event.action in ("GetObject","PutObject") + and event.action in ("GetObject", "PutObject") - // filter for IAM users, not federated identities + // IAM users or assumed roles only and aws.cloudtrail.user_identity.type in ("IAMUser", "AssumedRole") - // filter for S3 static site bucket paths from webpack or similar + // Requests for static site bundles and aws.cloudtrail.request_parameters LIKE "*static/js/*.js*" - // exclude common IaC tools and automation scripts + // Exclude IaC and automation tools and not ( user_agent.original LIKE "*Terraform*" or user_agent.original LIKE "*Ansible*" or user_agent.original LIKE "*Pulumni*" ) -// extract bucket and object details from request parameters -| dissect aws.cloudtrail.request_parameters "%{{?bucket.name.key}=%{bucket.name}, %{?host.key}=%{bucket.host}, %{?bucket.object.location.key}=%{bucket.object.location}}" +// Extract fields from request parameters +| dissect aws.cloudtrail.request_parameters + "%{{?bucket.name.key}=%{esql.bucket.name}, %{?host.key}=%{esql.bucket.host}, %{?bucket.object.location.key}=%{esql.object.location}}" + +// Extract file name portion from full object path +| dissect esql.object.location "%{}static/js/%{esql.object.key}" -// filter for specific bucket and object structure -| dissect bucket.object.location "%{}static/js/%{bucket.object}" +// Match on JavaScript files +| where ENDS_WITH(esql.object.key, ".js") -// filter for JavaScript files -| where ENDS_WITH(bucket.object, ".js") +// Retain relevant ECS and dissected fields | keep aws.cloudtrail.user_identity.arn, aws.cloudtrail.user_identity.access_key_id, aws.cloudtrail.user_identity.type, aws.cloudtrail.request_parameters, - bucket.name, - bucket.object, + esql.bucket.name, + esql.object.key, user_agent.original, source.ip, event.action, From e1753105663f3ffe173472002cf3d0b7f38af334 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:51:13 -0400 Subject: [PATCH 12/94] adjusted AWS Access Token Used from Multiple Addresses --- ...on_token_used_from_multiple_addresses.toml | 144 ++++++++++-------- 1 file changed, 80 insertions(+), 64 deletions(-) diff --git a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml index 124a39fc313..db22647da88 100644 --- a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml +++ b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/11" integration = ["aws"] maturity = "production" -updated_date = "2025/07/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -33,12 +33,12 @@ note = """## Triage and Analysis ### Investigating AWS Access Token Used from Multiple Addresses -Access tokens are bound to a single user. Usage from multiple IP addresses may indicate the token was stolen and used elsewhere. By correlating this with additional detection criteria like multiple user agents, different cities, and different networks, we can improve the fidelity of the rule and help to eliminate false positives associated with expected behavior, like dual-stack IPV4/IPV6 usage. +Access tokens are bound to a single user. Usage from multiple IP addresses may indicate the token was stolen and used elsewhere. By correlating this with additional detection criteria like multiple user agents, different cities, and different networks, we can improve the fidelity of the rule and help to eliminate false positives associated with expected behavior, like dual-stack IPV4/IPV6 usage. #### Possible Investigation Steps - **Identify the IAM User**: Examine the `aws.cloudtrail.user_identity.arn` stored in `user_id` and correlate with the `source.ips` stored in `ip_list` and `unique_ips` count to determine how widely the token was used. -- **Correlate Additional Detection Context**: Examine `activity_type` and `fidelity_score` to determine additional cities, networks or user agents associated with the token usage. +- **Correlate Additional Detection Context**: Examine `activity_type` and `fidelity_score` to determine additional cities, networks or user agents associated with the token usage. - **Determine Access Key Type**: Examine the `access_key_id` to determine whether the token is short-term (beginning with ASIA) or long-term (beginning with AKIA). - **Check Recent MFA Events**: Determine whether the user recently enabled MFA, registered devices, or assumed a role using this token. - **Review Workload Context**: Confirm whether the user was expected to be active across multiple cities, networks or user agent environments. @@ -80,81 +80,97 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail* metadata _id, _version, _index +FROM logs-aws.cloudtrail* METADATA _id, _version, _index | WHERE @timestamp > NOW() - 30 minutes - // filter on CloudTrail logs for STS temporary session tokens used by IAM users - AND event.dataset == "aws.cloudtrail" AND aws.cloudtrail.user_identity.arn IS NOT NULL AND aws.cloudtrail.user_identity.type == "IAMUser" AND source.ip IS NOT NULL - - // exclude known benign IaC tools and Amazon Network - AND NOT (user_agent.original LIKE "%Terraform%" OR user_agent.original LIKE "%Ansible%" OR user_agent.original LIKE "%Pulumni%") + AND NOT ( + user_agent.original LIKE "%Terraform%" OR + user_agent.original LIKE "%Ansible%" OR + user_agent.original LIKE "%Pulumni%" + ) AND `source.as.organization.name` != "AMAZON-AES" - - // exclude noisy service APIs less indicative of malicous behavior - AND event.provider NOT IN ("health.amazonaws.com", "monitoring.amazonaws.com", "notifications.amazonaws.com", "ce.amazonaws.com", "cost-optimization-hub.amazonaws.com", "servicecatalog-appregistry.amazonaws.com", "securityhub.amazonaws.com") + AND event.provider NOT IN ( + "health.amazonaws.com", "monitoring.amazonaws.com", "notifications.amazonaws.com", + "ce.amazonaws.com", "cost-optimization-hub.amazonaws.com", + "servicecatalog-appregistry.amazonaws.com", "securityhub.amazonaws.com" + ) | EVAL - // create a time window for aggregation - time_window = DATE_TRUNC(30 minutes, @timestamp), - // capture necessary fields for detection and investigation - user_id = aws.cloudtrail.user_identity.arn, - access_key_id = aws.cloudtrail.user_identity.access_key_id, - ip = source.ip, - user_agent = user_agent.original, - ip_string = TO_STRING(source.ip), // Convert IP to string - ip_user_agent_pair = CONCAT(ip_string, " - ", user_agent.original), // Combine IP and user agent - ip_city_pair = CONCAT(ip_string, " - ", source.geo.city_name), // Combine IP and city - city = source.geo.city_name, - event_time = @timestamp, - network_arn = `source.as.organization.name` + esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), + esql.user.id = aws.cloudtrail.user_identity.arn, + esql.user.access_key_id = aws.cloudtrail.user_identity.access_key_id, + esql.source.ip = source.ip, + esql.user_agent.original = user_agent.original, + esql.source.ip_string = TO_STRING(source.ip), + esql.source.ip_user_agent_pair = CONCAT(esql.source.ip_string, " - ", user_agent.original), + esql.source.ip_city_pair = CONCAT(esql.source.ip_string, " - ", source.geo.city_name), + esql.source.geo.city_name = source.geo.city_name, + esql.event.timestamp = @timestamp, + esql.source.network.org_name = `source.as.organization.name` | STATS - event_actions = VALUES(event.action), - event_providers = VALUES(event.provider), - access_key_id = VALUES(access_key_id), - user_id = VALUES(user_id), - ip_list = VALUES(ip), // Collect list of IPs - user_agent_list = VALUES(user_agent), // Collect list of user agents - ip_user_agent_pairs = VALUES(ip_user_agent_pair), // Collect list of IP - user agent pairs - cities_list = VALUES(city), // Collect list of cities - ip_city_pairs = VALUES(ip_city_pair), // Collect list of IP - city pairs - networks_list = VALUES(network_arn), // Collect list of networks - unique_ips = COUNT_DISTINCT(ip), - unique_user_agents = COUNT_DISTINCT(user_agent), - unique_cities = COUNT_DISTINCT(city), - unique_networks = COUNT_DISTINCT(network_arn), - first_seen = MIN(event_time), - last_seen = MAX(event_time), - total_events = COUNT() - BY time_window, access_key_id + esql.event.action.values = VALUES(event.action), + esql.event.provider.values = VALUES(event.provider), + esql.user.access_key_id.values = VALUES(esql.user.access_key_id), + esql.user.id.values = VALUES(esql.user.id), + esql.source.ip.values = VALUES(esql.source.ip), + esql.user_agent.original.values = VALUES(esql.user_agent.original), + esql.source.ip_user_agent_pair.values = VALUES(esql.source.ip_user_agent_pair), + esql.source.geo.city_name.values = VALUES(esql.source.geo.city_name), + esql.source.ip_city_pair.values = VALUES(esql.source.ip_city_pair), + esql.source.network.org_name.values = VALUES(esql.source.network.org_name), + esql.source.ip.count_distinct = COUNT_DISTINCT(esql.source.ip), + esql.user_agent.original.count_distinct = COUNT_DISTINCT(esql.user_agent.original), + esql.source.geo.city_name.count_distinct = COUNT_DISTINCT(esql.source.geo.city_name), + esql.source.network.org_name.count_distinct = COUNT_DISTINCT(esql.source.network.org_name), + esql.event.first_seen = MIN(esql.event.timestamp), + esql.event.last_seen = MAX(esql.event.timestamp), + esql.event.count = COUNT() + BY esql.time_window.date_trunc, esql.user.access_key_id | EVAL - // activity type based on combinations of detection criteria - activity_type = CASE( - unique_ips >= 2 AND unique_networks >= 2 AND unique_cities >= 2 AND unique_user_agents >= 2, "multiple_ip_network_city_user_agent", // high severity - unique_ips >= 2 AND unique_networks >= 2 AND unique_cities >= 2, "multiple_ip_network_city", // high severity - unique_ips >= 2 AND unique_cities >= 2, "multiple_ip_and_city", // medium severity - unique_ips >= 2 AND unique_networks >= 2, "multiple_ip_and_network", // medium severity - unique_ips >= 2 AND unique_user_agents >= 2, "multiple_ip_and_user_agent", // low severity - "normal_activity" - ), - // likelihood of malicious activity based on activity type - fidelity_score = CASE( - activity_type == "multiple_ip_network_city_user_agent", "high", - activity_type == "multiple_ip_network_city", "high", - activity_type == "multiple_ip_and_city", "medium", - activity_type == "multiple_ip_and_network", "medium", - activity_type == "multiple_ip_and_user_agent", "low" - ) + esql.activity.type = CASE( + esql.source.ip.count_distinct >= 2 AND esql.source.network.org_name.count_distinct >= 2 AND esql.source.geo.city_name.count_distinct >= 2 AND esql.user_agent.original.count_distinct >= 2, "multiple_ip_network_city_user_agent", + esql.source.ip.count_distinct >= 2 AND esql.source.network.org_name.count_distinct >= 2 AND esql.source.geo.city_name.count_distinct >= 2, "multiple_ip_network_city", + esql.source.ip.count_distinct >= 2 AND esql.source.geo.city_name.count_distinct >= 2, "multiple_ip_and_city", + esql.source.ip.count_distinct >= 2 AND esql.source.network.org_name.count_distinct >= 2, "multiple_ip_and_network", + esql.source.ip.count_distinct >= 2 AND esql.user_agent.original.count_distinct >= 2, "multiple_ip_and_user_agent", + "normal_activity" + ), + esql.activity.fidelity_score = CASE( + esql.activity.type == "multiple_ip_network_city_user_agent", "high", + esql.activity.type == "multiple_ip_network_city", "high", + esql.activity.type == "multiple_ip_and_city", "medium", + esql.activity.type == "multiple_ip_and_network", "medium", + esql.activity.type == "multiple_ip_and_user_agent", "low" + ) | KEEP - time_window, activity_type, fidelity_score, total_events, first_seen, last_seen, - user_id, access_key_id, event_actions, event_providers, ip_list, user_agent_list, ip_user_agent_pairs, cities_list, ip_city_pairs, networks_list, unique_ips, unique_user_agents, unique_cities, unique_networks - -| WHERE activity_type != "normal_activity" + esql.time_window.date_trunc, + esql.activity.type, + esql.activity.fidelity_score, + esql.event.count, + esql.event.first_seen, + esql.event.last_seen, + esql.user.id.values, + esql.user.access_key_id.values, + esql.event.action.values, + esql.event.provider.values, + esql.source.ip.values, + esql.user_agent.original.values, + esql.source.ip_user_agent_pair.values, + esql.source.geo.city_name.values, + esql.source.ip_city_pair.values, + esql.source.network.org_name.values, + esql.source.ip.count_distinct, + esql.user_agent.original.count_distinct, + esql.source.geo.city_name.count_distinct, + esql.source.network.org_name.count_distinct + +| WHERE esql.activity.type != "normal_activity" ''' From af2198d347005c9a455a1ad3cd3c22be822d5c4c Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 09:53:30 -0400 Subject: [PATCH 13/94] adjusted AWS Signin Single Factor Console Login with Federated User --- ...al_access_signin_console_login_no_mfa.toml | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml index 946da73f024..32050469927 100644 --- a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml +++ b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml @@ -2,7 +2,7 @@ creation_date = "2024/08/19" integration = ["aws"] maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -68,15 +68,29 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* metadata _id, _version, _index +FROM logs-aws.cloudtrail-* METADATA _id, _version, _index + | where event.provider == "signin.amazonaws.com" and event.action == "GetSigninToken" and aws.cloudtrail.event_type == "AwsConsoleSignIn" and aws.cloudtrail.user_identity.type == "FederatedUser" -| dissect aws.cloudtrail.additional_eventdata "{%{?mobile_version_key}=%{mobile_version}, %{?mfa_used_key}=%{mfa_used}}" -| where mfa_used == "No" -| keep @timestamp, event.action, aws.cloudtrail.event_type, aws.cloudtrail.user_identity.type + +// Extract mobile version and MFA usage +| dissect aws.cloudtrail.additional_eventdata + "{%{?mobile_version_key}=%{esql.device.version}, %{?mfa_used_key}=%{esql.auth.mfa.used}}" + +// Only keep events where MFA was not used +| where esql.auth.mfa.used == "No" + +// Keep relevant ECS and dissected fields +| keep + @timestamp, + event.action, + aws.cloudtrail.event_type, + aws.cloudtrail.user_identity.type, + esql.device.version, + esql.auth.mfa.used ''' From fcc8d02243fce9d4a19d2e74a59de1c7d20e6490 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:05:02 -0400 Subject: [PATCH 14/94] adjusted AWS IAM AdministratorAccess Policy Attached to Group --- ...tratoraccess_policy_attached_to_group.toml | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml index 54972f45093..0ce637144f6 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml @@ -2,7 +2,7 @@ creation_date = "2024/05/31" integration = ["aws"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -98,11 +98,28 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* metadata _id, _version, _index -| where event.provider == "iam.amazonaws.com" and event.action == "AttachGroupPolicy" and event.outcome == "success" -| dissect aws.cloudtrail.request_parameters "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{policyName},%{?groupName}=%{group.name}}" -| where policyName == "AdministratorAccess" -| keep @timestamp, event.provider, event.action, event.outcome, policyName, group.name +FROM logs-aws.cloudtrail-* METADATA _id, _version, _index + +| where + event.provider == "iam.amazonaws.com" + and event.action == "AttachGroupPolicy" + and event.outcome == "success" + +// Extract policy and group details from request parameters +| dissect aws.cloudtrail.request_parameters + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.policy.name},%{?groupName}=%{esql.group.name}}" + +// Filter for attachment of AdministratorAccess policy +| where esql.policy.name == "AdministratorAccess" + +// Keep ECS and derived fields +| keep + @timestamp, + event.provider, + event.action, + event.outcome, + esql.policy.name, + esql.group.name ''' From 4eeedb0754ea5c12d77c55c2aae8e963f4b53cc3 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:06:47 -0400 Subject: [PATCH 15/94] adjusted AWS IAM AdministratorAccess Policy Attached to Role --- ...stratoraccess_policy_attached_to_role.toml | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml index 77254acc4c6..870bdb8ee6d 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml @@ -2,7 +2,7 @@ creation_date = "2024/05/31" integration = ["aws"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -97,11 +97,28 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* metadata _id, _version, _index -| where event.provider == "iam.amazonaws.com" and event.action == "AttachRolePolicy" and event.outcome == "success" -| dissect aws.cloudtrail.request_parameters "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{policyName},%{?roleName}=%{role.name}}" -| where policyName == "AdministratorAccess" -| keep @timestamp, event.provider, event.action, event.outcome, policyName, role.name +FROM logs-aws.cloudtrail-* METADATA _id, _version, _index + +| where + event.provider == "iam.amazonaws.com" + and event.action == "AttachRolePolicy" + and event.outcome == "success" + +// Extract policy name and role name from request parameters +| dissect aws.cloudtrail.request_parameters + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.policy.name},%{?roleName}=%{esql.role.name}}" + +// Filter for AdministratorAccess policy attachment +| where esql.policy.name == "AdministratorAccess" + +// Keep relevant ECS and dynamic fields +| keep + @timestamp, + event.provider, + event.action, + event.outcome, + esql.policy.name, + esql.role.name ''' From 0c817ffd2b422e579fe409cf6b0b9eb1592e76c9 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:16:21 -0400 Subject: [PATCH 16/94] adjusted AWS IAM AdministratorAccess Policy Attached to User --- ...stratoraccess_policy_attached_to_user.toml | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml index 906e2d40b4f..31da536f585 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml @@ -2,7 +2,7 @@ creation_date = "2024/05/30" integration = ["aws"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -97,18 +97,29 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws.cloudtrail-* metadata _id, _version, _index -| where event.provider == "iam.amazonaws.com" and event.action == "AttachUserPolicy" and event.outcome == "success" -| dissect aws.cloudtrail.request_parameters "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{policyName},%{?userName}=%{target.userName}}" -| where policyName == "AdministratorAccess" +FROM logs-aws.cloudtrail-* METADATA _id, _version, _index + +| where + event.provider == "iam.amazonaws.com" + and event.action == "AttachUserPolicy" + and event.outcome == "success" + +// Extract policy name and user name from request parameters +| dissect aws.cloudtrail.request_parameters + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.policy.name},%{?userName}=%{esql.target.user.name}}" + +// Filter for AdministratorAccess policy +| where esql.policy.name == "AdministratorAccess" + +// Keep ECS and parsed fields | keep @timestamp, cloud.region, event.provider, event.action, event.outcome, - policyName, - target.userName, + esql.policy.name, + esql.target.user.name, aws.cloudtrail.request_parameters, aws.cloudtrail.user_identity.arn, related.user, From 7c7afcc4f91fd0745efe267c6bed145a1314ef87 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:20:33 -0400 Subject: [PATCH 17/94] adjusted AWS Bedrock Invocations without Guardrails Detected by a Single User Over a Session --- ...rivilege_escalation_sts_role_chaining.toml | 2 +- ..._bedrock_execution_without_guardrails.toml | 35 ++++++++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml b/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml index fb1acc26080..43c46cd30db 100644 --- a/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml +++ b/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml @@ -2,7 +2,7 @@ creation_date = "2024/10/23" integration = ["aws"] maturity = "production" -updated_date = "2025/01/15" +updated_date = "2025/07/16" [rule] author = ["Elastic"] diff --git a/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml b/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml index ea726f44005..efd4eb679af 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/11/25" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -79,13 +79,30 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* -// create time window buckets of 1 minute -| eval time_window = date_trunc(1 minute, @timestamp) -| where gen_ai.guardrail_id is NULL -| KEEP @timestamp, time_window, gen_ai.guardrail_id , user.id -| stats model_invocation_without_guardrails = count() by user.id -| where model_invocation_without_guardrails > 5 -| sort model_invocation_without_guardrails desc +FROM logs-aws_bedrock.invocation-* + +// Create 1-minute time buckets +| eval esql.time_window.date_trunc = DATE_TRUNC(1 minute, @timestamp) + +// Filter for invocations without guardrails +| where gen_ai.guardrail_id IS NULL and user.id IS NOT NULL + +// Keep only relevant fields +| keep + @timestamp, + esql.time_window.date_trunc, + gen_ai.guardrail_id, + user.id + +// Count number of unsafe invocations per user +| stats + esql.ml.invocations.no_guardrails.count = COUNT() + by user.id + +// Filter for suspicious volume +| where esql.ml.invocations.no_guardrails.count > 5 + +// Sort descending +| sort esql.ml.invocations.no_guardrails.count desc ''' From 229e206b5dc9a2364cb4bd8eda9fb02f6ee80728 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:22:31 -0400 Subject: [PATCH 18/94] adjusted AWS Bedrock Guardrails Detected Multiple Violations by a Single User Over a Session --- ...ls_multiple_violations_by_single_user.toml | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml index 11dcb4124e3..9e9ecdcd295 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/05/02" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -80,11 +80,29 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* +FROM logs-aws_bedrock.invocation-* + +// Filter for compliance violations detected | where gen_ai.compliance.violation_detected -| keep user.id, gen_ai.request.model.id, cloud.account.id -| stats violations = count(*) by user.id, gen_ai.request.model.id, cloud.account.id -| where violations > 1 -| sort violations desc + +// Keep relevant ECS + model fields +| keep + user.id, + gen_ai.request.model.id, + cloud.account.id + +// Count violations by user, model, and account +| stats + esql.ml.violations.count = COUNT(*) + by + user.id, + gen_ai.request.model.id, + cloud.account.id + +// Filter for repeated violations +| where esql.ml.violations.count > 1 + +// Sort descending by violation volume +| sort esql.ml.violations.count desc ''' From 0642d252a44fd3e61557e0c75f27ccda32a13bb6 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:25:50 -0400 Subject: [PATCH 19/94] adjusted AWS Bedrock Guardrails Detected Multiple Policy Violations Within a Single Blocked Request --- ...multiple_violations_in_single_request.toml | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml index 9d28ad526c5..28d134b16e5 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/05/02" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -80,12 +80,35 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* +FROM logs-aws_bedrock.invocation-* + +// Filter for policy-blocked requests | where gen_ai.policy.action == "BLOCKED" -| eval policy_violations = mv_count(gen_ai.policy.name) -| where policy_violations > 1 -| keep gen_ai.policy.action, policy_violations, user.id, gen_ai.request.model.id, cloud.account.id, user.id -| stats total_unique_request_violations = count(*) by policy_violations, user.id, gen_ai.request.model.id, cloud.account.id -| sort total_unique_request_violations desc + +// Count number of policy matches per request (multi-valued) +| eval esql.ml.policy.violations.mv_count = MV_COUNT(gen_ai.policy.name) + +// Filter for requests with more than one policy match +| where esql.ml.policy.violations.mv_count > 1 + +// Keep relevant fields +| keep + gen_ai.policy.action, + esql.ml.policy.violations.mv_count, + user.id, + gen_ai.request.model.id, + cloud.account.id + +// Aggregate requests with multiple violations +| stats + esql.ml.policy.violations.total_unique_requests.count = COUNT(*) + by + esql.ml.policy.violations.mv_count, + user.id, + gen_ai.request.model.id, + cloud.account.id + +// Sort by number of unique requests +| sort esql.ml.policy.violations.total_unique_requests.count desc ''' From 3566242e6110531b03db7f317a6afbee56b03ec6 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:28:49 -0400 Subject: [PATCH 20/94] adjusted Unusual High Confidence Content Filter Blocks Detected --- ...confidence_misconduct_blocks_detected.toml | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml index c3cfe44e0e3..5cea1915614 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/05/05" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -79,17 +79,42 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* +FROM logs-aws_bedrock.invocation-* + +// Expand multi-value fields | MV_EXPAND gen_ai.compliance.violation_code | MV_EXPAND gen_ai.policy.confidence | MV_EXPAND gen_ai.policy.name -| where gen_ai.policy.action == "BLOCKED" and gen_ai.policy.name == "content_policy" and gen_ai.policy.confidence LIKE "HIGH" and gen_ai.compliance.violation_code IN ("HATE", "MISCONDUCT", "SEXUAL", "INSULTS", "PROMPT_ATTACK", "VIOLENCE") -| keep user.id, gen_ai.compliance.violation_code -| stats block_count_per_violation = count() by user.id, gen_ai.compliance.violation_code -| SORT block_count_per_violation DESC -| keep user.id, gen_ai.compliance.violation_code, block_count_per_violation -| STATS violation_count = SUM(block_count_per_violation) by user.id -| WHERE violation_count > 5 -| SORT violation_count DESC + +// Filter for high-confidence content policy blocks with targeted violations +| where + gen_ai.policy.action == "BLOCKED" + and gen_ai.policy.name == "content_policy" + and gen_ai.policy.confidence LIKE "HIGH" + and gen_ai.compliance.violation_code IN ("HATE", "MISCONDUCT", "SEXUAL", "INSULTS", "PROMPT_ATTACK", "VIOLENCE") + +// Keep ECS + compliance fields +| keep + user.id, + gen_ai.compliance.violation_code + +// Count blocked violations per user per violation type +| stats + esql.ml.policy.blocked.violation.count = COUNT() + by + user.id, + gen_ai.compliance.violation_code + +// Aggregate all violation types per user +| stats + esql.ml.policy.blocked.violation.total.count = SUM(esql.ml.policy.blocked.violation.count) + by + user.id + +// Filter for users with more than 5 total violations +| where esql.ml.policy.blocked.violation.total.count > 5 + +// Sort by violation volume +| sort esql.ml.policy.blocked.violation.total.count desc ''' From ba588319fcd7084ed8701a05acf47700a94ca0f6 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:30:47 -0400 Subject: [PATCH 21/94] adjusted Potential Abuse of Resources by High Token Count and Large Response Sizes --- ...k_high_resource_consumption_detection.toml | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml b/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml index e2d5e0deda7..e7e5a990d1a 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/05/04" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -79,16 +79,38 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* -| keep user.id, gen_ai.usage.prompt_tokens, gen_ai.usage.completion_tokens -| stats max_tokens = max(gen_ai.usage.prompt_tokens), - total_requests = count(*), - avg_response_size = avg(gen_ai.usage.completion_tokens) - by user.id -// tokens count depends on specific LLM, as is related to how embeddings are generated. -| where max_tokens > 5000 and total_requests > 10 and avg_response_size > 500 -| eval risk_factor = (max_tokens / 1000) * total_requests * (avg_response_size / 500) -| where risk_factor > 10 -| sort risk_factor desc +FROM logs-aws_bedrock.invocation-* + +// Keep token usage data +| keep + user.id, + gen_ai.usage.prompt_tokens, + gen_ai.usage.completion_tokens + +// Aggregate usage metrics +| stats + esql.ml.usage.prompt_tokens.max = MAX(gen_ai.usage.prompt_tokens), + esql.ml.invocations.total.count = COUNT(*), + esql.ml.usage.completion_tokens.avg = AVG(gen_ai.usage.completion_tokens) + by + user.id + +// Filter for suspicious usage patterns +| where + esql.ml.usage.prompt_tokens.max > 5000 + and esql.ml.invocations.total.count > 10 + and esql.ml.usage.completion_tokens.avg > 500 + +// Calculate a custom risk factor +| eval esql.ml.risk.score = + (esql.ml.usage.prompt_tokens.max / 1000) * + esql.ml.invocations.total.count * + (esql.ml.usage.completion_tokens.avg / 500) + +// Filter on risk score +| where esql.ml.risk.score > 10 + +// Sort high risk users to top +| sort esql.ml.risk.score desc ''' From a7f84d00ed7239f68653b43fea8c7f7551fff63a Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:32:24 -0400 Subject: [PATCH 22/94] AWS Bedrock Detected Multiple Attempts to use Denied Models by a Single User --- ...attempts_to_use_denied_models_by_user.toml | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml index 631b76a1e44..c07f43a3231 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/05/02" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -76,12 +76,31 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* +FROM logs-aws_bedrock.invocation-* + +// Filter for access denied errors from GenAI responses | where gen_ai.response.error_code == "AccessDeniedException" -| keep user.id, gen_ai.request.model.id, cloud.account.id, gen_ai.response.error_code -| stats total_denials = count(*) by user.id, gen_ai.request.model.id, cloud.account.id -| where total_denials > 3 -| sort total_denials desc + +// Keep ECS and response fields +| keep + user.id, + gen_ai.request.model.id, + cloud.account.id, + gen_ai.response.error_code + +// Count total denials per user/model/account +| stats + esql.ml.response.access_denied.count = COUNT(*) + by + user.id, + gen_ai.request.model.id, + cloud.account.id + +// Filter for users with repeated denials +| where esql.ml.response.access_denied.count > 3 + +// Sort by volume of denials +| sort esql.ml.response.access_denied.count desc ''' From bd84ad42fd9852c4125606b77d01828f65f15671 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:34:51 -0400 Subject: [PATCH 23/94] Unusual High Denied Sensitive Information Policy Blocks Detected --- ...ve_information_policy_blocks_detected.toml | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml index 8d521be1e61..29bbf04f808 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/11/20" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -78,12 +78,29 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* +FROM logs-aws_bedrock.invocation-* + +// Expand multi-valued policy name field | MV_EXPAND gen_ai.policy.name -| where gen_ai.policy.action == "BLOCKED" and gen_ai.compliance.violation_detected == "true" and gen_ai.policy.name == "sensitive_information_policy" + +// Filter for blocked actions related to sensitive info policy +| where + gen_ai.policy.action == "BLOCKED" + and gen_ai.compliance.violation_detected == "true" + and gen_ai.policy.name == "sensitive_information_policy" + +// Keep only relevant fields | keep user.id -| stats sensitive_information_block = count() by user.id -| where sensitive_information_block > 5 -| sort sensitive_information_block desc + +// Count how many times each user triggered a sensitive info block +| stats + esql.ml.policy.blocked.sensitive_info.count = COUNT() + by user.id + +// Filter for users with more than 5 violations +| where esql.ml.policy.blocked.sensitive_info.count > 5 + +// Sort highest to lowest +| sort esql.ml.policy.blocked.sensitive_info.count desc ''' From 0e505a775f194ec28f23a505647344e235288285 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:36:30 -0400 Subject: [PATCH 24/94] adjusted Unusual High Denied Topic Blocks Detected --- ...multiple_topic_policy_blocks_detected.toml | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml index e5674aaf888..2bc7c086c1b 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/11/20" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -78,12 +78,29 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* +FROM logs-aws_bedrock.invocation-* + +// Expand multi-value policy name field | MV_EXPAND gen_ai.policy.name -| where gen_ai.policy.action == "BLOCKED" and gen_ai.compliance.violation_detected == "true" and gen_ai.policy.name == "topic_policy" + +// Filter for blocked topic policy violations +| where + gen_ai.policy.action == "BLOCKED" + and gen_ai.compliance.violation_detected == "true" + and gen_ai.policy.name == "topic_policy" + +// Keep only user info | keep user.id -| stats denied_topics = count() by user.id -| where denied_topics > 5 -| sort denied_topics desc + +// Count how many times each user triggered a blocked topic policy +| stats + esql.ml.policy.blocked.topic.count = COUNT() + by user.id + +// Filter for excessive violations +| where esql.ml.policy.blocked.topic.count > 5 + +// Sort highest to lowest +| sort esql.ml.policy.blocked.topic.count desc ''' From 020b2307e6fb064867e86494e6dd21972641c47a Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:38:16 -0400 Subject: [PATCH 25/94] adjusted AWS Bedrock Detected Multiple Validation Exception Errors by a Single User --- ...ation_exception_errors_by_single_user.toml | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml index 5839ffdcad4..5541098e2d5 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml @@ -2,7 +2,7 @@ creation_date = "2024/09/11" integration = ["aws_bedrock"] maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -82,14 +82,33 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* -// truncate the timestamp to a 1-minute window -| eval target_time_window = DATE_TRUNC(1 minutes, @timestamp) +FROM logs-aws_bedrock.invocation-* + +// Truncate timestamp to 1-minute window +| eval esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) + +// Filter for validation exceptions in responses | where gen_ai.response.error_code == "ValidationException" -| keep user.id, gen_ai.request.model.id, cloud.account.id, gen_ai.response.error_code, target_time_window -// count the number of users causing validation errors within a 1 minute window -| stats total_denials = count(*) by target_time_window, user.id, cloud.account.id -| where total_denials > 3 + +// Keep relevant ECS and derived fields +| keep + user.id, + gen_ai.request.model.id, + cloud.account.id, + gen_ai.response.error_code, + esql.time_window.date_trunc + +// Count number of denials by user/account/time window +| stats + esql.ml.response.validation_error.count = COUNT(*) + by + esql.time_window.date_trunc, + user.id, + cloud.account.id + +// Filter for excessive errors +| where esql.ml.response.validation_error.count > 3 + ''' From 1686dc81b5a08570776cd004f542bccca8148822 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:39:40 -0400 Subject: [PATCH 26/94] adjusted Unusual High Word Policy Blocks Detected --- ..._multiple_word_policy_blocks_detected.toml | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml index ad942edf801..0877e6efcfb 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2024/11/20" maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -78,12 +78,29 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-aws_bedrock.invocation-* +FROM logs-aws_bedrock.invocation-* + +// Expand multivalued policy names | MV_EXPAND gen_ai.policy.name -| where gen_ai.policy.action == "BLOCKED" and gen_ai.compliance.violation_detected == "true" and gen_ai.policy.name == "word_policy" + +// Filter for blocked profanity-related policy violations +| where + gen_ai.policy.action == "BLOCKED" + and gen_ai.compliance.violation_detected == "true" + and gen_ai.policy.name == "word_policy" + +// Keep relevant user field | keep user.id -| stats profanity_words= count() by user.id -| where profanity_words > 5 -| sort profanity_words desc + +// Count blocked profanity attempts per user +| stats + esql.ml.policy.blocked.profanity.count = COUNT() + by user.id + +// Filter for excessive policy violations +| where esql.ml.policy.blocked.profanity.count > 5 + +// Sort by violation volume +| sort esql.ml.policy.blocked.profanity.count desc ''' From 17b5894f01d3c2eeb05834e73c4be77d585c9124 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:42:25 -0400 Subject: [PATCH 27/94] adjusted Microsoft Entra ID Concurrent Sign-Ins with Suspicious Properties --- ..._access_azure_entra_suspicious_signin.toml | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml index ec6a05b51f0..be3180d8d3b 100644 --- a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml +++ b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/28" integration = ["azure"] maturity = "production" -updated_date = "2025/04/28" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -68,20 +68,64 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.signinlogs* metadata _id, _version, _index -// the rule is scheduled to run every hour and looks for events occured during last 1 hour. -| where @timestamp > NOW() - 1 hours -| where event.dataset == "azure.signinlogs" and source.ip is not null and azure.signinlogs.identity is not null and to_lower(event.outcome) == "success" -| keep @timestamp, azure.signinlogs.identity, source.ip, azure.signinlogs.properties.authentication_requirement, azure.signinlogs.properties.app_id, azure.signinlogs.properties.resource_display_name, azure.signinlogs.properties.authentication_protocol, azure.signinlogs.properties.app_display_name -// devicecode authentication no MFA -| eval device_code = case(azure.signinlogs.properties.authentication_protocol == "deviceCode" and azure.signinlogs.properties.authentication_requirement != "multiFactorAuthentication", azure.signinlogs.identity, null), -// potential Visual Studio Code OAuth code phish - sign-in events with client set to Visual Studio Code - visual_studio = case(azure.signinlogs.properties.app_id == "aebc6443-996d-45c2-90f0-388ff96faa56" and azure.signinlogs.properties.resource_display_name == "Microsoft Graph", azure.signinlogs.identity, null), -// Other sign-in events - other = case(azure.signinlogs.properties.authentication_protocol != "deviceCode" and azure.signinlogs.properties.app_id != "aebc6443-996d-45c2-90f0-388ff96faa56", azure.signinlogs.identity, null) -| stats total = COUNT(*), device_code_count = COUNT_DISTINCT(device_code), vsc = count_distinct(visual_studio), other_count = COUNT_DISTINCT(other), src_ip = COUNT_DISTINCT(source.ip), ips = values(source.ip), clients = values(azure.signinlogs.properties.app_display_name), resources = VALUES(azure.signinlogs.properties.resource_display_name), auth_requirement = VALUES(azure.signinlogs.properties.authentication_requirement) by azure.signinlogs.identity -// 2 unique source.ip for same account - which may indicate the presence 2 sign-ins one by the adversary and the other by the victim -| where src_ip >= 2 and (device_code_count > 0 or vsc >0) +FROM logs-azure.signinlogs* METADATA _id, _version, _index + +// Scheduled to run every hour, reviewing events from past hour +| where + @timestamp > NOW() - 1 hours + and event.dataset == "azure.signinlogs" + and source.ip IS NOT NULL + and azure.signinlogs.identity IS NOT NULL + and TO_LOWER(event.outcome) == "success" + +// Keep relevant raw fields +| keep + @timestamp, + azure.signinlogs.identity, + source.ip, + azure.signinlogs.properties.authentication_requirement, + azure.signinlogs.properties.app_id, + azure.signinlogs.properties.resource_display_name, + azure.signinlogs.properties.authentication_protocol, + azure.signinlogs.properties.app_display_name + +// Case classifications for identity usage +| eval + esql.auth.device_code.case = case( + azure.signinlogs.properties.authentication_protocol == "deviceCode" + and azure.signinlogs.properties.authentication_requirement != "multiFactorAuthentication", + azure.signinlogs.identity, + null), + + esql.auth.visual_studio.case = case( + azure.signinlogs.properties.app_id == "aebc6443-996d-45c2-90f0-388ff96faa56" + and azure.signinlogs.properties.resource_display_name == "Microsoft Graph", + azure.signinlogs.identity, + null), + + esql.auth.other.case = case( + azure.signinlogs.properties.authentication_protocol != "deviceCode" + and azure.signinlogs.properties.app_id != "aebc6443-996d-45c2-90f0-388ff96faa56", + azure.signinlogs.identity, + null) + +// Aggregate metrics by user identity +| stats + esql.event.count = COUNT(*), + esql.auth.device_code.count_distinct = COUNT_DISTINCT(esql.auth.device_code.case), + esql.auth.visual_studio.count_distinct = COUNT_DISTINCT(esql.auth.visual_studio.case), + esql.auth.other.count_distinct = COUNT_DISTINCT(esql.auth.other.case), + esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + esql.source.ip.values = VALUES(source.ip), + esql.auth.client_app.values = VALUES(azure.signinlogs.properties.app_display_name), + esql.resource.display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + esql.auth.requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement) + by azure.signinlogs.identity + +// Detect multiple unique IPs for one user with signs of deviceCode or VSC OAuth usage +| where + esql.source.ip.count_distinct >= 2 + and (esql.auth.device_code.count_distinct > 0 or esql.auth.visual_studio.count_distinct > 0) ''' From e5c02e556e5e8ea483c21e838f47110f1191413f Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:45:58 -0400 Subject: [PATCH 28/94] adjusted Azure Entra MFA TOTP Brute Force Attempts --- ...azure_entra_totp_brute_force_attempts.toml | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml index 64c8660b6c6..47295e6828c 100644 --- a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml +++ b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml @@ -2,7 +2,7 @@ creation_date = "2024/12/11" integration = ["azure"] maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -114,26 +114,24 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-azure.signinlogs* metadata _id, _version, _index -| where - // filter for Entra Sign-In Logs +FROM logs-azure.signinlogs* METADATA _id, _version, _index + +| WHERE event.dataset == "azure.signinlogs" - and azure.signinlogs.operation_name == "Sign-in activity" - - // filter for MFA attempts with OATH conditional access attempts or TOTP - and azure.signinlogs.properties.authentication_requirement == "multiFactorAuthentication" - and azure.signinlogs.properties.mfa_detail.auth_method == "OATH verification code" - - // filter on failures only from brute-force attempts - and azure.signinlogs.properties.conditional_access_status == "failure" - and azure.signinlogs.result_description == "Authentication failed during strong authentication request." -| keep azure.signinlogs.properties.sign_in_identifier -| stats - // aggregate by the sign-in account or principal - failed_totp_code_attempts = count(*) by azure.signinlogs.properties.sign_in_identifier -| where - // filter on high frequency for a single user - failed_totp_code_attempts > 30 + AND azure.signinlogs.operation_name == "Sign-in activity" + AND azure.signinlogs.properties.authentication_requirement == "multiFactorAuthentication" + AND azure.signinlogs.properties.mfa_detail.auth_method == "OATH verification code" + AND azure.signinlogs.properties.conditional_access_status == "failure" + AND azure.signinlogs.result_description == "Authentication failed during strong authentication request." + +| KEEP azure.signinlogs.properties.sign_in_identifier + +| STATS + esql.auth.mfa.totp_failures.count = COUNT(*) + BY azure.signinlogs.properties.sign_in_identifier + +| WHERE + esql.auth.mfa.totp_failures.count > 30 ''' From cd8d6df6127ba51a33f77c2ccdb13f4899c95445 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 10:57:18 -0400 Subject: [PATCH 29/94] adjusted Microsoft Entra ID Sign-In Brute Force Activity --- ..._access_entra_id_brute_force_activity.toml | 158 ++++++++++-------- 1 file changed, 87 insertions(+), 71 deletions(-) diff --git a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml index bc84da89717..8a2887d066f 100644 --- a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml +++ b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml @@ -4,7 +4,7 @@ integration = ["azure"] maturity = "production" min_stack_comments = "Elastic ESQL values aggregation is more performant in 8.16.5 and above." min_stack_version = "8.17.0" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -93,9 +93,7 @@ query = ''' FROM logs-azure.signinlogs* // Define a time window for grouping and maintain the original event timestamp -| EVAL - time_window = DATE_TRUNC(15 minutes, @timestamp), - event_time = @timestamp +| EVAL esql.time_window.date_trunc = DATE_TRUNC(15 minutes, @timestamp) // Filter relevant failed authentication events with specific error codes | WHERE event.dataset == "azure.signinlogs" @@ -129,85 +127,103 @@ FROM logs-azure.signinlogs* AND user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" -// Aggregate statistics for behavioral pattern analysis | STATS - authentication_requirement = VALUES(azure.signinlogs.properties.authentication_requirement), - client_app_id = VALUES(azure.signinlogs.properties.app_id), - client_app_display_name = VALUES(azure.signinlogs.properties.app_display_name), - target_resource_id = VALUES(azure.signinlogs.properties.resource_id), - target_resource_display_name = VALUES(azure.signinlogs.properties.resource_display_name), - conditional_access_status = VALUES(azure.signinlogs.properties.conditional_access_status), - device_detail_browser = VALUES(azure.signinlogs.properties.device_detail.browser), - device_detail_device_id = VALUES(azure.signinlogs.properties.device_detail.device_id), - device_detail_operating_system = VALUES(azure.signinlogs.properties.device_detail.operating_system), - incoming_token_type = VALUES(azure.signinlogs.properties.incoming_token_type), - risk_state = VALUES(azure.signinlogs.properties.risk_state), - session_id = VALUES(azure.signinlogs.properties.session_id), - user_id = VALUES(azure.signinlogs.properties.user_id), - user_principal_name = VALUES(azure.signinlogs.properties.user_principal_name), - result_description = VALUES(azure.signinlogs.result_description), - result_signature = VALUES(azure.signinlogs.result_signature), - result_type = VALUES(azure.signinlogs.result_type), - - unique_users = COUNT_DISTINCT(azure.signinlogs.properties.user_id), - user_id_list = VALUES(azure.signinlogs.properties.user_id), - login_errors = VALUES(azure.signinlogs.result_description), - unique_login_errors = COUNT_DISTINCT(azure.signinlogs.result_description), - error_codes = VALUES(azure.signinlogs.properties.status.error_code), - unique_error_codes = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - request_types = VALUES(azure.signinlogs.properties.incoming_token_type), - app_names = VALUES(azure.signinlogs.properties.app_display_name), - ip_list = VALUES(source.ip), - unique_ips = COUNT_DISTINCT(source.ip), - source_orgs = VALUES(source.`as`.organization.name), - countries = VALUES(source.geo.country_name), - unique_country_count = COUNT_DISTINCT(source.geo.country_name), - unique_asn_orgs = COUNT_DISTINCT(source.`as`.organization.name), - first_seen = MIN(@timestamp), - last_seen = MAX(@timestamp), - total_attempts = COUNT() -BY time_window - -// Determine brute force behavior type based on statistical thresholds + esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), + esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), + esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), + esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), + esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), + esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), + esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), + esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), + esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), + esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), + esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), + esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), + esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), + esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), + esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), + + esql.azure.signinlogs.properties.user_id.unique_count = COUNT_DISTINCT(azure.signinlogs.properties.user_id), + esql.azure.signinlogs.properties.user_id.list = VALUES(azure.signinlogs.properties.user_id), + esql.azure.signinlogs.result_description.values_all = VALUES(azure.signinlogs.result_description), + esql.azure.signinlogs.result_description.unique_count = COUNT_DISTINCT(azure.signinlogs.result_description), + esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), + esql.azure.signinlogs.properties.status.error_code.unique_count = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + esql.azure.signinlogs.properties.incoming_token_type.values_all = VALUES(azure.signinlogs.properties.incoming_token_type), + esql.azure.signinlogs.properties.app_display_name.values_all = VALUES(azure.signinlogs.properties.app_display_name), + esql.source.ip.values = VALUES(source.ip), + esql.source.ip.unique_count = COUNT_DISTINCT(source.ip), + esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + esql.source.geo.country_name.values = VALUES(source.geo.country_name), + esql.source.geo.country_name.unique_count = COUNT_DISTINCT(source.geo.country_name), + esql.source.`as`.organization.name.unique_count = COUNT_DISTINCT(source.`as`.organization.name), + esql.first_seen = MIN(@timestamp), + esql.last_seen = MAX(@timestamp), + esql.total_attempts = COUNT() +BY esql.time_window.date_trunc + | EVAL - duration_seconds = DATE_DIFF("seconds", first_seen, last_seen), - bf_type = CASE( - // Many users, relatively few distinct login errors, distributed over multiple IPs (but not too many), - // and happens quickly. Often bots using leaked credentials. - unique_users >= 10 AND total_attempts >= 30 AND unique_login_errors <= 3 - AND unique_ips >= 5 - AND duration_seconds <= 600 - AND unique_users > unique_ips, + esql.duration.seconds = DATE_DIFF("seconds", esql.first_seen, esql.last_seen), + esql.brute_force.type = CASE( + esql.azure.signinlogs.properties.user_id.unique_count >= 10 AND esql.total_attempts >= 30 AND esql.azure.signinlogs.result_description.unique_count <= 3 + AND esql.source.ip.unique_count >= 5 + AND esql.duration.seconds <= 600 + AND esql.azure.signinlogs.properties.user_id.unique_count > esql.source.ip.unique_count, "credential_stuffing", - // One password against many users. Single error (e.g., "InvalidPassword"), not necessarily fast. - unique_users >= 15 AND unique_login_errors == 1 AND total_attempts >= 15 AND duration_seconds <= 1800, + esql.azure.signinlogs.properties.user_id.unique_count >= 15 AND esql.azure.signinlogs.result_description.unique_count == 1 AND esql.total_attempts >= 15 AND esql.duration.seconds <= 1800, "password_spraying", - // One user targeted repeatedly (same error), OR extremely noisy pattern from many IPs. - (unique_users == 1 AND unique_login_errors == 1 AND total_attempts >= 30 AND duration_seconds <= 300) - OR (unique_users <= 3 AND unique_ips > 30 AND total_attempts >= 100), + (esql.azure.signinlogs.properties.user_id.unique_count == 1 AND esql.azure.signinlogs.result_description.unique_count == 1 AND esql.total_attempts >= 30 AND esql.duration.seconds <= 300) + OR (esql.azure.signinlogs.properties.user_id.unique_count <= 3 AND esql.source.ip.unique_count > 30 AND esql.total_attempts >= 100), "password_guessing", - // everything else "other" ) -// Only keep columns necessary for detection output/reporting | KEEP - time_window, bf_type, duration_seconds, total_attempts, first_seen, last_seen, - unique_users, user_id_list, login_errors, unique_login_errors, - unique_error_codes, error_codes, request_types, app_names, - ip_list, unique_ips, source_orgs, countries, - unique_country_count, unique_asn_orgs, - authentication_requirement, client_app_id, client_app_display_name, - target_resource_id, target_resource_display_name, conditional_access_status, - device_detail_browser, device_detail_device_id, device_detail_operating_system, - incoming_token_type, risk_state, session_id, user_id, - user_principal_name, result_description, result_signature, result_type - -// Remove anything not classified as credential attack activity -| WHERE bf_type != "other" + esql.time_window.date_trunc, + esql.brute_force.type, + esql.duration.seconds, + esql.total_attempts, + esql.first_seen, + esql.last_seen, + esql.azure.signinlogs.properties.user_id.unique_count, + esql.azure.signinlogs.properties.user_id.list, + esql.azure.signinlogs.result_description.values_all, + esql.azure.signinlogs.result_description.unique_count, + esql.azure.signinlogs.properties.status.error_code.unique_count, + esql.azure.signinlogs.properties.status.error_code.values, + esql.azure.signinlogs.properties.incoming_token_type.values_all, + esql.azure.signinlogs.properties.app_display_name.values_all, + esql.source.ip.values, + esql.source.ip.unique_count, + esql.source.`as`.organization.name.values, + esql.source.geo.country_name.values, + esql.source.geo.country_name.unique_count, + esql.source.`as`.organization.name.unique_count, + esql.azure.signinlogs.properties.authentication_requirement.values, + esql.azure.signinlogs.properties.app_id.values, + esql.azure.signinlogs.properties.app_display_name.values, + esql.azure.signinlogs.properties.resource_id.values, + esql.azure.signinlogs.properties.resource_display_name.values, + esql.azure.signinlogs.properties.conditional_access_status.values, + esql.azure.signinlogs.properties.device_detail.browser.values, + esql.azure.signinlogs.properties.device_detail.device_id.values, + esql.azure.signinlogs.properties.device_detail.operating_system.values, + esql.azure.signinlogs.properties.incoming_token_type.values, + esql.azure.signinlogs.properties.risk_state.values, + esql.azure.signinlogs.properties.session_id.values, + esql.azure.signinlogs.properties.user_id.values, + esql.azure.signinlogs.properties.user_principal_name.values, + esql.azure.signinlogs.result_description.values, + esql.azure.signinlogs.result_signature.values, + esql.azure.signinlogs.result_type.values + +| WHERE esql.brute_force.type != "other" ''' From d99dd39f3016dfc37e7c528ba1a1ae3554abf1d7 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:08:03 -0400 Subject: [PATCH 30/94] adjusted Microsoft Entra ID Exccessive Account Lockouts Detected --- ...s_entra_id_excessive_account_lockouts.toml | 144 ++++++++++-------- 1 file changed, 82 insertions(+), 62 deletions(-) diff --git a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml index 62c22b810e8..541b9ef5ebe 100644 --- a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml +++ b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml @@ -2,7 +2,7 @@ creation_date = "2025/07/01" integration = ["azure"] maturity = "production" -updated_date = "2025/07/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -87,76 +87,96 @@ query = ''' FROM logs-azure.signinlogs* | EVAL - time_window = DATE_TRUNC(30 minutes, @timestamp), - user_id = TO_LOWER(azure.signinlogs.properties.user_principal_name), - ip = source.ip, - login_error = azure.signinlogs.result_description, - error_code = azure.signinlogs.properties.status.error_code, - request_type = TO_LOWER(azure.signinlogs.properties.incoming_token_type), - app_name = TO_LOWER(azure.signinlogs.properties.app_display_name), - asn_org = source.`as`.organization.name, - country = source.geo.country_name, - user_agent = user_agent.original, - event_time = @timestamp + esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), + esql.azure.signinlogs.properties.user_principal_name.lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), + esql.azure.signinlogs.properties.incoming_token_type.lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), + esql.azure.signinlogs.properties.app_display_name.lower = TO_LOWER(azure.signinlogs.properties.app_display_name) | WHERE event.dataset == "azure.signinlogs" AND event.category == "authentication" AND azure.signinlogs.category IN ("NonInteractiveUserSignInLogs", "SignInLogs") AND event.outcome == "failure" AND azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication" - AND error_code == 50053 - AND user_id IS NOT NULL AND user_id != "" - AND asn_org != "MICROSOFT-CORP-MSN-AS-BLOCK" + AND azure.signinlogs.properties.status.error_code == 50053 + AND azure.signinlogs.properties.user_principal_name IS NOT NULL + AND azure.signinlogs.properties.user_principal_name != "" + AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" | STATS - authentication_requirement = VALUES(azure.signinlogs.properties.authentication_requirement), - client_app_id = VALUES(azure.signinlogs.properties.app_id), - client_app_display_name = VALUES(azure.signinlogs.properties.app_display_name), - target_resource_id = VALUES(azure.signinlogs.properties.resource_id), - target_resource_display_name = VALUES(azure.signinlogs.properties.resource_display_name), - conditional_access_status = VALUES(azure.signinlogs.properties.conditional_access_status), - device_detail_browser = VALUES(azure.signinlogs.properties.device_detail.browser), - device_detail_device_id = VALUES(azure.signinlogs.properties.device_detail.device_id), - device_detail_operating_system = VALUES(azure.signinlogs.properties.device_detail.operating_system), - incoming_token_type = VALUES(azure.signinlogs.properties.incoming_token_type), - risk_state = VALUES(azure.signinlogs.properties.risk_state), - session_id = VALUES(azure.signinlogs.properties.session_id), - user_id = VALUES(azure.signinlogs.properties.user_id), - user_principal_name = VALUES(azure.signinlogs.properties.user_principal_name), - result_description = VALUES(azure.signinlogs.result_description), - result_signature = VALUES(azure.signinlogs.result_signature), - result_type = VALUES(azure.signinlogs.result_type), - - unique_users = COUNT_DISTINCT(user_id), - user_id_list = VALUES(user_id), - login_errors = VALUES(login_error), - unique_login_errors = COUNT_DISTINCT(login_error), - error_codes = VALUES(error_code), - unique_error_codes = COUNT_DISTINCT(error_code), - request_types = VALUES(request_type), - app_names = VALUES(app_name), - ip_list = VALUES(ip), - unique_ips = COUNT_DISTINCT(ip), - source_orgs = VALUES(asn_org), - countries = VALUES(country), - unique_country_count = COUNT_DISTINCT(country), - unique_asn_orgs = COUNT_DISTINCT(asn_org), - first_seen = MIN(event_time), - last_seen = MAX(event_time), - total_attempts = COUNT() -BY time_window -| WHERE unique_users >= 15 AND total_attempts >= 20 + esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), + esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), + esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), + esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), + esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), + esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), + esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), + esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), + esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), + esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), + esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), + esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), + esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), + esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), + esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), + + esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.properties.user_principal_name.lower), + esql.azure.signinlogs.properties.user_principal_name.lower.values = VALUES(esql.azure.signinlogs.properties.user_principal_name.lower), + esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), + esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), + esql.azure.signinlogs.properties.incoming_token_type.lower.values = VALUES(esql.azure.signinlogs.properties.incoming_token_type.lower), + esql.azure.signinlogs.properties.app_display_name.lower.values = VALUES(esql.azure.signinlogs.properties.app_display_name.lower), + esql.source.ip.values = VALUES(source.ip), + esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + esql.source.geo.country_name.values = VALUES(source.geo.country_name), + esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + esql.@timestamp.min = MIN(@timestamp), + esql.@timestamp.max = MAX(@timestamp), + esql.event.count = COUNT() +BY esql.time_window.date_trunc + +| WHERE esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 AND esql.event.count >= 20 + | KEEP - time_window, total_attempts, first_seen, last_seen, - unique_users, user_id_list, login_errors, unique_login_errors, - unique_error_codes, error_codes, request_types, app_names, - ip_list, unique_ips, source_orgs, countries, - unique_country_count, unique_asn_orgs, - authentication_requirement, client_app_id, client_app_display_name, - target_resource_id, target_resource_display_name, conditional_access_status, - device_detail_browser, device_detail_device_id, device_detail_operating_system, - incoming_token_type, risk_state, session_id, user_id, - user_principal_name, result_description, result_signature, result_type + esql.time_window.date_trunc, + esql.event.count, + esql.@timestamp.min, + esql.@timestamp.max, + esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct, + esql.azure.signinlogs.properties.user_principal_name.lower.values, + esql.azure.signinlogs.result_description.count_distinct, + esql.azure.signinlogs.result_description.values, + esql.azure.signinlogs.properties.status.error_code.count_distinct, + esql.azure.signinlogs.properties.status.error_code.values, + esql.azure.signinlogs.properties.incoming_token_type.lower.values, + esql.azure.signinlogs.properties.app_display_name.lower.values, + esql.source.ip.values, + esql.source.ip.count_distinct, + esql.source.`as`.organization.name.values, + esql.source.`as`.organization.name.count_distinct, + esql.source.geo.country_name.values, + esql.source.geo.country_name.count_distinct, + esql.azure.signinlogs.properties.authentication_requirement.values, + esql.azure.signinlogs.properties.app_id.values, + esql.azure.signinlogs.properties.app_display_name.values, + esql.azure.signinlogs.properties.resource_id.values, + esql.azure.signinlogs.properties.resource_display_name.values, + esql.azure.signinlogs.properties.conditional_access_status.values, + esql.azure.signinlogs.properties.device_detail.browser.values, + esql.azure.signinlogs.properties.device_detail.device_id.values, + esql.azure.signinlogs.properties.device_detail.operating_system.values, + esql.azure.signinlogs.properties.incoming_token_type.values, + esql.azure.signinlogs.properties.risk_state.values, + esql.azure.signinlogs.properties.session_id.values, + esql.azure.signinlogs.properties.user_id.values, + esql.azure.signinlogs.properties.user_principal_name.values, + esql.azure.signinlogs.result_description.values, + esql.azure.signinlogs.result_signature.values, + esql.azure.signinlogs.result_type.values ''' From 80ab978294e329def0fa134e3b0f527cb5a9467f Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:12:43 -0400 Subject: [PATCH 31/94] adjusted Microsoft 365 Brute Force via Entra ID Sign-Ins --- ...ntra_signin_brute_force_microsoft_365.toml | 177 ++++++++++-------- 1 file changed, 100 insertions(+), 77 deletions(-) diff --git a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml index a311d6ba5ec..395c158a5c9 100644 --- a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml +++ b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml @@ -2,7 +2,7 @@ creation_date = "2024/09/06" integration = ["azure"] maturity = "production" -updated_date = "2025/07/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -91,24 +91,18 @@ query = ''' FROM logs-azure.signinlogs* | EVAL - time_window = DATE_TRUNC(15 minutes, @timestamp), - user_id = TO_LOWER(azure.signinlogs.properties.user_principal_name), - ip = source.ip, - login_error = azure.signinlogs.result_description, - error_code = azure.signinlogs.properties.status.error_code, - request_type = TO_LOWER(azure.signinlogs.properties.incoming_token_type), - app_name = TO_LOWER(azure.signinlogs.properties.app_display_name), - asn_org = source.`as`.organization.name, - country = source.geo.country_name, - user_agent = user_agent.original, - event_time = @timestamp + esql.time_window.date_trunc = DATE_TRUNC(15 minutes, @timestamp), + esql.azure.signinlogs.properties.user_principal_name.lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), + esql.azure.signinlogs.properties.incoming_token_type.lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), + esql.azure.signinlogs.properties.app_display_name.lower = TO_LOWER(azure.signinlogs.properties.app_display_name), + esql.user_agent.original = user_agent.original | WHERE event.dataset == "azure.signinlogs" AND event.category == "authentication" AND azure.signinlogs.category IN ("NonInteractiveUserSignInLogs", "SignInLogs") AND azure.signinlogs.properties.resource_display_name RLIKE "(.*)365|SharePoint|Exchange|Teams|Office(.*)" AND event.outcome == "failure" - AND error_code != 50053 + AND azure.signinlogs.properties.status.error_code != 50053 AND azure.signinlogs.properties.status.error_code IN ( 50034, // UserAccountNotFound 50126, // InvalidUsernameOrPassword @@ -131,84 +125,113 @@ FROM logs-azure.signinlogs* 120002, // PasswordChangeInvalidNewPasswordWeak 120020 // PasswordChangeFailure ) - AND user_id IS NOT NULL AND user_id != "" - AND user_agent != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" + AND azure.signinlogs.properties.user_principal_name IS NOT NULL + AND azure.signinlogs.properties.user_principal_name != "" + AND user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" | STATS - authentication_requirement = VALUES(azure.signinlogs.properties.authentication_requirement), - client_app_id = VALUES(azure.signinlogs.properties.app_id), - client_app_display_name = VALUES(azure.signinlogs.properties.app_display_name), - target_resource_id = VALUES(azure.signinlogs.properties.resource_id), - target_resource_display_name = VALUES(azure.signinlogs.properties.resource_display_name), - conditional_access_status = VALUES(azure.signinlogs.properties.conditional_access_status), - device_detail_browser = VALUES(azure.signinlogs.properties.device_detail.browser), - device_detail_device_id = VALUES(azure.signinlogs.properties.device_detail.device_id), - device_detail_operating_system = VALUES(azure.signinlogs.properties.device_detail.operating_system), - incoming_token_type = VALUES(azure.signinlogs.properties.incoming_token_type), - risk_state = VALUES(azure.signinlogs.properties.risk_state), - session_id = VALUES(azure.signinlogs.properties.session_id), - user_id = VALUES(azure.signinlogs.properties.user_id), - user_principal_name = VALUES(azure.signinlogs.properties.user_principal_name), - result_description = VALUES(azure.signinlogs.result_description), - result_signature = VALUES(azure.signinlogs.result_signature), - result_type = VALUES(azure.signinlogs.result_type), - - unique_users = COUNT_DISTINCT(user_id), - user_id_list = VALUES(user_id), - login_errors = VALUES(login_error), - unique_login_errors = COUNT_DISTINCT(login_error), - error_codes = VALUES(error_code), - unique_error_codes = COUNT_DISTINCT(error_code), - request_types = VALUES(request_type), - app_names = VALUES(app_name), - ip_list = VALUES(ip), - unique_ips = COUNT_DISTINCT(ip), - source_orgs = VALUES(asn_org), - countries = VALUES(country), - unique_country_count = COUNT_DISTINCT(country), - unique_asn_orgs = COUNT_DISTINCT(asn_org), - first_seen = MIN(event_time), - last_seen = MAX(event_time), - total_attempts = COUNT() -BY time_window + esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), + esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), + esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), + esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), + esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), + esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), + esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), + esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), + esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), + esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), + esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), + esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), + esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), + esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), + esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), + + esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.properties.user_principal_name.lower), + esql.azure.signinlogs.properties.user_principal_name.lower.values = VALUES(esql.azure.signinlogs.properties.user_principal_name.lower), + esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), + esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), + esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), + esql.azure.signinlogs.properties.incoming_token_type.lower.values = VALUES(esql.azure.signinlogs.properties.incoming_token_type.lower), + esql.azure.signinlogs.properties.app_display_name.lower.values = VALUES(esql.azure.signinlogs.properties.app_display_name.lower), + esql.source.ip.values = VALUES(source.ip), + esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + esql.source.geo.country_name.values = VALUES(source.geo.country_name), + esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + esql.@timestamp.min = MIN(@timestamp), + esql.@timestamp.max = MAX(@timestamp), + esql.event.count = COUNT() +BY esql.time_window.date_trunc | EVAL - duration_seconds = DATE_DIFF("seconds", first_seen, last_seen), - bf_type = CASE( - // Many users, relatively few distinct login errors, distributed over multiple IPs (but not too many), - // and happens quickly. Often bots using leaked credentials. - unique_users >= 10 AND total_attempts >= 30 AND unique_login_errors <= 3 - AND unique_ips >= 5 - AND duration_seconds <= 600 - AND unique_users > unique_ips, + esql.event.duration_seconds = DATE_DIFF("seconds", esql.@timestamp.min, esql.@timestamp.max), + esql.event.bf_type = CASE( + esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 10 + AND esql.event.count >= 30 + AND esql.azure.signinlogs.result_description.count_distinct <= 3 + AND esql.source.ip.count_distinct >= 5 + AND esql.event.duration_seconds <= 600 + AND esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct > esql.source.ip.count_distinct, "credential_stuffing", - // One password against many users. Single error (e.g., "InvalidPassword"), not necessarily fast. - unique_users >= 15 AND unique_login_errors == 1 AND total_attempts >= 15 AND duration_seconds <= 1800, + esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 + AND esql.azure.signinlogs.result_description.count_distinct == 1 + AND esql.event.count >= 15 + AND esql.event.duration_seconds <= 1800, "password_spraying", - // One user targeted repeatedly (same error), OR extremely noisy pattern from many IPs. - (unique_users == 1 AND unique_login_errors == 1 AND total_attempts >= 30 AND duration_seconds <= 300) - OR (unique_users <= 3 AND unique_ips > 30 AND total_attempts >= 100), + (esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct == 1 + AND esql.azure.signinlogs.result_description.count_distinct == 1 + AND esql.event.count >= 30 + AND esql.event.duration_seconds <= 300) + OR (esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct <= 3 + AND esql.source.ip.count_distinct > 30 + AND esql.event.count >= 100), "password_guessing", - // everything else "other" ) +| WHERE esql.event.bf_type != "other" + | KEEP - time_window, bf_type, duration_seconds, total_attempts, first_seen, last_seen, - unique_users, user_id_list, login_errors, unique_login_errors, - unique_error_codes, error_codes, request_types, app_names, - ip_list, unique_ips, source_orgs, countries, - unique_country_count, unique_asn_orgs, - authentication_requirement, client_app_id, client_app_display_name, - target_resource_id, target_resource_display_name, conditional_access_status, - device_detail_browser, device_detail_device_id, device_detail_operating_system, - incoming_token_type, risk_state, session_id, user_id, - user_principal_name, result_description, result_signature, result_type - -| WHERE bf_type != "other" + esql.time_window.date_trunc, + esql.event.bf_type, + esql.event.duration_seconds, + esql.event.count, + esql.@timestamp.min, + esql.@timestamp.max, + esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct, + esql.azure.signinlogs.properties.user_principal_name.lower.values, + esql.azure.signinlogs.result_description.count_distinct, + esql.azure.signinlogs.result_description.values, + esql.azure.signinlogs.properties.status.error_code.count_distinct, + esql.azure.signinlogs.properties.status.error_code.values, + esql.azure.signinlogs.properties.incoming_token_type.lower.values, + esql.azure.signinlogs.properties.app_display_name.lower.values, + esql.source.ip.values, + esql.source.ip.count_distinct, + esql.source.`as`.organization.name.values, + esql.source.`as`.organization.name.count_distinct, + esql.source.geo.country_name.values, + esql.source.geo.country_name.count_distinct, + esql.azure.signinlogs.properties.authentication_requirement.values, + esql.azure.signinlogs.properties.app_id.values, + esql.azure.signinlogs.properties.app_display_name.values, + esql.azure.signinlogs.properties.resource_id.values, + esql.azure.signinlogs.properties.resource_display_name.values, + esql.azure.signinlogs.properties.conditional_access_status.values, + esql.azure.signinlogs.properties.device_detail.browser.values, + esql.azure.signinlogs.properties.device_detail.device_id.values, + esql.azure.signinlogs.properties.device_detail.operating_system.values, + esql.azure.signinlogs.properties.incoming_token_type.values, + esql.azure.signinlogs.properties.risk_state.values, + esql.azure.signinlogs.properties.session_id.values, + esql.azure.signinlogs.properties.user_id.values ''' From effa807b2466f0525c304da781aadd51306f2141 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:14:48 -0400 Subject: [PATCH 32/94] deprecated Azure Entra Sign-in Brute Force Microsoft 365 Accounts by Repeat Source --- ...entra_signin_brute_force_microsoft_365_repeat_source.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename rules/{integrations/azure => _deprecated}/credential_access_entra_signin_brute_force_microsoft_365_repeat_source.toml (98%) diff --git a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365_repeat_source.toml b/rules/_deprecated/credential_access_entra_signin_brute_force_microsoft_365_repeat_source.toml similarity index 98% rename from rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365_repeat_source.toml rename to rules/_deprecated/credential_access_entra_signin_brute_force_microsoft_365_repeat_source.toml index 98bc858fcff..f5050278864 100644 --- a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365_repeat_source.toml +++ b/rules/_deprecated/credential_access_entra_signin_brute_force_microsoft_365_repeat_source.toml @@ -1,8 +1,9 @@ [metadata] creation_date = "2024/09/06" +deprecation_date = "2025/07/16" integration = ["azure"] -maturity = "production" -updated_date = "2025/06/06" +maturity = "deprecated" +updated_date = "2025/07/16" [rule] author = ["Elastic"] From 407ce82d10cf1334dc4a88b84b6d38cd505764d4 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:18:05 -0400 Subject: [PATCH 33/94] adjusted Microsoft Entra ID Session Reuse with Suspicious Graph Access --- ...ingle_session_from_multiple_addresses.toml | 82 +++++++++++-------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml b/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml index 6feacb31af9..318abdfa028 100644 --- a/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml +++ b/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml @@ -2,7 +2,7 @@ creation_date = "2025/05/08" integration = ["azure"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -90,47 +90,63 @@ type = "esql" query = ''' FROM logs-azure.* | WHERE - (event.dataset == "azure.signinlogs" AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" AND azure.signinlogs.properties.session_id IS NOT NULL) + (event.dataset == "azure.signinlogs" + AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" + AND azure.signinlogs.properties.session_id IS NOT NULL) OR - (event.dataset == "azure.graphactivitylogs" AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" AND azure.graphactivitylogs.properties.c_sid IS NOT NULL) + (event.dataset == "azure.graphactivitylogs" + AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" + AND azure.graphactivitylogs.properties.c_sid IS NOT NULL) + | EVAL - session_id = COALESCE(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid), - user_id = COALESCE(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id), - client_id = COALESCE(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id), - source_ip = source.ip, - event_time = @timestamp, - event_type = CASE( + esql.azure.signinlogs.properties.session_id.coalesce = COALESCE(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid), + esql.azure.signinlogs.properties.user_id.coalesce = COALESCE(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id), + esql.azure.signinlogs.properties.app_id.coalesce = COALESCE(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id), + esql.source.ip = source.ip, + esql.@timestamp = @timestamp, + esql.event.type.case = CASE( event.dataset == "azure.signinlogs", "signin", event.dataset == "azure.graphactivitylogs", "graph", "other" ), - time_window = DATE_TRUNC(5 minutes, @timestamp) -| KEEP session_id, source_ip, event_time, event_type, time_window, user_id, client_id + esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp) + +| KEEP + esql.azure.signinlogs.properties.session_id.coalesce, + esql.source.ip, + esql.@timestamp, + esql.event.type.case, + esql.time_window.date_trunc, + esql.azure.signinlogs.properties.user_id.coalesce, + esql.azure.signinlogs.properties.app_id.coalesce + | STATS - user_id = VALUES(user_id), - session_id = VALUES(session_id), - source_ip_list = VALUES(source_ip), - source_ip_count = COUNT_DISTINCT(source_ip), - client_id_list = VALUES(client_id), - application_count = COUNT_DISTINCT(client_id), - event_type_list = VALUES(event_type), - event_type_count = COUNT_DISTINCT(event_type), - event_start = MIN(event_time), - event_end = MAX(event_time), - signin_time = MIN(CASE(event_type == "signin", event_time, NULL)), - graph_time = MIN(CASE(event_type == "graph", event_time, NULL)), - document_count = COUNT() - BY session_id, time_window + esql.azure.signinlogs.properties.user_id.coalesce.values = VALUES(esql.azure.signinlogs.properties.user_id.coalesce), + esql.azure.signinlogs.properties.session_id.coalesce.values = VALUES(esql.azure.signinlogs.properties.session_id.coalesce), + esql.source.ip.values = VALUES(esql.source.ip), + esql.source.ip.count_distinct = COUNT_DISTINCT(esql.source.ip), + esql.azure.signinlogs.properties.app_id.coalesce.values = VALUES(esql.azure.signinlogs.properties.app_id.coalesce), + esql.azure.signinlogs.properties.app_id.coalesce.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.properties.app_id.coalesce), + esql.event.type.case.values = VALUES(esql.event.type.case), + esql.event.type.case.count_distinct = COUNT_DISTINCT(esql.event.type.case), + esql.@timestamp.min = MIN(esql.@timestamp), + esql.@timestamp.max = MAX(esql.@timestamp), + esql.signin.time.min = MIN(CASE(esql.event.type.case == "signin", esql.@timestamp, NULL)), + esql.graph.time.min = MIN(CASE(esql.event.type.case == "graph", esql.@timestamp, NULL)), + esql.event.count = COUNT() + BY esql.azure.signinlogs.properties.session_id.coalesce, esql.time_window.date_trunc + | EVAL - duration_minutes = DATE_DIFF("minutes", event_start, event_end), - signin_to_graph_delay_minutes = DATE_DIFF("minutes", signin_time, graph_time) + esql.event.duration_minutes.date_diff = DATE_DIFF("minutes", esql.@timestamp.min, esql.@timestamp.max), + esql.event.signin_to_graph_delay_minutes.date_diff = DATE_DIFF("minutes", esql.signin.time.min, esql.graph.time.min) + | WHERE - event_type_count > 1 AND - source_ip_count > 1 AND - duration_minutes <= 5 AND - signin_time IS NOT NULL AND - graph_time IS NOT NULL AND - signin_to_graph_delay_minutes >= 0 + esql.event.type.case.count_distinct > 1 AND + esql.source.ip.count_distinct > 1 AND + esql.event.duration_minutes.date_diff <= 5 AND + esql.signin.time.min IS NOT NULL AND + esql.graph.time.min IS NOT NULL AND + esql.event.signin_to_graph_delay_minutes.date_diff >= 0 ''' From fbd78084cf521ac671c4ce89255ac0976b78b90a Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:21:45 -0400 Subject: [PATCH 34/94] adjusted Suspicious Microsoft OAuth Flow via Auth Broker to DRS --- ...ous_oauth_flow_via_auth_broker_to_drs.toml | 168 ++++++++++-------- 1 file changed, 89 insertions(+), 79 deletions(-) diff --git a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml index 61581563aa1..9cbf13f8883 100644 --- a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml +++ b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/30" integration = ["azure"] maturity = "production" -updated_date = "2025/07/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -89,95 +89,105 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.signinlogs* metadata _id, _version, _index - -// Filter for Microsoft Entra ID sign-in logs -| WHERE event.dataset == "azure.signinlogs" - AND event.outcome == "success" - AND azure.signinlogs.properties.user_type == "Member" - AND azure.signinlogs.identity IS NOT NULL - AND azure.signinlogs.properties.user_principal_name IS NOT NULL - AND source.address IS NOT NULL - - // Filter for MAB as client (app_id) and DRS as resource (resource_id) - AND azure.signinlogs.properties.app_id == "29d9ed98-a469-4536-ade2-f981bc1d605e" // MAB - AND azure.signinlogs.properties.resource_id == "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9" // DRS - -// Normalize timestamps into 30-minute detection windows -| EVAL target_time_window = DATE_TRUNC(30 minutes, @timestamp) +FROM logs-azure.signinlogs* METADATA _id, _version, _index +| WHERE + event.dataset == "azure.signinlogs" AND + event.outcome == "success" AND + azure.signinlogs.properties.user_type == "Member" AND + azure.signinlogs.identity IS NOT NULL AND + azure.signinlogs.properties.user_principal_name IS NOT NULL AND + source.address IS NOT NULL AND + azure.signinlogs.properties.app_id == "29d9ed98-a469-4536-ade2-f981bc1d605e" AND // MAB + azure.signinlogs.properties.resource_id == "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9" // DRS -// Tag browser-based requests and extract session ID | EVAL - session_id = azure.signinlogs.properties.session_id, - is_browser = CASE( + esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), + esql.azure.signinlogs.properties.session_id = azure.signinlogs.properties.session_id, + esql.is_browser.case = CASE( TO_LOWER(azure.signinlogs.properties.device_detail.browser) RLIKE "(chrome|firefox|edge|safari).*", 1, 0 ) | STATS - // user & session identity - user_display_name = VALUES(azure.signinlogs.properties.user_display_name), - user_principal_name = VALUES(azure.signinlogs.properties.user_principal_name), - session_id = VALUES(azure.signinlogs.properties.session_id), - unique_token_id = VALUES(azure.signinlogs.properties.unique_token_identifier), - - // geolocation - city_name = VALUES(source.geo.city_name), - country_name = VALUES(source.geo.country_name), - region_name = VALUES(source.geo.region_name), - source_ip = VALUES(source.address), - ip_count = COUNT_DISTINCT(source.address), - autonomous_system = VALUES(source.`as`.organization.name), - - // authentication context - auth_protocol = VALUES(azure.signinlogs.properties.authentication_protocol), - auth_requirement = VALUES(azure.signinlogs.properties.authentication_requirement), - is_interactive = VALUES(azure.signinlogs.properties.is_interactive), - - // token & app context - token_type = VALUES(azure.signinlogs.properties.incoming_token_type), - token_session_status = VALUES(azure.signinlogs.properties.token_protection_status_details.sign_in_session_status), - session_id_count = COUNT_DISTINCT(session_id), - client_app_display_name = VALUES(azure.signinlogs.properties.app_display_name), - client_app_ids = VALUES(azure.signinlogs.properties.app_id), - target_resource_ids = VALUES(azure.signinlogs.properties.resource_id), - target_resource_display_name = VALUES(azure.signinlogs.properties.resource_display_name), - - // tenant details - app_owner_tenant_id = VALUES(azure.signinlogs.properties.app_owner_tenant_id), - resource_owner_tenant_id = VALUES(azure.signinlogs.properties.resource_owner_tenant_id), - - // conditional access & risk signals - conditional_access_status = VALUES(azure.signinlogs.properties.conditional_access_status), - risk_state = VALUES(azure.signinlogs.properties.risk_state), - risk_level_aggregated = VALUES(azure.signinlogs.properties.risk_level_aggregated), - - // user agent & device - browser = VALUES(azure.signinlogs.properties.device_detail.browser), - os = VALUES(azure.signinlogs.properties.device_detail.operating_system), - user_agent = VALUES(user_agent.original), - has_browser = MAX(is_browser), - - auth_count = COUNT(*) -BY - target_time_window, + esql.azure.signinlogs.properties.user_display_name.values = VALUES(azure.signinlogs.properties.user_display_name), + esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), + esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), + esql.azure.signinlogs.properties.unique_token_identifier.values = VALUES(azure.signinlogs.properties.unique_token_identifier), + + esql.source.geo.city_name.values = VALUES(source.geo.city_name), + esql.source.geo.country_name.values = VALUES(source.geo.country_name), + esql.source.geo.region_name.values = VALUES(source.geo.region_name), + esql.source.address.values = VALUES(source.address), + esql.source.address.count_distinct = COUNT_DISTINCT(source.address), + esql.source.as.organization.name.values = VALUES(source.`as`.organization.name), + + esql.azure.signinlogs.properties.authentication_protocol.values = VALUES(azure.signinlogs.properties.authentication_protocol), + esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), + esql.azure.signinlogs.properties.is_interactive.values = VALUES(azure.signinlogs.properties.is_interactive), + + esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), + esql.azure.signinlogs.properties.token_protection_status_details.sign_in_session_status.values = VALUES(azure.signinlogs.properties.token_protection_status_details.sign_in_session_status), + esql.azure.signinlogs.properties.session_id.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.session_id), + esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), + esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), + esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), + esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + + esql.azure.signinlogs.properties.app_owner_tenant_id.values = VALUES(azure.signinlogs.properties.app_owner_tenant_id), + esql.azure.signinlogs.properties.resource_owner_tenant_id.values = VALUES(azure.signinlogs.properties.resource_owner_tenant_id), + + esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), + esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), + esql.azure.signinlogs.properties.risk_level_aggregated.values = VALUES(azure.signinlogs.properties.risk_level_aggregated), + + esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), + esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + esql.user_agent.original.values = VALUES(user_agent.original), + esql.is_browser.case.max = MAX(esql.is_browser.case), + + esql.event.count = COUNT(*) + BY + esql.time_window.date_trunc, azure.signinlogs.properties.user_principal_name, - session_id + azure.signinlogs.properties.session_id | KEEP - target_time_window, user_display_name, user_principal_name, session_id, unique_token_id, - city_name, country_name, region_name, source_ip, ip_count, autonomous_system, - auth_protocol, auth_requirement, is_interactive, - token_type, token_session_status, session_id_count, client_app_display_name, - client_app_ids, target_resource_ids, target_resource_display_name, - app_owner_tenant_id, resource_owner_tenant_id, - conditional_access_status, risk_state, risk_level_aggregated, - browser, os, user_agent, has_browser, auth_count + esql.time_window.date_trunc, + esql.azure.signinlogs.properties.user_display_name.values, + esql.azure.signinlogs.properties.user_principal_name.values, + esql.azure.signinlogs.properties.session_id.values, + esql.azure.signinlogs.properties.unique_token_identifier.values, + esql.source.geo.city_name.values, + esql.source.geo.country_name.values, + esql.source.geo.region_name.values, + esql.source.address.values, + esql.source.address.count_distinct, + esql.source.as.organization.name.values, + esql.azure.signinlogs.properties.authentication_protocol.values, + esql.azure.signinlogs.properties.authentication_requirement.values, + esql.azure.signinlogs.properties.is_interactive.values, + esql.azure.signinlogs.properties.incoming_token_type.values, + esql.azure.signinlogs.properties.token_protection_status_details.sign_in_session_status.values, + esql.azure.signinlogs.properties.session_id.count_distinct, + esql.azure.signinlogs.properties.app_display_name.values, + esql.azure.signinlogs.properties.app_id.values, + esql.azure.signinlogs.properties.resource_id.values, + esql.azure.signinlogs.properties.resource_display_name.values, + esql.azure.signinlogs.properties.app_owner_tenant_id.values, + esql.azure.signinlogs.properties.resource_owner_tenant_id.values, + esql.azure.signinlogs.properties.conditional_access_status.values, + esql.azure.signinlogs.properties.risk_state.values, + esql.azure.signinlogs.properties.risk_level_aggregated.values, + esql.azure.signinlogs.properties.device_detail.browser.values, + esql.azure.signinlogs.properties.device_detail.operating_system.values, + esql.user_agent.original.values, + esql.is_browser.case.max, + esql.event.count | WHERE - ip_count >= 2 AND - session_id_count == 1 AND - has_browser >= 1 AND - auth_count >= 2 + esql.source.address.count_distinct >= 2 AND + esql.azure.signinlogs.properties.session_id.count_distinct == 1 AND + esql.is_browser.case.max >= 1 AND + esql.event.count >= 2 ''' From 92dad4707e3f4d938cc0392d341635517bf79b5f Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:25:35 -0400 Subject: [PATCH 35/94] adjusted Potential Denial of Azure OpenAI ML Service --- ...openai_denial_of_ml_service_detection.toml | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml b/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml index e470b7e5e87..0660f69f091 100644 --- a/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2025/02/25" maturity = "production" -updated_date = "2025/04/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -76,13 +76,24 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-azure_openai.logs-* -// truncate the timestamp to a 1-minute window -| eval target_time_window = DATE_TRUNC(1 minutes, @timestamp) -| where azure.open_ai.operation_name == "ChatCompletions_Create" -| keep azure.open_ai.properties.request_length, azure.resource.name, cloud.account.id,target_time_window -| stats count = count(), avg_request_size = avg(azure.open_ai.properties.request_length) by target_time_window, azure.resource.name -| where count >= 10 and avg_request_size >= 5000 -| sort count desc +FROM logs-azure_openai.logs-* +| EVAL + esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) +| WHERE azure.open_ai.operation_name == "ChatCompletions_Create" +| KEEP + azure.open_ai.properties.request_length, + azure.resource.name, + cloud.account.id, + esql.time_window.date_trunc +| STATS + esql.event.count = COUNT(*), + esql.azure.open_ai.properties.request_length.avg = AVG(azure.open_ai.properties.request_length) + BY + esql.time_window.date_trunc, + azure.resource.name +| WHERE + esql.event.count >= 10 AND + esql.azure.open_ai.properties.request_length.avg >= 5000 +| SORT esql.event.count DESC ''' From c90f6b25e27948f4bf26a5632e35fab0ea54238c Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:28:12 -0400 Subject: [PATCH 36/94] adjusted Azure OpenAI Insecure Output Handling --- ...ai_insecure_output_handling_detection.toml | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml b/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml index 9a77b283ba6..fc21007479e 100644 --- a/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2025/02/25" maturity = "production" -updated_date = "2025/04/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -71,11 +71,23 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-azure_openai.logs-* -| where azure.open_ai.properties.response_length == 0 and azure.open_ai.result_signature == "200" and azure.open_ai.operation_name == "ChatCompletions_Create" -| keep azure.open_ai.properties.request_length, azure.open_ai.result_signature, cloud.account.id, azure.resource.name -| stats count = count() by azure.resource.name -| where count >= 10 -| sort count desc +FROM logs-azure_openai.logs-* +| WHERE + azure.open_ai.properties.response_length == 0 AND + azure.open_ai.result_signature == "200" AND + azure.open_ai.operation_name == "ChatCompletions_Create" +| KEEP + azure.open_ai.properties.request_length, + azure.open_ai.result_signature, + cloud.account.id, + azure.resource.name +| STATS + esql.event.count = COUNT(*) + BY + azure.resource.name +| WHERE + esql.event.count >= 10 +| SORT + esql.event.count DESC ''' From 662e8f803746b45c718eda5b2c509c63ba531c82 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:30:36 -0400 Subject: [PATCH 37/94] adjusted Potential Azure OpenAI Model Theft --- .../azure_openai_model_theft_detection.toml | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml b/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml index b1bb72f1ec6..f707fe9f09f 100644 --- a/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2025/02/25" maturity = "production" -updated_date = "2025/04/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -73,11 +73,27 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-azure_openai.logs-* -| where azure.open_ai.operation_name == "ListKey" and azure.open_ai.category == "Audit" -| KEEP @timestamp, azure.open_ai.operation_name , azure.open_ai.category, azure.resource.group, azure.resource.name, azure.open_ai.properties.response_length -| stats count = count(), max_data_transferred = max(azure.open_ai.properties.response_length) by azure.resource.group , azure.resource.name -| where count >= 100 or max_data_transferred >= 1000000 -| sort count desc +FROM logs-azure_openai.logs-* +| WHERE + azure.open_ai.operation_name == "ListKey" AND + azure.open_ai.category == "Audit" +| KEEP + @timestamp, + azure.open_ai.operation_name, + azure.open_ai.category, + azure.resource.group, + azure.resource.name, + azure.open_ai.properties.response_length +| STATS + esql.event.count = COUNT(*), + esql.azure.open_ai.properties.response_length.max = MAX(azure.open_ai.properties.response_length) + BY + azure.resource.group, + azure.resource.name +| WHERE + esql.event.count >= 100 OR + esql.azure.open_ai.properties.response_length.max >= 1000000 +| SORT + esql.event.count DESC ''' From 215526714585fe7dd3a164f26cec6f74a24fa84b Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:32:54 -0400 Subject: [PATCH 38/94] adjusted M365 OneDrive Excessive File Downloads with OAuth Token --- ...ion_onedrive_excessive_file_downloads.toml | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml b/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml index 6976ee1c19a..f7ab0e89f0b 100644 --- a/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml +++ b/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml @@ -2,7 +2,7 @@ creation_date = "2025/02/19" integration = ["o365"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -81,26 +81,29 @@ type = "esql" query = ''' FROM logs-o365.audit-* -| WHERE @timestamp > now() - 14 day | WHERE - event.dataset == "o365.audit" and - - // filter on files downloaded from OneDrive - event.provider == "OneDrive" and - event.action == "FileDownloaded" and - - // filter on OAuth authentication which encompasses device code workflow - o365.audit.AuthenticationType == "OAuth" - and event.outcome == "success" -// bucket authentication attempts by 1 minute -| EVAL target_time_window = DATE_TRUNC(1 minutes, @timestamp) -| KEEP target_time_window, o365.audit.UserId, file.name, source.ip - -// aggregate on unique file names and download attempts -| STATS unique_file_count = count_distinct(file.name), download_attempt_count = count(*) BY target_time_window, o365.audit.UserId, source.ip - -// adjustable range for "excessive" unique files that were downloaded -| WHERE unique_file_count >= 25 + @timestamp > NOW() - 14d AND + event.dataset == "o365.audit" AND + event.provider == "OneDrive" AND + event.action == "FileDownloaded" AND + o365.audit.AuthenticationType == "OAuth" AND + event.outcome == "success" +| EVAL + esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) +| KEEP + esql.time_window.date_trunc, + o365.audit.UserId, + file.name, + source.ip +| STATS + esql.file.name.count_distinct = COUNT_DISTINCT(file.name), + esql.event.count = COUNT(*) + BY + esql.time_window.date_trunc, + o365.audit.UserId, + source.ip +| WHERE + esql.file.name.count_distinct >= 25 ''' From 9f29facc98d528cf46907da9156d3ee6e174d742 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:35:35 -0400 Subject: [PATCH 39/94] adjusted Multiple Microsoft 365 User Account Lockouts in Short Time Window --- ...rosoft_365_excessive_account_lockouts.toml | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml index b2cb4e27195..4838a827e5d 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml @@ -2,7 +2,7 @@ creation_date = "2025/05/10" integration = ["o365"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -73,56 +73,54 @@ type = "esql" query = ''' FROM logs-o365.audit-* - | MV_EXPAND event.category | EVAL - time_window = DATE_TRUNC(5 minutes, @timestamp), - user_id = TO_LOWER(o365.audit.UserId), - ip = source.ip, - login_error = o365.audit.LogonError, - request_type = TO_LOWER(o365.audit.ExtendedProperties.RequestType), - asn_org = source.`as`.organization.name, - country = source.geo.country_name, - event_time = @timestamp - -| WHERE event.dataset == "o365.audit" - AND event.category == "authentication" - AND event.provider IN ("AzureActiveDirectory", "Exchange") - AND event.action IN ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") - AND request_type RLIKE "(oauth.*||.*login.*)" - AND login_error == "IdsLocked" - AND user_id != "not available" - AND o365.audit.Target.Type IN ("0", "2", "6", "10") - AND asn_org != "MICROSOFT-CORP-MSN-AS-BLOCK" - + esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp) +| WHERE + event.dataset == "o365.audit" AND + event.category == "authentication" AND + event.provider IN ("AzureActiveDirectory", "Exchange") AND + event.action IN ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") AND + TO_LOWER(o365.audit.ExtendedProperties.RequestType) RLIKE "(oauth.*||.*login.*)" AND + o365.audit.LogonError == "IdsLocked" AND + TO_LOWER(o365.audit.UserId) != "not available" AND + o365.audit.Target.Type IN ("0", "2", "6", "10") AND + source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" | STATS - unique_users = COUNT_DISTINCT(user_id), - user_id_list = VALUES(user_id), - ip_list = VALUES(ip), - unique_ips = COUNT_DISTINCT(ip), - source_orgs = VALUES(asn_org), - countries = VALUES(country), - unique_country_count = COUNT_DISTINCT(country), - unique_asn_orgs = COUNT_DISTINCT(asn_org), - request_types = VALUES(request_type), - first_seen = MIN(event_time), - last_seen = MAX(event_time), - total_lockout_responses = COUNT() - BY time_window - + esql.o365.audit.UserId.count_distinct = COUNT_DISTINCT(TO_LOWER(o365.audit.UserId)), + esql.o365.audit.UserId.values = VALUES(TO_LOWER(o365.audit.UserId)), + esql.source.ip.values = VALUES(source.ip), + esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + esql.source.geo.country_name.values = VALUES(source.geo.country_name), + esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(TO_LOWER(o365.audit.ExtendedProperties.RequestType)), + esql.event.first_seen = MIN(@timestamp), + esql.event.last_seen = MAX(@timestamp), + esql.event.count = COUNT(*) + BY esql.time_window.date_trunc | EVAL - duration_seconds = DATE_DIFF("seconds", first_seen, last_seen) - + esql.event.duration.seconds = DATE_DIFF("seconds", esql.event.first_seen, esql.event.last_seen) | KEEP - time_window, unique_users, user_id_list, ip_list, - unique_ips, source_orgs, countries, unique_country_count, - unique_asn_orgs, request_types, first_seen, last_seen, - total_lockout_responses, duration_seconds - + esql.time_window.date_trunc, + esql.o365.audit.UserId.count_distinct, + esql.o365.audit.UserId.values, + esql.source.ip.values, + esql.source.ip.count_distinct, + esql.source.`as`.organization.name.values, + esql.source.`as`.organization.name.count_distinct, + esql.source.geo.country_name.values, + esql.source.geo.country_name.count_distinct, + esql.o365.audit.ExtendedProperties.RequestType.values, + esql.event.first_seen, + esql.event.last_seen, + esql.event.count, + esql.event.duration.seconds | WHERE - unique_users >= 10 AND - total_lockout_responses >= 10 AND - duration_seconds <= 300 + esql.o365.audit.UserId.count_distinct >= 10 AND + esql.event.count >= 10 AND + esql.event.duration.seconds <= 300 ''' From 595ba2d0be532efd0924d054df029b9ba38ba5e9 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:43:49 -0400 Subject: [PATCH 40/94] adjusted Potential Microsoft 365 User Account Brute Force --- ...65_potential_user_account_brute_force.toml | 117 ++++++++++-------- 1 file changed, 63 insertions(+), 54 deletions(-) diff --git a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml index bb2a93206f3..619a90d4875 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml @@ -2,7 +2,7 @@ creation_date = "2020/11/30" integration = ["o365"] maturity = "production" -updated_date = "2025/07/02" +updated_date = "2025/07/16" [rule] author = ["Elastic", "Willem D'Haese", "Austin Songer"] @@ -79,66 +79,75 @@ type = "esql" query = ''' FROM logs-o365.audit-* - | MV_EXPAND event.category | EVAL - time_window = DATE_TRUNC(5 minutes, @timestamp), - user_id = TO_LOWER(o365.audit.UserId), - ip = source.ip, - login_error = o365.audit.LogonError, - request_type = TO_LOWER(o365.audit.ExtendedProperties.RequestType), - asn_org = source.`as`.organization.name, - country = source.geo.country_name, - event_time = @timestamp - -| WHERE event.dataset == "o365.audit" - AND event.category == "authentication" - AND event.provider IN ("AzureActiveDirectory", "Exchange") - AND event.action IN ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") - AND request_type RLIKE "(oauth.*||.*login.*)" - AND login_error != "IdsLocked" - AND login_error NOT IN ( - "EntitlementGrantsNotFound", "UserStrongAuthEnrollmentRequired", "UserStrongAuthClientAuthNRequired", - "InvalidReplyTo", "SsoArtifactExpiredDueToConditionalAccess", "PasswordResetRegistrationRequiredInterrupt", - "SsoUserAccountNotFoundInResourceTenant", "UserStrongAuthExpired", "CmsiInterrupt" - ) - AND user_id != "not available" - AND o365.audit.Target.Type IN ("0", "2", "6", "10") - + esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp), + esql.o365.audit.UserId.lower = TO_LOWER(o365.audit.UserId), + esql.o365.audit.LogonError = o365.audit.LogonError, + esql.o365.audit.ExtendedProperties.RequestType.lower = TO_LOWER(o365.audit.ExtendedProperties.RequestType) +| WHERE + event.dataset == "o365.audit" AND + event.category == "authentication" AND + event.provider IN ("AzureActiveDirectory", "Exchange") AND + event.action IN ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") AND + esql.o365.audit.ExtendedProperties.RequestType.lower RLIKE "(oauth.*||.*login.*)" AND + esql.o365.audit.LogonError != "IdsLocked" AND + esql.o365.audit.LogonError NOT IN ( + "EntitlementGrantsNotFound", + "UserStrongAuthEnrollmentRequired", + "UserStrongAuthClientAuthNRequired", + "InvalidReplyTo", + "SsoArtifactExpiredDueToConditionalAccess", + "PasswordResetRegistrationRequiredInterrupt", + "SsoUserAccountNotFoundInResourceTenant", + "UserStrongAuthExpired", + "CmsiInterrupt" + ) AND + esql.o365.audit.UserId.lower != "not available" AND + o365.audit.Target.Type IN ("0", "2", "6", "10") | STATS - unique_users = COUNT_DISTINCT(user_id), - user_id_list = VALUES(user_id), - login_errors = VALUES(login_error), - unique_login_errors = COUNT_DISTINCT(login_error), - request_types = VALUES(request_type), - ip_list = VALUES(ip), - unique_ips = COUNT_DISTINCT(ip), - source_orgs = VALUES(asn_org), - countries = VALUES(country), - unique_country_count = COUNT_DISTINCT(country), - unique_asn_orgs = COUNT_DISTINCT(asn_org), - first_seen = MIN(event_time), - last_seen = MAX(event_time), - total_attempts = COUNT() - BY time_window - + esql.user.id.count_distinct = COUNT_DISTINCT(esql.o365.audit.UserId.lower), + esql.o365.audit.UserId.lower.values = VALUES(esql.o365.audit.UserId.lower), + esql.o365.audit.LogonError.values = VALUES(esql.o365.audit.LogonError), + esql.o365.audit.LogonError.count_distinct = COUNT_DISTINCT(esql.o365.audit.LogonError), + esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(esql.o365.audit.ExtendedProperties.RequestType.lower), + esql.source.ip.values = VALUES(source.ip), + esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + esql.source.geo.country_name.values = VALUES(source.geo.country_name), + esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + esql.event.first_seen = MIN(@timestamp), + esql.event.last_seen = MAX(@timestamp), + esql.event.count = COUNT(*) + BY esql.time_window.date_trunc | EVAL - duration_seconds = DATE_DIFF("seconds", first_seen, last_seen), - bf_type = CASE( - unique_users >= 15 AND unique_login_errors == 1 AND total_attempts >= 10 AND duration_seconds <= 1800, "password_spraying", - unique_users >= 8 AND total_attempts >= 15 AND unique_login_errors <= 3 AND unique_ips <= 5 AND duration_seconds <= 600, "credential_stuffing", - unique_users == 1 AND unique_login_errors == 1 AND total_attempts >= 20 AND duration_seconds <= 300, "password_guessing", + esql.event.duration.seconds = DATE_DIFF("seconds", esql.event.first_seen, esql.event.last_seen), + esql.brute_force.type = CASE( + esql.user.id.count_distinct >= 15 AND esql.o365.audit.LogonError.count_distinct == 1 AND esql.event.count >= 10 AND esql.event.duration.seconds <= 1800, "password_spraying", + esql.user.id.count_distinct >= 8 AND esql.event.count >= 15 AND esql.o365.audit.LogonError.count_distinct <= 3 AND esql.source.ip.count_distinct <= 5 AND esql.event.duration.seconds <= 600, "credential_stuffing", + esql.user.id.count_distinct == 1 AND esql.o365.audit.LogonError.count_distinct == 1 AND esql.event.count >= 20 AND esql.event.duration.seconds <= 300, "password_guessing", "other" ) - | KEEP - time_window, unique_users, user_id_list, login_errors, unique_login_errors, - request_types, ip_list, unique_ips, source_orgs, countries, - unique_country_count, unique_asn_orgs, first_seen, last_seen, - duration_seconds, total_attempts, bf_type - -| WHERE - bf_type != "other" + esql.time_window.date_trunc, + esql.user.id.count_distinct, + esql.o365.audit.UserId.lower.values, + esql.o365.audit.LogonError.values, + esql.o365.audit.LogonError.count_distinct, + esql.o365.audit.ExtendedProperties.RequestType.values, + esql.source.ip.values, + esql.source.ip.count_distinct, + esql.source.`as`.organization.name.values, + esql.source.geo.country_name.values, + esql.source.geo.country_name.count_distinct, + esql.source.`as`.organization.name.count_distinct, + esql.event.first_seen, + esql.event.last_seen, + esql.event.duration.seconds, + esql.event.count, + esql.brute_force.type +| WHERE esql.brute_force.type != "other" ''' From c3071cf5de3e741690f53f0a2bf2aee773759ef4 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:46:49 -0400 Subject: [PATCH 41/94] adjusted Suspicious Microsoft 365 UserLoggedIn via OAuth Code --- ...crosoft_365_susp_oauth2_authorization.toml | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml index a77b4e56c2e..25c2130f775 100644 --- a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml +++ b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml @@ -2,7 +2,7 @@ creation_date = "2025/05/01" integration = ["o365"] maturity = "production" -updated_date = "2025/07/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -68,44 +68,46 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-o365.audit-* -| WHERE event.dataset == "o365.audit" and event.action == "UserLoggedIn" and - - // ensure source, application and user are not null - source.ip is not null and - o365.audit.UserId is not null and - o365.audit.ApplicationId is not null and - - // filter for user principals that are not service accounts - o365.audit.UserType in ("0", "2", "3", "10") and - - // filter for successful logon to Microsoft Graph and from the Microsoft Authentication Broker or Visual Studio Code - o365.audit.ApplicationId in ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") and - o365.audit.ObjectId in ("00000003-0000-0000-c000-000000000000") - -// keep relevant fields only -| keep @timestamp, o365.audit.UserId, source.ip, o365.audit.ApplicationId, o365.audit.ObjectId, o365.audit.ExtendedProperties.RequestType, source.as.organization.name, o365.audit.ExtendedProperties.ResultStatusDetail - -// case statements to track which are OAuth2 authorization request via redirect and which are related to OAuth2 code to token conversion -| eval - oauth_authorize = case(o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" and o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", o365.audit.UserId, null), - oauth_token = case(o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", o365.audit.UserId, null) - -// split time to 30 minutes intervals -| eval target_time_window = DATE_TRUNC(30 minutes, @timestamp) - -// aggregate by principal, applicationId, objectId and time window -| stats - unique_ips = COUNT_DISTINCT(source.ip), - source_ips = VALUES(source.ip), - appIds = VALUES(o365.audit.ApplicationId), - asn = values(`source.as.organization.name`), - is_oauth_token = COUNT_DISTINCT(oauth_token), - is_oauth_authorize = COUNT_DISTINCT(oauth_authorize) -by o365.audit.UserId, target_time_window, o365.audit.ApplicationId, o365.audit.ObjectId - -// filter for cases where the same appId is used by the same principal user to access the same object and from multiple addresses via OAuth2 token -| where unique_ips >= 2 and is_oauth_authorize > 0 and is_oauth_token > 0 +FROM logs-o365.audit-* +| WHERE + event.dataset == "o365.audit" AND + event.action == "UserLoggedIn" AND + source.ip IS NOT NULL AND + o365.audit.UserId IS NOT NULL AND + o365.audit.ApplicationId IS NOT NULL AND + o365.audit.UserType IN ("0", "2", "3", "10") AND + o365.audit.ApplicationId IN ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") AND + o365.audit.ObjectId IN ("00000003-0000-0000-c000-000000000000") +| EVAL + esql.o365.audit.ExtendedProperties.RequestType = o365.audit.ExtendedProperties.RequestType, + esql.o365.audit.ExtendedProperties.ResultStatusDetail = o365.audit.ExtendedProperties.ResultStatusDetail, + esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), + esql.oauth.authorize.user_id.case = CASE( + esql.o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" AND esql.o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", + o365.audit.UserId, + null + ), + esql.oauth.token.user_id.case = CASE( + esql.o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", + o365.audit.UserId, + null + ) +| STATS + esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + esql.source.ip.values = VALUES(source.ip), + esql.o365.audit.ApplicationId.values = VALUES(o365.audit.ApplicationId), + esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + esql.oauth.token.count_distinct = COUNT_DISTINCT(esql.oauth.token.user_id.case), + esql.oauth.authorize.count_distinct = COUNT_DISTINCT(esql.oauth.authorize.user_id.case) + BY + o365.audit.UserId, + esql.time_window.date_trunc, + o365.audit.ApplicationId, + o365.audit.ObjectId +| WHERE + esql.source.ip.count_distinct >= 2 AND + esql.oauth.token.count_distinct > 0 AND + esql.oauth.authorize.count_distinct > 0 ''' From 7007e9dc5eed63f667653fa1cc04fa52dc49b637 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:54:02 -0400 Subject: [PATCH 42/94] adjusted Multiple Device Token Hashes for Single Okta Session --- ..._token_hashes_for_single_okta_session.toml | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml b/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml index f1109e5cde7..7cde6e2964d 100644 --- a/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml +++ b/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml @@ -2,7 +2,7 @@ creation_date = "2023/11/08" integration = ["okta"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -79,28 +79,29 @@ type = "esql" query = ''' FROM logs-okta* | WHERE - event.dataset == "okta.system" - // ignore authentication events where session and device token hash change often - AND NOT event.action IN ( + event.dataset == "okta.system" AND + NOT event.action IN ( "policy.evaluate_sign_on", "user.session.start", "user.authentication.sso" - ) - // ignore Okta system events and only allow registered users - AND ( - okta.actor.alternate_id != "system@okta.com" - AND okta.actor.alternate_id RLIKE "[^@\\s]+\\@[^@\\s]+" - ) - AND okta.authentication_context.external_session_id != "unknown" -| KEEP event.action, okta.actor.alternate_id, okta.authentication_context.external_session_id, okta.debug_context.debug_data.dt_hash + ) AND + okta.actor.alternate_id != "system@okta.com" AND + okta.actor.alternate_id RLIKE "[^@\\s]+\\@[^@\\s]+" AND + okta.authentication_context.external_session_id != "unknown" +| KEEP + event.action, + okta.actor.alternate_id, + okta.authentication_context.external_session_id, + okta.debug_context.debug_data.dt_hash | STATS - dt_hash_counts = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) BY - okta.actor.alternate_id, - okta.authentication_context.external_session_id + esql.okta.debug_context.debug_data.dt_hash.count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) + BY + okta.actor.alternate_id, + okta.authentication_context.external_session_id | WHERE - dt_hash_counts >= 2 + esql.okta.debug_context.debug_data.dt_hash.count_distinct >= 2 | SORT - dt_hash_counts DESC + esql.okta.debug_context.debug_data.dt_hash.count_distinct DESC ''' From 0beadb80f189c4f7b2fd47673280f7e1e4f4ca6c Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:55:48 -0400 Subject: [PATCH 43/94] adjusted Multiple Okta User Authentication Events with Client Address --- ...for_multiple_users_from_single_source.toml | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml index dd686b8570f..8c5ea8cd1ed 100644 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml +++ b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml @@ -2,7 +2,7 @@ creation_date = "2024/06/17" integration = ["okta"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -92,17 +92,24 @@ type = "esql" query = ''' FROM logs-okta* | WHERE - event.dataset == "okta.system" - AND (event.action == "user.session.start" OR event.action RLIKE "user\\.authentication(.*)") - AND okta.outcome.reason == "INVALID_CREDENTIALS" -| KEEP okta.client.ip, okta.actor.alternate_id, okta.actor.id, event.action, okta.outcome.reason + event.dataset == "okta.system" AND + (event.action == "user.session.start" OR event.action RLIKE "user\\.authentication(.*)") AND + okta.outcome.reason == "INVALID_CREDENTIALS" +| KEEP + okta.client.ip, + okta.actor.alternate_id, + okta.actor.id, + event.action, + okta.outcome.reason | STATS - source_auth_count = COUNT_DISTINCT(okta.actor.id) - BY okta.client.ip, okta.actor.alternate_id + esql.okta.actor.id.count_distinct = COUNT_DISTINCT(okta.actor.id) + BY + okta.client.ip, + okta.actor.alternate_id | WHERE - source_auth_count > 5 + esql.okta.actor.id.count_distinct > 5 | SORT - source_auth_count DESC + esql.okta.actor.id.count_distinct DESC ''' From 0306f2736d609da3e83bf8928966e7bcc2399080 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:57:34 -0400 Subject: [PATCH 44/94] adjusted Multiple Okta User Authentication Events with Same Device Token Hash --- ...users_with_the_same_device_token_hash.toml | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml index 799578c7650..4a6f16c90f5 100644 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml +++ b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml @@ -2,7 +2,7 @@ creation_date = "2024/06/17" integration = ["okta"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -89,18 +89,25 @@ type = "esql" query = ''' FROM logs-okta* | WHERE - event.dataset == "okta.system" - AND (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") - AND okta.debug_context.debug_data.dt_hash != "-" - AND okta.outcome.reason == "INVALID_CREDENTIALS" -| KEEP event.action, okta.debug_context.debug_data.dt_hash, okta.actor.id, okta.actor.alternate_id, okta.outcome.reason + event.dataset == "okta.system" AND + (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") AND + okta.debug_context.debug_data.dt_hash != "-" AND + okta.outcome.reason == "INVALID_CREDENTIALS" +| KEEP + event.action, + okta.debug_context.debug_data.dt_hash, + okta.actor.id, + okta.actor.alternate_id, + okta.outcome.reason | STATS - target_auth_count = COUNT_DISTINCT(okta.actor.id) - BY okta.debug_context.debug_data.dt_hash, okta.actor.alternate_id + esql.okta.actor.id.count_distinct = COUNT_DISTINCT(okta.actor.id) + BY + okta.debug_context.debug_data.dt_hash, + okta.actor.alternate_id | WHERE - target_auth_count > 20 + esql.okta.actor.id.count_distinct > 20 | SORT - target_auth_count DESC + esql.okta.actor.id.count_distinct DESC ''' From 728282ee53463cee821e73fd2ed9ad16a385e38b Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 11:58:58 -0400 Subject: [PATCH 45/94] adjusted High Number of Okta Device Token Cookies Generated for Authentication --- ...e_device_token_hashes_for_single_user.toml | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml index a5f018981af..bcf26c4cb37 100644 --- a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml +++ b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml @@ -2,7 +2,7 @@ creation_date = "2024/06/17" integration = ["okta"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -93,18 +93,26 @@ type = "esql" query = ''' FROM logs-okta* | WHERE - event.dataset == "okta.system" - AND (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") - AND okta.debug_context.debug_data.request_uri == "/api/v1/authn" - AND okta.outcome.reason == "INVALID_CREDENTIALS" -| KEEP event.action, okta.debug_context.debug_data.dt_hash, okta.client.ip, okta.actor.alternate_id, okta.debug_context.debug_data.request_uri, okta.outcome.reason + event.dataset == "okta.system" AND + (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") AND + okta.debug_context.debug_data.request_uri == "/api/v1/authn" AND + okta.outcome.reason == "INVALID_CREDENTIALS" +| KEEP + event.action, + okta.debug_context.debug_data.dt_hash, + okta.client.ip, + okta.actor.alternate_id, + okta.debug_context.debug_data.request_uri, + okta.outcome.reason | STATS - source_auth_count = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) - BY okta.client.ip, okta.actor.alternate_id + esql.okta.debug_context.debug_data.dt_hash.count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) + BY + okta.client.ip, + okta.actor.alternate_id | WHERE - source_auth_count >= 30 + esql.okta.debug_context.debug_data.dt_hash.count_distinct >= 30 | SORT - source_auth_count DESC + esql.okta.debug_context.debug_data.dt_hash.count_distinct DESC ''' From 08e6faa84a4e52b7769af6fdded9b900ce6e448b Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:01:17 -0400 Subject: [PATCH 46/94] adjusted Okta User Sessions Started from Different Geolocations --- ...s_started_from_different_geolocations.toml | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml b/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml index d4487daff7c..ac73641bcd7 100644 --- a/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml +++ b/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml @@ -2,7 +2,7 @@ creation_date = "2023/11/18" integration = ["okta"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -80,16 +80,25 @@ type = "esql" query = ''' FROM logs-okta* | WHERE - event.dataset == "okta.system" - AND (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") - AND okta.security_context.is_proxy != true and okta.actor.id != "unknown" - AND event.outcome == "success" -| KEEP event.action, okta.security_context.is_proxy, okta.actor.id, event.outcome, client.geo.country_name, okta.actor.alternate_id + event.dataset == "okta.system" AND + (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") AND + okta.security_context.is_proxy != true AND + okta.actor.id != "unknown" AND + event.outcome == "success" +| KEEP + event.action, + okta.security_context.is_proxy, + okta.actor.id, + okta.actor.alternate_id, + event.outcome, + client.geo.country_name | STATS - geo_auth_counts = COUNT_DISTINCT(client.geo.country_name) + esql.client.geo.country_name.count_distinct = COUNT_DISTINCT(client.geo.country_name) BY okta.actor.id, okta.actor.alternate_id | WHERE - geo_auth_counts >= 2 + esql.client.geo.country_name.count_distinct >= 2 +| SORT + esql.client.geo.country_name.count_distinct DESC ''' From f714d8318b176228a117607e344669190a039774 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:04:06 -0400 Subject: [PATCH 47/94] adjusted High Number of Egress Network Connections from Unusual Executable --- ...ent_egress_netcon_from_sus_executable.toml | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml b/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml index c62b74db0fa..6c0eb3952c3 100644 --- a/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml +++ b/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml @@ -2,7 +2,7 @@ creation_date = "2025/02/20" integration = ["endpoint"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -93,32 +93,52 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.network-* -| keep @timestamp, host.os.type, event.type, event.action, process.name, process.executable, destination.ip, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "start" and event.action == "connection_attempted" and ( - ( - process.executable like "/tmp/*" or - process.executable like "/var/tmp/*" or - process.executable like "/dev/shm/*" - ) or - (process.name like ".*") -) and not ( - CIDR_MATCH( - destination.ip, "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32", - "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", - "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24","198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4", "::1", - "FE80::/10", "FF00::/8" - ) or - process.executable like "/nix/store/*" or - process.executable like "/tmp/newroot/*" or - process.executable like "/tmp/.mount*" or - process.executable like "/tmp/go-build*" - ) -| stats cc = count(), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.executable -| where agent_count == 1 and cc > 15 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.network-* +| WHERE + @timestamp > NOW() - 1h AND + host.os.type == "linux" AND + event.type == "start" AND + event.action == "connection_attempted" AND + ( + process.executable LIKE "/tmp/*" OR + process.executable LIKE "/var/tmp/*" OR + process.executable LIKE "/dev/shm/*" OR + process.name RLIKE ".*" + ) AND NOT ( + CIDR_MATCH(destination.ip, + "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", + "192.0.0.0/24", "192.0.0.29/32", "192.0.0.8/32", "192.0.0.9/32", + "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", + "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", + "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24", "198.18.0.0/15", + "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1", "FE80::/10", "FF00::/8" + ) OR + process.executable LIKE "/nix/store/*" OR + process.executable LIKE "/tmp/newroot/*" OR + process.executable LIKE "/tmp/.mount*" OR + process.executable LIKE "/tmp/go-build*" + ) +| KEEP + @timestamp, + host.os.type, + event.type, + event.action, + process.name, + process.executable, + destination.ip, + agent.id, + host.name +| STATS + esql.event.count = COUNT(), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + esql.host.name.values = VALUES(host.name), + esql.agent.id.values = VALUES(agent.id) + BY process.executable +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.event.count > 15 +| SORT esql.event.count ASC +| LIMIT 100 ''' From 21754e37901a02e83fbbfb8859e48148e2521fc5 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:05:47 -0400 Subject: [PATCH 48/94] adjusted Unusual Base64 Encoding/Decoding Activity --- ...ense_evasion_base64_decoding_activity.toml | 78 ++++++++++++++----- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/rules/linux/defense_evasion_base64_decoding_activity.toml b/rules/linux/defense_evasion_base64_decoding_activity.toml index 074373a3404..8273c1287a7 100644 --- a/rules/linux/defense_evasion_base64_decoding_activity.toml +++ b/rules/linux/defense_evasion_base64_decoding_activity.toml @@ -2,7 +2,7 @@ creation_date = "2025/02/21" integration = ["endpoint"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -94,23 +94,65 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.process-* -| keep @timestamp, host.os.type, event.type, event.action, process.name, process.args, process.command_line, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "start" and event.action == "exec" and ( - (process.name in ("base64", "base64plain", "base64url", "base64mime", "base64pem", "base32", "base16") and process.command_line like "*-*d*") or - (process.name == "openssl" and process.args == "enc" and process.args in ("-d", "-base64", "-a")) or - (process.name like "python*" and - (process.args == "base64" and process.args in ("-d", "-u", "-t")) or - (process.args == "-c" and process.command_line like "*base64*" and process.command_line like "*b64decode*") - ) or - (process.name like "perl*" and process.command_line like "*decode_base64*") or - (process.name like "ruby*" and process.args == "-e" and process.command_line like "*Base64.decode64*") -) -| stats cc = count(), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.name, process.command_line -| where agent_count == 1 and cc < 15 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.process-* +| WHERE + @timestamp > NOW() - 1h AND + host.os.type == "linux" AND + event.type == "start" AND + event.action == "exec" AND ( + ( + process.name IN ("base64", "base64plain", "base64url", "base64mime", "base64pem", "base32", "base16") AND + process.command_line LIKE "*-*d*" + ) OR + ( + process.name == "openssl" AND + process.args == "enc" AND + process.args IN ("-d", "-base64", "-a") + ) OR + ( + process.name RLIKE "^python.*" AND ( + ( + process.args == "base64" AND + process.args IN ("-d", "-u", "-t") + ) OR + ( + process.args == "-c" AND + process.command_line LIKE "*base64*" AND + process.command_line LIKE "*b64decode*" + ) + ) + ) OR + ( + process.name RLIKE "^perl.*" AND + process.command_line LIKE "*decode_base64*" + ) OR + ( + process.name RLIKE "^ruby.*" AND + process.args == "-e" AND + process.command_line LIKE "*Base64.decode64*" + ) + ) +| KEEP + @timestamp, + host.os.type, + event.type, + event.action, + process.name, + process.args, + process.command_line, + agent.id, + host.name +| STATS + esql.event.count = COUNT(), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + esql.host.name.values = VALUES(host.name), + esql.agent.id.values = VALUES(agent.id) + BY process.name, process.command_line +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.event.count < 15 +| SORT esql.event.count ASC +| LIMIT 100 ''' From 59a65335508589147f60e3c063e4441980578f44 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:06:56 -0400 Subject: [PATCH 49/94] adjusted Potential Port Scanning Activity from Compromised Host --- ...anning_activity_from_compromised_host.toml | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml index b31aa56d377..0670600c69d 100644 --- a/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml @@ -2,7 +2,7 @@ creation_date = "2025/03/04" integration = ["endpoint"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -95,14 +95,34 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.network-* -| keep @timestamp, host.os.type, event.type, event.action, destination.port, process.executable, destination.ip, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "start" and event.action == "connection_attempted" -| stats cc = count(), port_count = count_distinct(destination.port), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.executable, destination.ip -| where agent_count == 1 and port_count > 100 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.network-* +| WHERE + @timestamp > NOW() - 1h AND + host.os.type == "linux" AND + event.type == "start" AND + event.action == "connection_attempted" +| KEEP + @timestamp, + host.os.type, + event.type, + event.action, + destination.port, + process.executable, + destination.ip, + agent.id, + host.name +| STATS + esql.event.count = COUNT(), + esql.destination.port.count_distinct = COUNT_DISTINCT(destination.port), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + esql.host.name.values = VALUES(host.name), + esql.agent.id.values = VALUES(agent.id) + BY process.executable, destination.ip +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.destination.port.count_distinct > 100 +| SORT esql.event.count ASC +| LIMIT 100 ''' From a6b62db26db79d1462f6b8fff775b7b317d59d77 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:10:00 -0400 Subject: [PATCH 50/94] adjusted Potential Subnet Scanning Activity from Compromised Host --- ...anning_activity_from_compromised_host.toml | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml index 4e5da31b19f..89fec2254cf 100644 --- a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml @@ -2,7 +2,7 @@ creation_date = "2025/03/04" integration = ["endpoint"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -94,14 +94,25 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.network-* -| keep @timestamp, host.os.type, event.type, event.action, process.executable, destination.ip, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "start" and event.action == "connection_attempted" -| stats cc = count(), dest_count = count_distinct(destination.ip), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.executable -| where agent_count == 1 and dest_count > 250 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.network-* +| KEEP @timestamp, host.os.type, event.type, event.action, process.executable, destination.ip, agent.id, host.name +| WHERE + @timestamp > NOW() - 1 HOURS AND + host.os.type == "linux" AND + event.type == "start" AND + event.action == "connection_attempted" +| STATS + esql.connection.count = COUNT(), + esql.destination.ip.count_distinct = COUNT_DISTINCT(destination.ip), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + host.name.values = VALUES(host.name), + agent.id.values = VALUES(agent.id) + BY process.executable +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.destination.ip.count_distinct > 250 +| SORT esql.connection.count ASC +| LIMIT 100 ''' From 136152fc337a54bb533cf7f9da0f9fc562513ea6 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:11:15 -0400 Subject: [PATCH 51/94] adjusted Unusual File Transfer Utility Launched --- ...nusual_file_transfer_utility_launched.toml | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml index 0ec94a90c4c..c96db12650d 100644 --- a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml +++ b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml @@ -2,7 +2,7 @@ creation_date = "2025/02/21" integration = ["endpoint"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -93,15 +93,25 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.process-* -| keep @timestamp, host.os.type, event.type, event.action, process.name, process.executable, process.parent.executable, process.command_line, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "start" and event.action == "exec" and - process.name in ("scp", "ftp", "sftp", "vsftpd", "sftp-server", "rsync") -| stats cc = count(), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.executable, process.parent.executable, process.command_line -| where agent_count == 1 and cc < 5 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.process-* +| KEEP @timestamp, host.os.type, event.type, event.action, process.name, process.executable, process.parent.executable, process.command_line, agent.id, host.name +| WHERE + @timestamp > NOW() - 1 HOURS AND + host.os.type == "linux" AND + event.type == "start" AND + event.action == "exec" AND + process.name IN ("scp", "ftp", "sftp", "vsftpd", "sftp-server", "rsync") +| STATS + esql.event.count = COUNT(), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + host.name.values = VALUES(host.name), + agent.id.values = VALUES(agent.id) + BY process.executable, process.parent.executable, process.command_line +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.event.count < 5 +| SORT esql.event.count ASC +| LIMIT 100 ''' From 9003afeeeab16078de40a93e8d39c8b420c3a4fe Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:12:50 -0400 Subject: [PATCH 52/94] adjusted Potential Malware-Driven SSH Brute Force Attempt --- ...otential_bruteforce_malware_infection.toml | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/rules/linux/impact_potential_bruteforce_malware_infection.toml b/rules/linux/impact_potential_bruteforce_malware_infection.toml index 859f717dfa9..7cafb07e37f 100644 --- a/rules/linux/impact_potential_bruteforce_malware_infection.toml +++ b/rules/linux/impact_potential_bruteforce_malware_infection.toml @@ -2,7 +2,7 @@ creation_date = "2025/02/20" integration = ["endpoint"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -97,21 +97,34 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.network-* -| keep @timestamp, host.os.type, event.type, event.action, destination.port, process.executable, destination.ip, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "start" and event.action == "connection_attempted" and - destination.port in (22, 222, 2222, 10022, 2022, 2200, 62612, 8022) and not - CIDR_MATCH( - destination.ip, "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32", - "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", - "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24","198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4", "::1", - "FE80::/10", "FF00::/8" - ) -| stats cc = count(), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.executable, destination.port -| where agent_count == 1 and cc > 15 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.network-* +| KEEP @timestamp, host.os.type, event.type, event.action, destination.port, process.executable, destination.ip, agent.id, host.name +| WHERE + @timestamp > NOW() - 1 HOURS AND + host.os.type == "linux" AND + event.type == "start" AND + event.action == "connection_attempted" AND + destination.port IN (22, 222, 2222, 10022, 2022, 2200, 62612, 8022) AND + NOT CIDR_MATCH( + destination.ip, + "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", + "192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32", + "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", + "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", + "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24", "198.18.0.0/15", + "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1", "FE80::/10", "FF00::/8" + ) +| STATS + esql.event.count = COUNT(), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + host.name.values = VALUES(host.name), + agent.id.values = VALUES(agent.id) + BY process.executable, destination.port +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.event.count > 15 +| SORT esql.event.count ASC +| LIMIT 100 ''' From caf9c631d5c503df495d73db63430ce907f7b745 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:15:12 -0400 Subject: [PATCH 53/94] adjusted Unusual Process Spawned from Web Server Parent --- ...sistence_web_server_sus_child_spawned.toml | 81 ++++++++++++------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/rules/linux/persistence_web_server_sus_child_spawned.toml b/rules/linux/persistence_web_server_sus_child_spawned.toml index 01e65c1d014..6b4e294ae84 100644 --- a/rules/linux/persistence_web_server_sus_child_spawned.toml +++ b/rules/linux/persistence_web_server_sus_child_spawned.toml @@ -2,7 +2,7 @@ creation_date = "2025/03/04" integration = ["endpoint"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -96,34 +96,57 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.process-* -| keep @timestamp, host.os.type, event.type, event.action, process.parent.name, user.name, user.id, process.working_directory, process.name, process.executable, process.command_line, process.parent.executable, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "start" and event.action == "exec" and ( - process.parent.name in ( - "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", - "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", - "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" - ) or - process.parent.name like "php-*" or - process.parent.name like "python*" or - process.parent.name like "ruby*" or - process.parent.name like "perl*" or - user.name in ( - "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" - ) or - user.id in ("99", "33", "498", "48") or - process.working_directory like "/var/www/*" -) and -not ( - process.working_directory like "/home/*" or - process.working_directory like "/" or - process.parent.executable like "/vscode/vscode-server/*" -) -| stats cc = count(), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.executable, process.working_directory, process.parent.executable -| where agent_count == 1 and cc < 5 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.process-* +| KEEP + @timestamp, + host.os.type, + event.type, + event.action, + process.parent.name, + user.name, + user.id, + process.working_directory, + process.name, + process.executable, + process.command_line, + process.parent.executable, + agent.id, + host.name +| WHERE + @timestamp > NOW() - 1 HOURS AND + host.os.type == "linux" AND + event.type == "start" AND + event.action == "exec" AND ( + process.parent.name IN ( + "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", + "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", + "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" + ) OR + process.parent.name LIKE "php-*" OR + process.parent.name LIKE "python*" OR + process.parent.name LIKE "ruby*" OR + process.parent.name LIKE "perl*" OR + user.name IN ( + "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" + ) OR + user.id IN ("99", "33", "498", "48") OR + process.working_directory LIKE "/var/www/*" + ) AND NOT ( + process.working_directory LIKE "/home/*" OR + process.working_directory == "/" OR + process.parent.executable LIKE "/vscode/vscode-server/*" + ) +| STATS + esql.event.count = COUNT(), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + host.name.values = VALUES(host.name), + agent.id.values = VALUES(agent.id) + BY process.executable, process.working_directory, process.parent.executable +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.event.count < 5 +| SORT esql.event.count ASC +| LIMIT 100 ''' From 8838461d3c067905c63d6ab542c658913e2e0d25 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:16:54 -0400 Subject: [PATCH 54/94] adjusted Unusual Command Execution from Web Server Parent --- ...ence_web_server_sus_command_execution.toml | 87 ++++++++++++------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/rules/linux/persistence_web_server_sus_command_execution.toml b/rules/linux/persistence_web_server_sus_command_execution.toml index 2c796ccc7cf..0b881bf66bf 100644 --- a/rules/linux/persistence_web_server_sus_command_execution.toml +++ b/rules/linux/persistence_web_server_sus_command_execution.toml @@ -2,7 +2,7 @@ creation_date = "2025/03/04" integration = ["endpoint"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -103,37 +103,60 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.process-* -| keep @timestamp, host.os.type, event.type, event.action, process.parent.name, user.name, user.id, process.working_directory, process.name, process.command_line, process.parent.executable, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "start" and event.action == "exec" and ( - process.parent.name in ( - "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", - "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", - "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" - ) or - process.parent.name like "php-*" or - process.parent.name like "python*" or - process.parent.name like "ruby*" or - process.parent.name like "perl*" or - user.name in ( - "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" - ) or - user.id in ("99", "33", "498", "48") or - process.working_directory like "/var/www/*" -) and - process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and process.command_line like "* -c *" and - not ( - process.working_directory like "/home/*" or - process.working_directory like "/" or - process.working_directory like "/vscode/vscode-server/*" or - process.parent.executable like "/vscode/vscode-server/*" or - process.parent.executable == "/usr/bin/xfce4-terminal" -) -| stats cc = count(), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.command_line, process.working_directory, process.parent.executable -| where agent_count == 1 and cc < 5 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.process-* +| KEEP + @timestamp, + host.os.type, + event.type, + event.action, + process.parent.name, + user.name, + user.id, + process.working_directory, + process.name, + process.command_line, + process.parent.executable, + agent.id, + host.name +| WHERE + @timestamp > NOW() - 1 HOURS AND + host.os.type == "linux" AND + event.type == "start" AND + event.action == "exec" AND ( + process.parent.name IN ( + "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", + "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", + "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" + ) OR + process.parent.name LIKE "php-*" OR + process.parent.name LIKE "python*" OR + process.parent.name LIKE "ruby*" OR + process.parent.name LIKE "perl*" OR + user.name IN ( + "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" + ) OR + user.id IN ("99", "33", "498", "48") OR + process.working_directory LIKE "/var/www/*" + ) AND + process.name IN ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") AND + process.command_line LIKE "* -c *" AND NOT ( + process.working_directory LIKE "/home/*" OR + process.working_directory == "/" OR + process.working_directory LIKE "/vscode/vscode-server/*" OR + process.parent.executable LIKE "/vscode/vscode-server/*" OR + process.parent.executable == "/usr/bin/xfce4-terminal" + ) +| STATS + esql.event.count = COUNT(), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + host.name.values = VALUES(host.name), + agent.id.values = VALUES(agent.id) + BY process.command_line, process.working_directory, process.parent.executable +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.event.count < 5 +| SORT esql.event.count ASC +| LIMIT 100 ''' From 48d6541c5137ca4474d2530c97a297788d2c7337 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:22:35 -0400 Subject: [PATCH 55/94] adjusted Rare Connection to WebDAV Target --- ...ential_access_rare_webdav_destination.toml | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/rules/windows/credential_access_rare_webdav_destination.toml b/rules/windows/credential_access_rare_webdav_destination.toml index e7bc190e789..2474dcd6457 100644 --- a/rules/windows/credential_access_rare_webdav_destination.toml +++ b/rules/windows/credential_access_rare_webdav_destination.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/28" integration = ["endpoint", "system", "windows", "m365_defender", "crowdstrike"] maturity = "production" -updated_date = "2025/07/02" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -55,14 +55,31 @@ type = "esql" query = ''' FROM logs-* -| where @timestamp > NOW() - 8 hours -| WHERE event.category == "process" and event.type == "start" and process.name == "rundll32.exe" and process.command_line like "*DavSetCookie*" -| keep host.id, process.command_line, user.name -| grok process.command_line """(?DavSetCookie .* http)""" -| eval webdav_target = REPLACE(target, "(DavSetCookie | http)", "") -| where webdav_target is not null and webdav_target rlike """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" and not webdav_target in ("www.google.com@SSL", "www.elastic.co@SSL") and not webdav_target rlike """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" -| stats total = count(*), unique_count_host = count_distinct(host.id), hosts = VALUES(host.id), users = VALUES(user.name) by webdav_target -| where unique_count_host == 1 and total <= 3 +| WHERE + @timestamp > NOW() - 8 HOURS AND + event.category == "process" AND + event.type == "start" AND + process.name == "rundll32.exe" AND + process.command_line LIKE "*DavSetCookie*" +| KEEP host.id, process.command_line, user.name +| GROK + process.command_line """(?DavSetCookie .* http)""" +| EVAL + esql.target.webdav.grok.replace = REPLACE(esql.target.webdav.grok, "(DavSetCookie | http)", "") +| WHERE + esql.target.webdav.grok.replace IS NOT NULL AND + esql.target.webdav.grok.replace RLIKE """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" AND + NOT esql.target.webdav.grok.replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND + NOT esql.target.webdav.grok.replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" +| STATS + esql.total.count = COUNT(*), + esql.host.id.count_distinct = COUNT_DISTINCT(host.id), + host.id.values = VALUES(host.id), + user.name.values = VALUES(user.name) + BY esql.target.webdav.grok.replace +| WHERE + esql.host.id.count_distinct == 1 AND + esql.total.count <= 3 ''' From 6a80d6e155956be5caba340d65b9b0b828e197f0 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:26:52 -0400 Subject: [PATCH 56/94] adjusted Potential PowerShell Obfuscation via Invalid Escape Sequences --- ...nse_evasion_posh_obfuscation_backtick.toml | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml index 307279c76f5..449a3bbae13 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/15" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -90,26 +90,41 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index -| WHERE event.code == "4104" and powershell.file.script_block_text LIKE "*`*" +FROM logs-windows.powershell_operational* METADATA _id, _version, _index +| WHERE + event.code == "4104" AND + powershell.file.script_block_text LIKE "*`*" // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") +| EVAL esql.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) +| EVAL esql.script_block_text.replace.length = LENGTH(esql.script_block_text.replace) - LENGTH(REPLACE(esql.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.name, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count >= 10 +| KEEP + esql.script_block_text.replace.length, + esql.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.name, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +| WHERE esql.script_block_text.replace.length >= 10 // Filter FPs, and due to the behavior of the LIKE operator, allow null values -| WHERE (file.name NOT LIKE "TSS_*.psm1" or file.name IS NULL) +| WHERE (file.name NOT LIKE "TSS_*.psm1" OR file.name IS NULL) -| WHERE - // VSCode Shell integration - NOT powershell.file.script_block_text LIKE "*$([char]0x1b)]633*" +// VSCode Shell integration +| WHERE NOT powershell.file.script_block_text LIKE "*$([char]0x1b)]633*" ''' [[rule.threat]] From f389c429dc5274e554d6658a512a37e5a2ad7743 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:29:03 -0400 Subject: [PATCH 57/94] adjusted Potential PowerShell Obfuscation via Backtick-Escaped Variable Expansion --- ...evasion_posh_obfuscation_backtick_var.toml | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index 0b59c441508..595447ded54 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/16" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -84,23 +84,37 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL script_len = LENGTH(powershell.file.script_block_text) -| WHERE script_len > 500 +| EVAL esql.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE esql.script_block_text.length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") +| EVAL esql.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) +| EVAL esql.script_block_text.replace.length = LENGTH(esql.script_block_text.replace) - LENGTH(REPLACE(esql.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, file.name, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count >= 1 +| KEEP + esql.script_block_text.replace.length, + esql.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + file.name, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +| WHERE esql.script_block_text.replace.length >= 1 ''' From 37e013d7350b156986947390eee11d4410e946db Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:30:51 -0400 Subject: [PATCH 58/94] adjusted Unusual File Creation by Web Server --- ...sistence_web_server_sus_file_creation.toml | 69 ++++++++++++------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/rules_building_block/persistence_web_server_sus_file_creation.toml b/rules_building_block/persistence_web_server_sus_file_creation.toml index 9aaeb1a79ee..03a9e92cbc4 100644 --- a/rules_building_block/persistence_web_server_sus_file_creation.toml +++ b/rules_building_block/persistence_web_server_sus_file_creation.toml @@ -3,7 +3,7 @@ bypass_bbr_timing = true creation_date = "2025/03/06" integration = ["endpoint"] maturity = "production" -updated_date = "2025/04/03" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -61,29 +61,50 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -from logs-endpoint.events.file-* -| keep @timestamp, host.os.type, event.type, event.action, user.name, user.id, process.name, process.executable, file.path, agent.id, host.name -| where @timestamp > now() - 1 hours -| where host.os.type == "linux" and event.type == "change" and event.action in ("rename", "creation") and ( - user.name in ( - "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" - ) or - user.id in ("99", "33", "498", "48") - ) and ( - process.name in ( - "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", - "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", - "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" - ) or - process.name like "php-*" or - process.name like "python*" or - process.name like "ruby*" or - process.name like "perl*" - ) -| stats cc = count(), agent_count = count_distinct(agent.id), host.name = VALUES(host.name), agent.id = VALUES(agent.id) by process.executable, file.path -| where agent_count == 1 and cc < 5 -| sort cc asc -| limit 100 +FROM logs-endpoint.events.file-* +| KEEP + @timestamp, + host.os.type, + event.type, + event.action, + user.name, + user.id, + process.name, + process.executable, + file.path, + agent.id, + host.name +| WHERE + @timestamp > NOW() - 1 HOURS AND + host.os.type == "linux" AND + event.type == "change" AND + event.action IN ("rename", "creation") AND ( + user.name IN ( + "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" + ) OR + user.id IN ("99", "33", "498", "48") + ) AND ( + process.name IN ( + "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", + "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", + "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" + ) OR + process.name LIKE "php-*" OR + process.name LIKE "python*" OR + process.name LIKE "ruby*" OR + process.name LIKE "perl*" + ) +| STATS + esql.event.count = COUNT(), + esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + host.name.values = VALUES(host.name), + agent.id.values = VALUES(agent.id) + BY process.executable, file.path +| WHERE + esql.agent.id.count_distinct == 1 AND + esql.event.count < 5 +| SORT esql.event.count ASC +| LIMIT 100 ''' From fab91afeadd202610989ddf4a514c367b1d8730a Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:36:17 -0400 Subject: [PATCH 59/94] adjusted Potential PowerShell Obfuscation via High Special Character Proportion --- ..._obfuscation_proportion_special_chars.toml | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml index 689ebc37b84..1d4539dc91a 100644 --- a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml +++ b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml @@ -3,7 +3,7 @@ bypass_bbr_timing = true creation_date = "2025/04/16" integration = ["windows"] maturity = "production" -updated_date = "2025/04/16" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -49,27 +49,43 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 1000 chars that contain a related keyword -| EVAL script_len = LENGTH(powershell.file.script_block_text) -| WHERE script_len > 1000 +| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) + +// Filter for long scripts +| WHERE esql.powershell.file.script_block_text.length > 1000 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 // Excludes spaces, #, = and - as they are heavily used in scripts for formatting -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") +| EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") // Count the occurrence of special chars and their proportion to the total chars in the script -| EVAL special_count = script_len - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) -| EVAL proportion = special_count::double / script_len::double +| EVAL esql.character.special.length = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL esql.character.special.ratio = esql.character.special.length::double / esql.powershell.file.script_block_text.length::double // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP special_count, script_len, proportion, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id +| KEEP + esql.character.special.length, + esql.powershell.file.script_block_text.length, + esql.character.special.ratio, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id // Filter for scripts with a 25%+ proportion of special chars -| WHERE proportion > 0.25 +| WHERE esql.character.special.ratio > 0.25 ''' From 759dbd899380407d171928a549a63d036fcb7f7c Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:39:03 -0400 Subject: [PATCH 60/94] adjusted Potential Malicious PowerShell Based on Alert Correlation --- .../execution_posh_malicious_script_agg.toml | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/rules/windows/execution_posh_malicious_script_agg.toml b/rules/windows/execution_posh_malicious_script_agg.toml index 56c3b796b68..20706405e33 100644 --- a/rules/windows/execution_posh_malicious_script_agg.toml +++ b/rules/windows/execution_posh_malicious_script_agg.toml @@ -1,7 +1,7 @@ [metadata] creation_date = "2025/04/16" maturity = "production" -updated_date = "2025/04/16" +updated_date = "2025/07/16" [transform] [[transform.osquery]] @@ -107,20 +107,27 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM .alerts-security.* metadata _id +FROM .alerts-security.* METADATA _id // Filter for PowerShell related alerts | WHERE kibana.alert.rule.name LIKE "*PowerShell*" // As alerts don't have non-ECS fields, parse the script block ID using GROK -| GROK message "ScriptBlock ID: (?.+)" -| WHERE powershell.file.script_block_id IS NOT NULL +| GROK message "ScriptBlock ID: (?.+)" +| WHERE esql.message.powershell.file.script_block_id IS NOT NULL -| KEEP kibana.alert.rule.name, powershell.file.script_block_id, _id +// Keep relevant fields for further processing +| KEEP kibana.alert.rule.name, esql.message.powershell.file.script_block_id, _id // Count distinct alerts and filter for matches above the threshold -| STATS distinct_alerts = COUNT_DISTINCT(kibana.alert.rule.name), rules_triggered = VALUES(kibana.alert.rule.name), alert_ids = VALUES(_id) BY powershell.file.script_block_id -| WHERE distinct_alerts >= 5 +| STATS + esql.kibana.alert.rule.name.count_distinct = COUNT_DISTINCT(kibana.alert.rule.name), + esql.kibana.alert.rule.name.values = VALUES(kibana.alert.rule.name), + esql._id.values = VALUES(_id) + BY esql.message.powershell.file.script_block_id + +// Apply detection threshold +| WHERE esql.kibana.alert.rule.name.count_distinct >= 5 ''' From c9d007d5ca1c497a869738de40659e64dd96c9f0 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:41:06 -0400 Subject: [PATCH 61/94] adjusted Potential PowerShell Obfuscation via Character Array Reconstruction --- ..._evasion_posh_obfuscation_char_arrays.toml | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml index 5cdc9ed0231..cf5b7c63c40 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/14" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -84,7 +84,9 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index + +// Filter for Event ID 4104 indicating script block logging | WHERE event.code == "4104" // Filter for scripts that contain the "char" keyword using MATCH, boosts the query performance @@ -92,14 +94,32 @@ FROM logs-windows.powershell_operational* metadata _id, _version, _index // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """(char\[\]\]\(\d+,\d+[^)]+|(\s?\(\[char\]\d+\s?\)\+){2,})""", "🔥") +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + powershell.file.script_block_text, + """(char\[\]\]\(\d+,\d+[^)]+|(\s?\(\[char\]\d+\s?\)\+){2,})""", + "🔥" +) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.replace.length = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count >= 1 +| KEEP + esql.character.count, + esql.powershell.file.script_block_text.replace.length, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Final filter on match count +| WHERE esql.character.count >= 1 ''' From 78149bc8349730ef64dc8e6c8b085f31490efba2 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 12:59:40 -0400 Subject: [PATCH 62/94] adjusted Potential PowerShell Obfuscation via String Reordering --- ...vasion_posh_obfuscation_string_format.toml | 43 ++++++++++--- ...scation_whitespace_special_proportion.toml | 62 ++++++++++++------- 2 files changed, 74 insertions(+), 31 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index 913377a3d04..54640ce9234 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/03" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -82,29 +82,52 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index + +// Filter for PowerShell Event ID 4104 | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL script_len = LENGTH(powershell.file.script_block_text) -| WHERE script_len > 500 +| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE esql.powershell.file.script_block_text.length > 500 | WHERE powershell.file.script_block_text LIKE "*{0}*" // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """((\{\d+\}){2,}["']\s?-f|::Format[^\{]+(\{\d+\}){2,})""", "🔥") +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + powershell.file.script_block_text, + """((\{\d+\}){2,}["']\s?-f|::Format[^\{]+(\{\d+\}){2,})""", + "🔥" +) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) +| EVAL esql.powershell.script_block_text.format.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count > 3 +| KEEP + esql.powershell.script_block_text.format.count, + esql.powershell.file.script_block_text.length, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + file.name, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Filter for scripts with more than 3 suspicious format patterns +| WHERE esql.powershell.script_block_text.format.count > 3 // Exclude Noisy Patterns // Icinga Framework -| WHERE (file.name NOT LIKE "framework_cache.psm1" or file.name IS NULL) +| WHERE (file.name NOT LIKE "framework_cache.psm1" OR file.name IS NULL) + | WHERE NOT // https://wtfbins.wtf/17 ( @@ -112,7 +135,7 @@ FROM logs-windows.powershell_operational* metadata _id, _version, _index powershell.file.script_block_text LIKE "*:::::\\\\windows\\\\sentinel*") AND (powershell.file.script_block_text LIKE "*$local:Bypassed*" OR - powershell.file.script_block_text LIKE "*origPSExecutionPolicyPreference*") + powershell.file.script_block_text LIKE "*origPSExecutionPolicyPreference*") ) ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml index ee8e7621bae..538703e8af1 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/16" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -83,29 +83,49 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index + +// Filter for PowerShell Event ID 4104 | WHERE event.code == "4104" // Replace repeated spaces used for formatting after a new line with a single space to reduce FPs -| EVAL dedup_space_script_block = REPLACE(powershell.file.script_block_text, """\n\s+""", "\n ") - -// Look for scripts with more than 1000 chars that contain a related keyword -| EVAL script_len = LENGTH(dedup_space_script_block) -| WHERE script_len > 1000 - -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for -// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(dedup_space_script_block, """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\.]""", "🔥") - -// Count the occurrence of numbers and their proportion to the total chars in the script -| EVAL special_count = script_len - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) -| EVAL proportion = special_count::double / script_len::double - -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP special_count, script_len, proportion, dedup_space_script_block, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id - -// Filter for scripts with a 75%+ proportion of numbers -| WHERE proportion > 0.75 +| EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\n\s+""", "\n ") + +// Look for scripts with more than 1000 chars +| EVAL esql.powershell.file.script_block_text.replace.length = LENGTH(esql.powershell.file.script_block_text.replace) +| WHERE esql.powershell.file.script_block_text.replace.length > 1000 + +// Replace format characters with 🔥 to count suspicious formatting density +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + esql.powershell.file.script_block_text.replace, + """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\.]""", + "🔥" +) + +// Count 🔥 and calculate its proportion of the script +| EVAL esql.character.special.length = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL esql.character.special.ratio = esql.character.special.length::double / esql.powershell.file.script_block_text.length::double + +// Retain fields for context or alert generation +| KEEP + esql.character.special.length, + esql.powershell.file.script_block_text.length, + esql.character.special.ratio, + esql.powershell.file.script_block_text.replace, + esql.powershell.file.script_block_text.replace.length, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Filter for high-ratio suspicious formatting scripts +| WHERE esql.character.special.ratio > 0.75 ''' From 54f40ab9e809f45576d7324305a2ce59f7b25059 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 13:03:36 -0400 Subject: [PATCH 63/94] adjusted Potential PowerShell Obfuscation via String Concatenation --- ...vasion_posh_obfuscation_string_concat.toml | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml index eb11f0bd646..f79de9064db 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/14" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -83,23 +83,44 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index + +// Filter for PowerShell Event ID 4104 | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL script_len = LENGTH(powershell.file.script_block_text) -| WHERE script_len > 500 +| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE esql.powershell.file.script_block_text.length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """['"][A-Za-z0-9.]+['"](\s?\+\s?['"][A-Za-z0-9.,\-\s]+['"]){2,}""", "🔥") +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + powershell.file.script_block_text, + """['"][A-Za-z0-9.]+['"](\s?\+\s?['"][A-Za-z0-9.,\-\s]+['"]){2,}""", + "🔥" +) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count >= 2 +| KEEP + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.length, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Filter for scripts with at least 2 suspicious string concatenations +| WHERE esql.powershell.file.script_block_text.character.count >= 2 ''' From 9f76cb21f50e614fa58fc818dd5b24e9de5d90a6 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 13:06:20 -0400 Subject: [PATCH 64/94] adjusted Potential PowerShell Obfuscation via Reverse Keywords --- ...sion_posh_obfuscation_reverse_keyword.toml | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml index 1b91b3f7c92..e5621c4a2b7 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/14" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -82,22 +82,41 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index + +// Filter for PowerShell Event ID 4104 | WHERE event.code == "4104" -// Filter for scripts that contains these keywords using MATCH, boosts the query performance, match will ignore the | and look for the individual words +// Filter for scripts that contains these keywords using MATCH, boosts the query performance, +// match will ignore the | and look for the individual words | WHERE powershell.file.script_block_text : "rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman|vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod" // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """(?i)(rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman\.|:vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod)""", "🔥") +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + powershell.file.script_block_text, + """(?i)(rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman\.|:vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod)""", + "🔥" +) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, agent.id -| WHERE count >= 2 +| KEEP + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + agent.id + +// Filter for scripts with at least 2 suspicious keyword matches +| WHERE esql.powershell.file.script_block_text.character.count >= 2 ''' From 6b0411b0f74c4cd75d87bf47757ecacd95d8fe65 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 13:09:50 -0400 Subject: [PATCH 65/94] adjusted PowerShell Obfuscation via Negative Index String Reversal --- ...asion_posh_obfuscation_index_reversal.toml | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml index 98b4ca3d450..6f3735d46d8 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/14" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -85,26 +85,48 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index + +// Filter for PowerShell Event ID 4104 | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL script_len = LENGTH(powershell.file.script_block_text) -| WHERE script_len > 500 +| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE esql.powershell.file.script_block_text.length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """\$\w+\[\-\s?1\.\.""", "🔥") +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + powershell.file.script_block_text, + """\$\w+\[\-\s?1\.\.""", + "🔥" +) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count >= 1 +| KEEP + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.length, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Filter for patterns found at least once +| WHERE esql.powershell.file.script_block_text.character.count >= 1 // FP Patterns | WHERE NOT powershell.file.script_block_text LIKE "*GENESIS-5654*" + ''' From ff2db16e44786e2d50726dc2987cf9d3298bf345 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 13:11:59 -0400 Subject: [PATCH 66/94] adjusted Dynamic IEX Reconstruction via Method String Access --- ...obfuscation_iex_string_reconstruction.toml | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml index 6c82e783d2f..8ba089fb18a 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/16" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -84,23 +84,41 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" -// Look for scripts with more than 500 chars that contain a related keyword -| EVAL script_len = LENGTH(powershell.file.script_block_text) -| WHERE script_len > 500 - -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for -// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """(?i)['"]['"].(Insert|Normalize|Chars|SubString|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", "🔥") - -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) - -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count >= 1 +// Check script length > 500 +| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE esql.powershell.file.script_block_text.length > 500 + +// Replace method access patterns with 🔥 for detection +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + powershell.file.script_block_text, + """(?i)['"]['"].(Insert|Normalize|Chars|SubString|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", + "🔥" +) + +// Count 🔥 instances to determine presence of suspicious method usage +| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) + +// Keep relevant fields for context and triage +| KEEP + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.length, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Only return if at least one pattern is found +| WHERE esql.powershell.file.script_block_text.character.count >= 1 ''' From b5d61a090ab734d822b35c484c9978883297a5e8 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 13:14:22 -0400 Subject: [PATCH 67/94] adjusted Potential Dynamic IEX Reconstruction via Environment Variables --- ...fuscation_iex_env_vars_reconstruction.toml | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml index 81549f41ef6..998704ccff5 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/16" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -83,23 +83,41 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" -// Look for scripts with more than 500 chars that contain a related keyword -| EVAL script_len = LENGTH(powershell.file.script_block_text) -| WHERE script_len > 500 - -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for -// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """(?i)(\$(?:\w+|\w+\:\w+)\[\d++\]\+\$(?:\w+|\w+\:\w+)\[\d++\]\+['"]x['"]|\$(?:\w+\:\w+)\[\d++,\d++,\d++\]|\.name\[\d++,\d++,\d++\])""", "🔥") - -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) - -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count >= 1 +// Evaluate the length of the script block +| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE esql.powershell.file.script_block_text.length > 500 + +// Replace obfuscated dynamic string construction with 🔥 to count obfuscation attempts +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + powershell.file.script_block_text, + """(?i)(\$(?:\w+|\w+\:\w+)\[\d++\]\+\$(?:\w+|\w+\:\w+)\[\d++\]\+['"]x['"]|\$(?:\w+\:\w+)\[\d++,\d++,\d++\]|\.name\[\d++,\d++,\d++\])""", + "🔥" +) + +// Count how many 🔥 were inserted +| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) + +// Keep relevant context fields +| KEEP + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.length, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Filter for meaningful pattern matches +| WHERE esql.powershell.file.script_block_text.character.count >= 1 ''' From e77d6ccf5bbe781860a9ad78d0f8e96a6d37a192 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 13:16:19 -0400 Subject: [PATCH 68/94] adjusted Potential PowerShell Obfuscation via High Numeric Character Proportion --- ...sh_obfuscation_high_number_proportion.toml | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml index e1aa44cc221..0380216db2f 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/16" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -83,30 +83,42 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index +FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" -// Look for scripts with more than 1000 chars that contain a related keyword -| EVAL script_len = LENGTH(powershell.file.script_block_text) -| WHERE script_len > 1000 - -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for -// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """[0-9]""", "🔥") - -// Count the occurrence of numbers and their proportion to the total chars in the script -| EVAL special_count = script_len - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) -| EVAL proportion = special_count::double / script_len::double - -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP special_count, script_len, proportion, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id - -// Filter for scripts with a 30%+ proportion of numbers -| WHERE proportion > 0.30 - -// Exclude noisy patterns -| WHERE - NOT powershell.file.script_block_text RLIKE """.*\"[a-fA-F0-9]{64}\"\,.*""" +// Measure script length +| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE esql.powershell.file.script_block_text.length > 1000 + +// Replace digits with 🔥 for numeric pattern density analysis +| EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[0-9]""", "🔥") + +// Count numeric characters and calculate proportion of total +| EVAL esql.powershell.file.script_block_text.character.count = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.character.ratio = esql.powershell.file.script_block_text.character.count::double / esql.powershell.file.script_block_text.length::double + +// Retain relevant fields for investigation +| KEEP + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.character.ratio, + esql.powershell.file.script_block_text.length, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Filter for suspiciously high numeric content +| WHERE esql.powershell.file.script_block_text.character.ratio > 0.30 + +// Exclude noisy patterns such as 64-character hashes +| WHERE NOT powershell.file.script_block_text RLIKE """.*\"[a-fA-F0-9]{64}\"\,.*""" ''' From f2aac69d30e75bd1817dd940a7a7b3869d47f47e Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 13:18:21 -0400 Subject: [PATCH 69/94] adjusted Potential PowerShell Obfuscation via Concatenated Dynamic Command Invocation --- ...asion_posh_obfuscation_concat_dynamic.toml | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml index 2f8107596b0..c64ee620629 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/15" integration = ["windows"] maturity = "production" -updated_date = "2025/07/07" +updated_date = "2025/07/16" [rule] author = ["Elastic"] @@ -83,19 +83,37 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* metadata _id, _version, _index -| WHERE event.code == "4104" and powershell.file.script_block_text LIKE "*+*" +FROM logs-windows.powershell_operational* METADATA _id, _version, _index +| WHERE event.code == "4104" AND powershell.file.script_block_text LIKE "*+*" -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// Replace suspicious method string constructions with 🔥 for entropy-style detection // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """[.&]\(\s*(['"][A-Za-z0-9.-]+['"]\s*\+\s*)+['"][A-Za-z0-9.-]+['"]\s*\)""", "🔥") - -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", "")) - -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id -| WHERE count >= 1 +| EVAL esql.powershell.file.script_block_text.replace = REPLACE( + powershell.file.script_block_text, + """[.&]\(\s*(['"][A-Za-z0-9.-]+['"]\s*\+\s*)+['"][A-Za-z0-9.-]+['"]\s*\)""", + "🔥" +) + +// Count how many patterns were detected based on 🔥 characters +| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) + +// Keep relevant context fields for triage +| KEEP + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.replace, + powershell.file.script_block_text, + powershell.file.script_block_id, + file.path, + powershell.sequence, + powershell.total, + _id, + _index, + host.name, + agent.id, + user.id + +// Alert if suspicious pattern is present +| WHERE esql.powershell.file.script_block_text.character.count >= 1 ''' From 832b1a4f908966445b59dd24f80a921830a48bd0 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 14:23:28 -0400 Subject: [PATCH 70/94] adjusted Rare Connection to WebDAV Target --- .../credential_access_rare_webdav_destination.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rules/windows/credential_access_rare_webdav_destination.toml b/rules/windows/credential_access_rare_webdav_destination.toml index 2474dcd6457..9786204ae4b 100644 --- a/rules/windows/credential_access_rare_webdav_destination.toml +++ b/rules/windows/credential_access_rare_webdav_destination.toml @@ -63,20 +63,20 @@ FROM logs-* process.command_line LIKE "*DavSetCookie*" | KEEP host.id, process.command_line, user.name | GROK - process.command_line """(?DavSetCookie .* http)""" + process.command_line """(?DavSetCookie .* http)""" | EVAL - esql.target.webdav.grok.replace = REPLACE(esql.target.webdav.grok, "(DavSetCookie | http)", "") + esql.server.webdav.cookie.replace = REPLACE(esql.server.webdav.cookie, "(DavSetCookie | http)", "") | WHERE - esql.target.webdav.grok.replace IS NOT NULL AND - esql.target.webdav.grok.replace RLIKE """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" AND - NOT esql.target.webdav.grok.replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND - NOT esql.target.webdav.grok.replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" + esql.server.webdav.cookie.replace IS NOT NULL AND + esql.server.webdav.cookie.replace RLIKE """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" AND + NOT esql.server.webdav.cookie.replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND + NOT esql.server.webdav.cookie.replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" | STATS esql.total.count = COUNT(*), esql.host.id.count_distinct = COUNT_DISTINCT(host.id), host.id.values = VALUES(host.id), user.name.values = VALUES(user.name) - BY esql.target.webdav.grok.replace + BY esql.server.webdav.cookie.replace | WHERE esql.host.id.count_distinct == 1 AND esql.total.count <= 3 From 0f4046d33961536a1394ef3d74cf170f3857ce9b Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 14:31:24 -0400 Subject: [PATCH 71/94] adjusted Potential PowerShell Obfuscation via Invalid Escape Sequences --- .../windows/defense_evasion_posh_obfuscation_backtick.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml index 449a3bbae13..c9736c061e1 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml @@ -100,11 +100,11 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | EVAL esql.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.script_block_text.replace.length = LENGTH(esql.script_block_text.replace) - LENGTH(REPLACE(esql.script_block_text.replace, "🔥", "")) +| EVAL esql.script_block_text.character.count = LENGTH(esql.script_block_text.replace) - LENGTH(REPLACE(esql.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.script_block_text.replace.length, + esql.script_block_text.character.count, esql.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, @@ -118,7 +118,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id, user.id -| WHERE esql.script_block_text.replace.length >= 10 +| WHERE esql.script_block_text.character.count >= 10 // Filter FPs, and due to the behavior of the LIKE operator, allow null values | WHERE (file.name NOT LIKE "TSS_*.psm1" OR file.name IS NULL) From af6b79cbada5cb9fad957638f294476fc6e51921 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 14:34:10 -0400 Subject: [PATCH 72/94] adjusted Potential PowerShell Obfuscation via Backtick-Escaped Variable Expansion --- ...defense_evasion_posh_obfuscation_backtick_var.toml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index 595447ded54..7fb56031298 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -93,15 +93,16 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") +| EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.script_block_text.replace.length = LENGTH(esql.script_block_text.replace) - LENGTH(REPLACE(esql.script_block_text.replace, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.script_block_text.replace.length, - esql.script_block_text.replace, + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.replace.length, + esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -114,7 +115,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id, user.id -| WHERE esql.script_block_text.replace.length >= 1 +| WHERE esql.powershell.file.script_block_text.character.count >= 1 ''' From c6f182141a2c337f15c0855a74f752a8736fa20a Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 14:35:36 -0400 Subject: [PATCH 73/94] adjusted Potential PowerShell Obfuscation via Character Array Reconstruction --- .../defense_evasion_posh_obfuscation_char_arrays.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml index cf5b7c63c40..b7b42860f4b 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml @@ -101,12 +101,12 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.powershell.file.script_block_text.replace.length = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.character.count, - esql.powershell.file.script_block_text.replace.length, + esql.powershell.file.script_block_text.character.count, + esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -119,7 +119,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Final filter on match count -| WHERE esql.character.count >= 1 +| WHERE esql.powershell.file.script_block_text.character.count >= 1 ''' From 9e47f7657aa62923ed1380ccfe968e372b55bcf3 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 14:38:15 -0400 Subject: [PATCH 74/94] adjusted Potential PowerShell Obfuscation via High Special Character Proportion --- ...sion_posh_obfuscation_proportion_special_chars.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml index 1d4539dc91a..f7a42574af4 100644 --- a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml +++ b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml @@ -64,14 +64,14 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") // Count the occurrence of special chars and their proportion to the total chars in the script -| EVAL esql.character.special.length = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) -| EVAL esql.character.special.ratio = esql.character.special.length::double / esql.powershell.file.script_block_text.length::double +| EVAL esql.powershell.file.script_block_text.character.count = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.character.ratio = esql.powershell.file.script_block_text.character.count::double / esql.powershell.file.script_block_text.length::double // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.character.special.length, + esql.powershell.file.script_block_text.character.count, esql.powershell.file.script_block_text.length, - esql.character.special.ratio, + esql.powershell.file.script_block_text.character.ratio, esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, @@ -85,7 +85,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with a 25%+ proportion of special chars -| WHERE esql.character.special.ratio > 0.25 +| WHERE esql.powershell.file.script_block_text.character.ratio > 0.25 ''' From 9676b9400a7c7058e666ae0e8f962ff41f5b84fb Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 14:40:07 -0400 Subject: [PATCH 75/94] adjusted Potential PowerShell Obfuscation via Special Character Overuse --- ...posh_obfuscation_whitespace_special_proportion.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml index 538703e8af1..5c9c6e125a4 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml @@ -103,14 +103,14 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index ) // Count 🔥 and calculate its proportion of the script -| EVAL esql.character.special.length = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) -| EVAL esql.character.special.ratio = esql.character.special.length::double / esql.powershell.file.script_block_text.length::double +| EVAL esql.powershell.file.script_block_text.count = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL esql.powershell.file.script_block_text.ratio = esql.powershell.file.script_block_text.count::double / esql.powershell.file.script_block_text.length::double // Retain fields for context or alert generation | KEEP - esql.character.special.length, + esql.powershell.file.script_block_text.count, esql.powershell.file.script_block_text.length, - esql.character.special.ratio, + esql.powershell.file.script_block_text.ratio, esql.powershell.file.script_block_text.replace, esql.powershell.file.script_block_text.replace.length, powershell.file.script_block_text, @@ -125,7 +125,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for high-ratio suspicious formatting scripts -| WHERE esql.character.special.ratio > 0.75 +| WHERE esql.powershell.file.script_block_text.ratio > 0.75 ''' From dec8a0636a9890f95f724aab555fd3fe1dbcbedc Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 14:41:03 -0400 Subject: [PATCH 76/94] adjusted Potential PowerShell Obfuscation via String Reordering --- .../defense_evasion_posh_obfuscation_string_format.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index 54640ce9234..1269e80e628 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -101,11 +101,11 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.powershell.script_block_text.format.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL esql.powershell.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.powershell.script_block_text.format.count, + esql.powershell.script_block_text.character.count, esql.powershell.file.script_block_text.length, esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, @@ -121,7 +121,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with more than 3 suspicious format patterns -| WHERE esql.powershell.script_block_text.format.count > 3 +| WHERE esql.powershell.script_block_text.character.count > 3 // Exclude Noisy Patterns From ec0f1ed2202baa215bce8de152eb9430f44f25e8 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 14:44:57 -0400 Subject: [PATCH 77/94] adjusted Suspicious Microsoft 365 UserLoggedIn via OAuth Code --- ...e_evasion_microsoft_365_susp_oauth2_authorization.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml index 25c2130f775..5242bfcd201 100644 --- a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml +++ b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml @@ -104,6 +104,14 @@ FROM logs-o365.audit-* esql.time_window.date_trunc, o365.audit.ApplicationId, o365.audit.ObjectId +| KEEP + esql.time_window.date_trunc, + esql.source.ip.values, + esql.source.ip.count_distinct, + esql.o365.audit.ApplicationId.values, + esql.source.`as`.organization.name.values, + esql.oauth.token.count_distinct, + esql.oauth.authorize.count_distinct | WHERE esql.source.ip.count_distinct >= 2 AND esql.oauth.token.count_distinct > 0 AND From 2819eb282d0d58599bde80da5c193f716e26b818 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 16 Jul 2025 15:48:18 -0400 Subject: [PATCH 78/94] adjusted fields that were inconsistent --- ...otas_multi_region_service_quota_requests.toml | 8 ++++---- ...ebs_snapshot_shared_with_another_account.toml | 16 ++++++++-------- .../impact_ec2_ebs_snapshot_access_removed.toml | 12 ++++++------ ...et_object_uploaded_with_ransom_extension.toml | 10 +++++----- ...t_s3_object_encryption_with_external_key.toml | 12 ++++++------ .../impact_s3_static_site_js_file_uploaded.toml | 10 +++++----- ...ssion_token_used_from_multiple_addresses.toml | 8 ++++---- ...nistratoraccess_policy_attached_to_group.toml | 8 ++++---- ...inistratoraccess_policy_attached_to_role.toml | 6 +++--- ...inistratoraccess_policy_attached_to_user.toml | 8 ++++---- ...ial_access_entra_id_brute_force_activity.toml | 10 +++++----- ...s_entra_signin_brute_force_microsoft_365.toml | 10 +++++----- ...icious_oauth_flow_via_auth_broker_to_drs.toml | 4 ++-- ...microsoft_365_excessive_account_lockouts.toml | 10 +++++----- ...t_365_potential_user_account_brute_force.toml | 10 +++++----- 15 files changed, 71 insertions(+), 71 deletions(-) diff --git a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml index 779e70ba342..f748b929adf 100644 --- a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml +++ b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml @@ -47,18 +47,18 @@ FROM logs-aws.cloudtrail-* | eval esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) // dissect request parameters to extract service and quota code -| dissect aws.cloudtrail.request_parameters "{%{?esql.service_code.key}=%{esql.service_code}, %{?quota_code_key}=%{esql.quota_code}}" +| dissect aws.cloudtrail.request_parameters "{%{?esql.aws.cloudtrail.request_parameters.service_code.key}=%{esql.aws.cloudtrail.request_parameters.service_code}, %{?quota_code_key}=%{esql.aws.cloudtrail.request_parameters.quota_code}}" // filter for EC2 service quota L-1216C47A (vCPU on-demand instances) -| where esql.service_code == "ec2" and esql.quota_code == "L-1216C47A" +| where esql.aws.cloudtrail.request_parameters.service_code == "ec2" and esql.aws.cloudtrail.request_parameters.quota_code == "L-1216C47A" // keep only the relevant fields | keep esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn, cloud.region, - esql.service_code, - esql.quota_code + esql.aws.cloudtrail.request_parameters.service_code, + esql.aws.cloudtrail.request_parameters.quota_code // count the number of unique regions and total API calls within the time window | stats diff --git a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml index d0ba3359d4a..d662de1441a 100644 --- a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml +++ b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml @@ -88,12 +88,12 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract snapshotId, attribute type, operation type, and userId | dissect aws.cloudtrail.request_parameters - "{%{?snapshotId}=%{esql.snapshot_id},%{?attributeType}=%{esql.attribute_type},%{?createVolumePermission}={%{esql.operation_type}={%{?items}=[{%{?userId}=%{esql.user.id}}]}}}" + "{%{?snapshotId}=%{esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{esql.attribute.type},%{?createVolumePermission}={%{esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{esql.aws.cloudtrail.request_parameters.user.id}}]}}}" // Check for snapshot permission added for another AWS account | where - esql.operation_type == "add" - and cloud.account.id != esql.user.id + esql.aws.cloudtrail.request_parameters.operation.type == "add" + and cloud.account.id != esql.aws.cloudtrail.request_parameters.user.id // Keep ECS and derived fields | keep @@ -101,11 +101,11 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - esql.snapshot_id, - esql.attribute_type, - esql.operation_type, - esql.user.id, - source.address + esql.aws.cloudtrail.request_parameters.snapshot.id, + esql.aws.cloudtrail.request_parameters.attribute.type, + esql.aws.cloudtrail.request_parameters.operation.type, + esql.aws.cloudtrail.request_parameters.user.id, + source.ip ''' diff --git a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml index 5b3323b1eac..868b284a870 100644 --- a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml +++ b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml @@ -85,10 +85,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Dissect parameters to extract key fields | dissect aws.cloudtrail.request_parameters - "{%{?snapshotId}=%{esql.snapshot.id},%{?attributeType}=%{esql.attribute_type},%{?createVolumePermission}={%{esql.operation_type}={%{?items}=[{%{?userId}=%{esql.user.id}}]}}}" + "{%{?snapshotId}=%{esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{esql.aws.cloudtrail.request_parameters.attribute.type},%{?createVolumePermission}={%{esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{esql.aws.cloudtrail.request_parameters.user.id}}]}}}" // Match on snapshot permission **removal** -| where esql.operation_type == "remove" +| where esql.aws.cloudtrail.request_parameters.operation.type == "remove" // Keep ECS and derived fields | keep @@ -96,10 +96,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - esql.snapshot.id, - esql.attribute_type, - esql.operation_type, - esql.user.id, + esql.aws.cloudtrail.request_parameters.snapshot.id, + esql.aws.cloudtrail.request_parameters.attribute.type, + esql.aws.cloudtrail.request_parameters.operation.type, + esql.aws.cloudtrail.request_parameters.user.id, source.address ''' diff --git a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml index 9cf1a6caee7..b3cf027354e 100644 --- a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml +++ b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml @@ -91,18 +91,18 @@ FROM logs-aws.cloudtrail-* and event.outcome == "success" // extract object key from API request parameters -| dissect aws.cloudtrail.request_parameters "%{?ignore_values}key=%{esql.object.key}}" +| dissect aws.cloudtrail.request_parameters "%{?ignore_values}key=%{esql.aws.cloudtrail.request_parameters.object.key}}" // regex match against common ransomware naming patterns | where - esql.object.key rlike "(.*)(ransom|lock|crypt|enc|readme|how_to_decrypt|decrypt_instructions|recovery|datarescue)(.*)" - and not esql.object.key rlike "(.*)(AWSLogs|CloudTrail|access-logs)(.*)" + esql.aws.cloudtrail.request_parameters.object.key rlike "(.*)(ransom|lock|crypt|enc|readme|how_to_decrypt|decrypt_instructions|recovery|datarescue)(.*)" + and not esql.aws.cloudtrail.request_parameters.object.key rlike "(.*)(AWSLogs|CloudTrail|access-logs)(.*)" // keep relevant ECS and derived fields | keep tls.client.server_name, aws.cloudtrail.user_identity.arn, - esql.object.key + esql.aws.cloudtrail.request_parameters.object.key // aggregate by server name, actor, and object key | stats @@ -110,7 +110,7 @@ FROM logs-aws.cloudtrail-* by tls.client.server_name, aws.cloudtrail.user_identity.arn, - esql.object.key + esql.aws.cloudtrail.request_parameters.object.key // filter for rare single uploads (likely test/detonation) | where esql.event.count == 1 diff --git a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml index 20900d327bc..95e042f502f 100644 --- a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml +++ b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml @@ -94,10 +94,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // dissect request parameters to extract KMS key info and target object info | dissect aws.cloudtrail.request_parameters - "{%{?bucketName}=%{esql.target.bucket.name},%{?x-amz-server-side-encryption-aws-kms-key-id}=%{?arn}:%{?aws}:%{?kms}:%{?region}:%{esql.kms.key.account.id}:%{?key}/%{esql.kms.key.id},%{?Host}=%{?tls.client.server.name},%{?x-amz-server-side-encryption}=%{?server_side_encryption},%{?x-amz-copy-source}=%{?bucket.object.name},%{?key}=%{esql.target.object.key}}" + "{%{?bucketName}=%{esql.aws.cloudtrail.request_parameters.target.bucket.name},%{?x-amz-server-side-encryption-aws-kms-key-id}=%{?arn}:%{?aws}:%{?kms}:%{?region}:%{esql.aws.cloudtrail.request_parameters.kms.key.account.id}:%{?key}/%{esql.aws.cloudtrail.request_parameters.kms.key.id},%{?Host}=%{?tls.client.server.name},%{?x-amz-server-side-encryption}=%{?server_side_encryption},%{?x-amz-copy-source}=%{?bucket.object.name},%{?key}=%{esql.aws.cloudtrail.request_parameters.target.object.key}}" // detect cross-account key usage -| where cloud.account.id != esql.kms.key.account.id +| where cloud.account.id != esql.aws.cloudtrail.request_parameters.kms.key.account.id // keep ECS and dissected fields | keep @@ -105,10 +105,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - esql.target.bucket.name, - esql.kms.key.account.id, - esql.kms.key.id, - esql.target.object.key + esql.aws.cloudtrail.request_parameters.target.bucket.name, + esql.aws.cloudtrail.request_parameters.kms.key.account.id, + esql.aws.cloudtrail.request_parameters.kms.key.id, + esql.aws.cloudtrail.request_parameters.target.object.key ''' diff --git a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml index 058a607bc72..334f6223546 100644 --- a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml +++ b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml @@ -93,13 +93,13 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index // Extract fields from request parameters | dissect aws.cloudtrail.request_parameters - "%{{?bucket.name.key}=%{esql.bucket.name}, %{?host.key}=%{esql.bucket.host}, %{?bucket.object.location.key}=%{esql.object.location}}" + "%{{?bucket.name.key}=%{esql.aws.cloudtrail.request_parameters.bucket.name}, %{?host.key}=%{esql.aws.cloudtrail.request_parameters.host}, %{?bucket.object.location.key}=%{esql.aws.cloudtrail.request_parameters.bucket.object.location}}" // Extract file name portion from full object path -| dissect esql.object.location "%{}static/js/%{esql.object.key}" +| dissect esql.aws.cloudtrail.request_parameters.object.location "%{}static/js/%{esql.aws.cloudtrail.request_parameters.object.key}" // Match on JavaScript files -| where ENDS_WITH(esql.object.key, ".js") +| where ENDS_WITH(esql.aws.cloudtrail.request_parameters.object.key, ".js") // Retain relevant ECS and dissected fields | keep @@ -107,8 +107,8 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index aws.cloudtrail.user_identity.access_key_id, aws.cloudtrail.user_identity.type, aws.cloudtrail.request_parameters, - esql.bucket.name, - esql.object.key, + esql.aws.cloudtrail.request_parameters.bucket.name, + esql.aws.cloudtrail.request_parameters.object.key, user_agent.original, source.ip, event.action, diff --git a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml index db22647da88..7d5217c9102 100644 --- a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml +++ b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml @@ -126,8 +126,8 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index esql.user_agent.original.count_distinct = COUNT_DISTINCT(esql.user_agent.original), esql.source.geo.city_name.count_distinct = COUNT_DISTINCT(esql.source.geo.city_name), esql.source.network.org_name.count_distinct = COUNT_DISTINCT(esql.source.network.org_name), - esql.event.first_seen = MIN(esql.event.timestamp), - esql.event.last_seen = MAX(esql.event.timestamp), + esql.timestamp.first_seen = MIN(esql.event.timestamp), + esql.timestamp.last_seen = MAX(esql.event.timestamp), esql.event.count = COUNT() BY esql.time_window.date_trunc, esql.user.access_key_id @@ -153,8 +153,8 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index esql.activity.type, esql.activity.fidelity_score, esql.event.count, - esql.event.first_seen, - esql.event.last_seen, + esql.timestamp.first_seen, + esql.timestamp.last_seen, esql.user.id.values, esql.user.access_key_id.values, esql.event.action.values, diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml index 0ce637144f6..484bef9840d 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml @@ -107,10 +107,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy and group details from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.policy.name},%{?groupName}=%{esql.group.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.aws.cloudtrail.request_parameters.policy.name},%{?groupName}=%{esql.aws.cloudtrail.request_parameters.group.name}}" // Filter for attachment of AdministratorAccess policy -| where esql.policy.name == "AdministratorAccess" +| where esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" // Keep ECS and derived fields | keep @@ -118,8 +118,8 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - esql.policy.name, - esql.group.name + esql.aws.cloudtrail.request_parameters.policy.name, + esql.aws.cloudtrail.request_parameters.group.name ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml index 870bdb8ee6d..9e8c081a025 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml @@ -106,7 +106,7 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy name and role name from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.policy.name},%{?roleName}=%{esql.role.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.aws.cloudtrail.request_parameters.policy.name},%{?roleName}=%{esql.aws.cloudtrail.request_parameters.role.name}}" // Filter for AdministratorAccess policy attachment | where esql.policy.name == "AdministratorAccess" @@ -117,8 +117,8 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - esql.policy.name, - esql.role.name + esql.aws.cloudtrail.request_parameters.policy.name, + esql.aws.cloudtrail.request_parameters.role.name ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml index 31da536f585..5f1e7db14b1 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml @@ -106,10 +106,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy name and user name from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.policy.name},%{?userName}=%{esql.target.user.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.aws.cloudtrail.request_parameters.policy.name},%{?userName}=%{esql.aws.cloudtrail.request_parameters.target.user.name}}" // Filter for AdministratorAccess policy -| where esql.policy.name == "AdministratorAccess" +| where esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" // Keep ECS and parsed fields | keep @@ -118,8 +118,8 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - esql.policy.name, - esql.target.user.name, + esql.aws.cloudtrail.request_parameters.policy.name, + esql.aws.cloudtrail.request_parameters.target.user.name, aws.cloudtrail.request_parameters, aws.cloudtrail.user_identity.arn, related.user, diff --git a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml index 8a2887d066f..7931420a6d2 100644 --- a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml +++ b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml @@ -160,13 +160,13 @@ FROM logs-azure.signinlogs* esql.source.geo.country_name.values = VALUES(source.geo.country_name), esql.source.geo.country_name.unique_count = COUNT_DISTINCT(source.geo.country_name), esql.source.`as`.organization.name.unique_count = COUNT_DISTINCT(source.`as`.organization.name), - esql.first_seen = MIN(@timestamp), - esql.last_seen = MAX(@timestamp), + esql.timestamp.first_seen = MIN(@timestamp), + esql.timestamp.last_seen = MAX(@timestamp), esql.total_attempts = COUNT() BY esql.time_window.date_trunc | EVAL - esql.duration.seconds = DATE_DIFF("seconds", esql.first_seen, esql.last_seen), + esql.duration.seconds = DATE_DIFF("seconds", esql.timestamp.first_seen, esql.timestamp.last_seen), esql.brute_force.type = CASE( esql.azure.signinlogs.properties.user_id.unique_count >= 10 AND esql.total_attempts >= 30 AND esql.azure.signinlogs.result_description.unique_count <= 3 AND esql.source.ip.unique_count >= 5 @@ -189,8 +189,8 @@ BY esql.time_window.date_trunc esql.brute_force.type, esql.duration.seconds, esql.total_attempts, - esql.first_seen, - esql.last_seen, + esql.timestamp.first_seen, + esql.timestamp.last_seen, esql.azure.signinlogs.properties.user_id.unique_count, esql.azure.signinlogs.properties.user_id.list, esql.azure.signinlogs.result_description.values_all, diff --git a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml index 395c158a5c9..71a84191477 100644 --- a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml +++ b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml @@ -168,26 +168,26 @@ FROM logs-azure.signinlogs* BY esql.time_window.date_trunc | EVAL - esql.event.duration_seconds = DATE_DIFF("seconds", esql.@timestamp.min, esql.@timestamp.max), + esql.event.duration.seconds = DATE_DIFF("seconds", esql.@timestamp.min, esql.@timestamp.max), esql.event.bf_type = CASE( esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 10 AND esql.event.count >= 30 AND esql.azure.signinlogs.result_description.count_distinct <= 3 AND esql.source.ip.count_distinct >= 5 - AND esql.event.duration_seconds <= 600 + AND esql.event.duration.seconds <= 600 AND esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct > esql.source.ip.count_distinct, "credential_stuffing", esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 AND esql.azure.signinlogs.result_description.count_distinct == 1 AND esql.event.count >= 15 - AND esql.event.duration_seconds <= 1800, + AND esql.event.duration.seconds <= 1800, "password_spraying", (esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct == 1 AND esql.azure.signinlogs.result_description.count_distinct == 1 AND esql.event.count >= 30 - AND esql.event.duration_seconds <= 300) + AND esql.event.duration.seconds <= 300) OR (esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct <= 3 AND esql.source.ip.count_distinct > 30 AND esql.event.count >= 100), @@ -201,7 +201,7 @@ BY esql.time_window.date_trunc | KEEP esql.time_window.date_trunc, esql.event.bf_type, - esql.event.duration_seconds, + esql.event.duration.seconds, esql.event.count, esql.@timestamp.min, esql.@timestamp.max, diff --git a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml index 9cbf13f8883..dbe1ade5f83 100644 --- a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml +++ b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml @@ -118,7 +118,7 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index esql.source.geo.region_name.values = VALUES(source.geo.region_name), esql.source.address.values = VALUES(source.address), esql.source.address.count_distinct = COUNT_DISTINCT(source.address), - esql.source.as.organization.name.values = VALUES(source.`as`.organization.name), + esql.snapshot.idesql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), esql.azure.signinlogs.properties.authentication_protocol.values = VALUES(azure.signinlogs.properties.authentication_protocol), esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), @@ -161,7 +161,7 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index esql.source.geo.region_name.values, esql.source.address.values, esql.source.address.count_distinct, - esql.source.as.organization.name.values, + esql.snapshot.idesql.source.`as`.organization.name.values, esql.azure.signinlogs.properties.authentication_protocol.values, esql.azure.signinlogs.properties.authentication_requirement.values, esql.azure.signinlogs.properties.is_interactive.values, diff --git a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml index 4838a827e5d..e58e0cec2a2 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml @@ -96,12 +96,12 @@ FROM logs-o365.audit-* esql.source.geo.country_name.values = VALUES(source.geo.country_name), esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(TO_LOWER(o365.audit.ExtendedProperties.RequestType)), - esql.event.first_seen = MIN(@timestamp), - esql.event.last_seen = MAX(@timestamp), + esql.timestamp.first_seen = MIN(@timestamp), + esql.timestamp.last_seen = MAX(@timestamp), esql.event.count = COUNT(*) BY esql.time_window.date_trunc | EVAL - esql.event.duration.seconds = DATE_DIFF("seconds", esql.event.first_seen, esql.event.last_seen) + esql.event.duration.seconds = DATE_DIFF("seconds", esql.timestamp.first_seen, esql.timestamp.last_seen) | KEEP esql.time_window.date_trunc, esql.o365.audit.UserId.count_distinct, @@ -113,8 +113,8 @@ FROM logs-o365.audit-* esql.source.geo.country_name.values, esql.source.geo.country_name.count_distinct, esql.o365.audit.ExtendedProperties.RequestType.values, - esql.event.first_seen, - esql.event.last_seen, + esql.timestamp.first_seen, + esql.timestamp.last_seen, esql.event.count, esql.event.duration.seconds | WHERE diff --git a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml index 619a90d4875..026adb853f4 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml @@ -117,12 +117,12 @@ FROM logs-o365.audit-* esql.source.geo.country_name.values = VALUES(source.geo.country_name), esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - esql.event.first_seen = MIN(@timestamp), - esql.event.last_seen = MAX(@timestamp), + esql.timestamp.first_seen = MIN(@timestamp), + esql.timestamp.last_seen = MAX(@timestamp), esql.event.count = COUNT(*) BY esql.time_window.date_trunc | EVAL - esql.event.duration.seconds = DATE_DIFF("seconds", esql.event.first_seen, esql.event.last_seen), + esql.event.duration.seconds = DATE_DIFF("seconds", esql.timestamp.first_seen, esql.timestamp.last_seen), esql.brute_force.type = CASE( esql.user.id.count_distinct >= 15 AND esql.o365.audit.LogonError.count_distinct == 1 AND esql.event.count >= 10 AND esql.event.duration.seconds <= 1800, "password_spraying", esql.user.id.count_distinct >= 8 AND esql.event.count >= 15 AND esql.o365.audit.LogonError.count_distinct <= 3 AND esql.source.ip.count_distinct <= 5 AND esql.event.duration.seconds <= 600, "credential_stuffing", @@ -142,8 +142,8 @@ FROM logs-o365.audit-* esql.source.geo.country_name.values, esql.source.geo.country_name.count_distinct, esql.source.`as`.organization.name.count_distinct, - esql.event.first_seen, - esql.event.last_seen, + esql.timestamp.first_seen, + esql.timestamp.last_seen, esql.event.duration.seconds, esql.event.count, esql.brute_force.type From 3fd7b89b3c4e8c950b5510b42d2d039b2d9ae0c7 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Thu, 17 Jul 2025 09:17:48 -0400 Subject: [PATCH 79/94] adjusted additional fields --- ...stratoraccess_policy_attached_to_role.toml | 2 +- ..._access_entra_id_brute_force_activity.toml | 36 +++++++++---------- ...ous_oauth_flow_via_auth_broker_to_drs.toml | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml index 9e8c081a025..0318174eb6c 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml @@ -109,7 +109,7 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.aws.cloudtrail.request_parameters.policy.name},%{?roleName}=%{esql.aws.cloudtrail.request_parameters.role.name}}" // Filter for AdministratorAccess policy attachment -| where esql.policy.name == "AdministratorAccess" +| where esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" // Keep relevant ECS and dynamic fields | keep diff --git a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml index 7931420a6d2..837b9015540 100644 --- a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml +++ b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml @@ -146,20 +146,20 @@ FROM logs-azure.signinlogs* esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), - esql.azure.signinlogs.properties.user_id.unique_count = COUNT_DISTINCT(azure.signinlogs.properties.user_id), + esql.azure.signinlogs.properties.user_id.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.user_id), esql.azure.signinlogs.properties.user_id.list = VALUES(azure.signinlogs.properties.user_id), esql.azure.signinlogs.result_description.values_all = VALUES(azure.signinlogs.result_description), - esql.azure.signinlogs.result_description.unique_count = COUNT_DISTINCT(azure.signinlogs.result_description), + esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), - esql.azure.signinlogs.properties.status.error_code.unique_count = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), esql.azure.signinlogs.properties.incoming_token_type.values_all = VALUES(azure.signinlogs.properties.incoming_token_type), esql.azure.signinlogs.properties.app_display_name.values_all = VALUES(azure.signinlogs.properties.app_display_name), esql.source.ip.values = VALUES(source.ip), - esql.source.ip.unique_count = COUNT_DISTINCT(source.ip), + esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), esql.source.geo.country_name.values = VALUES(source.geo.country_name), - esql.source.geo.country_name.unique_count = COUNT_DISTINCT(source.geo.country_name), - esql.source.`as`.organization.name.unique_count = COUNT_DISTINCT(source.`as`.organization.name), + esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + esql.source.`as`.organization.name.distinct_count = COUNT_DISTINCT(source.`as`.organization.name), esql.timestamp.first_seen = MIN(@timestamp), esql.timestamp.last_seen = MAX(@timestamp), esql.total_attempts = COUNT() @@ -168,17 +168,17 @@ BY esql.time_window.date_trunc | EVAL esql.duration.seconds = DATE_DIFF("seconds", esql.timestamp.first_seen, esql.timestamp.last_seen), esql.brute_force.type = CASE( - esql.azure.signinlogs.properties.user_id.unique_count >= 10 AND esql.total_attempts >= 30 AND esql.azure.signinlogs.result_description.unique_count <= 3 - AND esql.source.ip.unique_count >= 5 + esql.azure.signinlogs.properties.user_id.count_distinct >= 10 AND esql.total_attempts >= 30 AND esql.azure.signinlogs.result_description.count_distinct <= 3 + AND esql.source.ip.count_distinct >= 5 AND esql.duration.seconds <= 600 - AND esql.azure.signinlogs.properties.user_id.unique_count > esql.source.ip.unique_count, + AND esql.azure.signinlogs.properties.user_id.count_distinct > esql.source.ip.count_distinct, "credential_stuffing", - esql.azure.signinlogs.properties.user_id.unique_count >= 15 AND esql.azure.signinlogs.result_description.unique_count == 1 AND esql.total_attempts >= 15 AND esql.duration.seconds <= 1800, + esql.azure.signinlogs.properties.user_id.count_distinct >= 15 AND esql.azure.signinlogs.result_description.count_distinct == 1 AND esql.total_attempts >= 15 AND esql.duration.seconds <= 1800, "password_spraying", - (esql.azure.signinlogs.properties.user_id.unique_count == 1 AND esql.azure.signinlogs.result_description.unique_count == 1 AND esql.total_attempts >= 30 AND esql.duration.seconds <= 300) - OR (esql.azure.signinlogs.properties.user_id.unique_count <= 3 AND esql.source.ip.unique_count > 30 AND esql.total_attempts >= 100), + (esql.azure.signinlogs.properties.user_id.count_distinct == 1 AND esql.azure.signinlogs.result_description.count_distinct == 1 AND esql.total_attempts >= 30 AND esql.duration.seconds <= 300) + OR (esql.azure.signinlogs.properties.user_id.count_distinct <= 3 AND esql.source.ip.count_distinct > 30 AND esql.total_attempts >= 100), "password_guessing", "other" @@ -191,20 +191,20 @@ BY esql.time_window.date_trunc esql.total_attempts, esql.timestamp.first_seen, esql.timestamp.last_seen, - esql.azure.signinlogs.properties.user_id.unique_count, + esql.azure.signinlogs.properties.user_id.count_distinct, esql.azure.signinlogs.properties.user_id.list, esql.azure.signinlogs.result_description.values_all, - esql.azure.signinlogs.result_description.unique_count, - esql.azure.signinlogs.properties.status.error_code.unique_count, + esql.azure.signinlogs.result_description.count_distinct, + esql.azure.signinlogs.properties.status.error_code.count_distinct, esql.azure.signinlogs.properties.status.error_code.values, esql.azure.signinlogs.properties.incoming_token_type.values_all, esql.azure.signinlogs.properties.app_display_name.values_all, esql.source.ip.values, - esql.source.ip.unique_count, + esql.source.ip.count_distinct, esql.source.`as`.organization.name.values, esql.source.geo.country_name.values, - esql.source.geo.country_name.unique_count, - esql.source.`as`.organization.name.unique_count, + esql.source.geo.country_name.distinct_count, + esql.source.`as`.organization.name.distinct_count, esql.azure.signinlogs.properties.authentication_requirement.values, esql.azure.signinlogs.properties.app_id.values, esql.azure.signinlogs.properties.app_display_name.values, diff --git a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml index dbe1ade5f83..a6b3b2022d0 100644 --- a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml +++ b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml @@ -118,7 +118,7 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index esql.source.geo.region_name.values = VALUES(source.geo.region_name), esql.source.address.values = VALUES(source.address), esql.source.address.count_distinct = COUNT_DISTINCT(source.address), - esql.snapshot.idesql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), esql.azure.signinlogs.properties.authentication_protocol.values = VALUES(azure.signinlogs.properties.authentication_protocol), esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), @@ -161,7 +161,7 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index esql.source.geo.region_name.values, esql.source.address.values, esql.source.address.count_distinct, - esql.snapshot.idesql.source.`as`.organization.name.values, + esql.source.`as`.organization.name.values, esql.azure.signinlogs.properties.authentication_protocol.values, esql.azure.signinlogs.properties.authentication_requirement.values, esql.azure.signinlogs.properties.is_interactive.values, From 49f4f5fc7666f2e9822c9d987c7508e8798c5b05 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Thu, 17 Jul 2025 11:42:37 -0400 Subject: [PATCH 80/94] adjusted esql to Esql --- ...otential_widespread_malware_infection.toml | 4 +- ..._access_azure_o365_with_network_alert.toml | 32 +-- ...y_ec2_multi_region_describe_instances.toml | 14 +- ..._multiple_discovery_api_calls_via_cli.toml | 16 +- ...s_multi_region_service_quota_requests.toml | 24 +-- ..._snapshot_shared_with_another_account.toml | 14 +- ..._s3_bucket_enumeration_or_brute_force.toml | 4 +- ...mpact_ec2_ebs_snapshot_access_removed.toml | 12 +- ...object_uploaded_with_ransom_extension.toml | 14 +- ...3_object_encryption_with_external_key.toml | 12 +- ...mpact_s3_static_site_js_file_uploaded.toml | 10 +- ...on_token_used_from_multiple_addresses.toml | 126 +++++------ ...al_access_signin_console_login_no_mfa.toml | 8 +- ...tratoraccess_policy_attached_to_group.toml | 8 +- ...stratoraccess_policy_attached_to_role.toml | 8 +- ...stratoraccess_policy_attached_to_user.toml | 8 +- ..._bedrock_execution_without_guardrails.toml | 10 +- ...ls_multiple_violations_by_single_user.toml | 6 +- ...multiple_violations_in_single_request.toml | 12 +- ...confidence_misconduct_blocks_detected.toml | 8 +- ...k_high_resource_consumption_detection.toml | 24 +-- ...attempts_to_use_denied_models_by_user.toml | 6 +- ...ve_information_policy_blocks_detected.toml | 6 +- ...multiple_topic_policy_blocks_detected.toml | 6 +- ...ation_exception_errors_by_single_user.toml | 10 +- ..._multiple_word_policy_blocks_detected.toml | 6 +- ..._access_azure_entra_suspicious_signin.toml | 28 +-- ...azure_entra_totp_brute_force_attempts.toml | 4 +- ...s_azure_key_vault_excessive_retrieval.toml | 195 ++++++++++++++++++ ..._access_entra_id_brute_force_activity.toml | 166 +++++++-------- ...s_entra_id_excessive_account_lockouts.toml | 152 +++++++------- ...ntra_signin_brute_force_microsoft_365.toml | 186 ++++++++--------- ...ingle_session_from_multiple_addresses.toml | 72 +++---- ...ous_oauth_flow_via_auth_broker_to_drs.toml | 152 +++++++------- ...openai_denial_of_ml_service_detection.toml | 16 +- ...ai_insecure_output_handling_detection.toml | 6 +- .../azure_openai_model_theft_detection.toml | 10 +- ...ion_onedrive_excessive_file_downloads.toml | 12 +- ...rosoft_365_excessive_account_lockouts.toml | 64 +++--- ...65_potential_user_account_brute_force.toml | 92 ++++----- ...crosoft_365_susp_oauth2_authorization.toml | 48 ++--- ..._token_hashes_for_single_okta_session.toml | 6 +- ...for_multiple_users_from_single_source.toml | 6 +- ...users_with_the_same_device_token_hash.toml | 6 +- ...e_device_token_hashes_for_single_user.toml | 6 +- ...s_started_from_different_geolocations.toml | 6 +- ...ent_egress_netcon_from_sus_executable.toml | 14 +- ...ense_evasion_base64_decoding_activity.toml | 14 +- ...anning_activity_from_compromised_host.toml | 16 +- ...anning_activity_from_compromised_host.toml | 12 +- ...nusual_file_transfer_utility_launched.toml | 10 +- ...otential_bruteforce_malware_infection.toml | 10 +- ...sistence_web_server_sus_child_spawned.toml | 10 +- ...ence_web_server_sus_command_execution.toml | 10 +- ...ential_access_rare_webdav_destination.toml | 22 +- ...nse_evasion_posh_obfuscation_backtick.toml | 10 +- ...evasion_posh_obfuscation_backtick_var.toml | 16 +- ..._evasion_posh_obfuscation_char_arrays.toml | 10 +- ...asion_posh_obfuscation_concat_dynamic.toml | 10 +- ...sh_obfuscation_high_number_proportion.toml | 20 +- ...fuscation_iex_env_vars_reconstruction.toml | 16 +- ...obfuscation_iex_string_reconstruction.toml | 16 +- ...asion_posh_obfuscation_index_reversal.toml | 16 +- ...sion_posh_obfuscation_reverse_keyword.toml | 10 +- ...vasion_posh_obfuscation_string_concat.toml | 16 +- ...vasion_posh_obfuscation_string_format.toml | 16 +- ...scation_whitespace_special_proportion.toml | 26 +-- .../discovery_command_system_account.toml | 26 +-- .../execution_posh_malicious_script_agg.toml | 16 +- ..._obfuscation_proportion_special_chars.toml | 20 +- ...sistence_web_server_sus_file_creation.toml | 10 +- 71 files changed, 1104 insertions(+), 909 deletions(-) create mode 100644 rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml diff --git a/rules/cross-platform/execution_potential_widespread_malware_infection.toml b/rules/cross-platform/execution_potential_widespread_malware_infection.toml index e02e3f18476..34abdc7950e 100644 --- a/rules/cross-platform/execution_potential_widespread_malware_infection.toml +++ b/rules/cross-platform/execution_potential_widespread_malware_infection.toml @@ -67,8 +67,8 @@ query = ''' from logs-endpoint.alerts-* | where event.code in ("malicious_file", "memory_signature", "shellcode_thread") and rule.name is not null | keep host.id, rule.name, event.code -| stats esql.host.id.count_distinct = count_distinct(host.id) by rule.name, event.code -| where esql.host.id.count_distinct >= 3 +| stats Esql.host.id.count_distinct = count_distinct(host.id) by rule.name, event.code +| where Esql.host.id.count_distinct >= 3 ''' diff --git a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml index 1e29d51fff6..34d6f745578 100644 --- a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml +++ b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml @@ -97,28 +97,28 @@ FROM logs-*, .alerts-security.* // classify each source IP based on alert type | eval - esql.source.ip.mail_access.case = case(event.dataset == "o365.audit" and event.action == "MailItemsAccessed" and event.outcome == "success", TO_IP(source.ip), null), - esql.source.ip.azure_signin.case = case(event.dataset == "azure.signinlogs" and event.outcome == "success", TO_IP(source.ip), null), - esql.source.ip.network_alert.case = case(kibana.alert.rule.name == "External Alerts" and not event.dataset in ("o365.audit", "azure.signinlogs"), TO_IP(source.ip), null) + Esql.source.ip.mail_access.case = case(event.dataset == "o365.audit" and event.action == "MailItemsAccessed" and event.outcome == "success", TO_IP(source.ip), null), + Esql.source.ip.azure_signin.case = case(event.dataset == "azure.signinlogs" and event.outcome == "success", TO_IP(source.ip), null), + Esql.source.ip.network_alert.case = case(kibana.alert.rule.name == "External Alerts" and not event.dataset in ("o365.audit", "azure.signinlogs"), TO_IP(source.ip), null) // aggregate by source IP | stats - esql.source.ip.alerts.count = count(*), - esql.source.ip.mail_access.case.count_distinct = COUNT_DISTINCT(esql.source.ip.mail_access.case), - esql.source.ip.azure_signin.case.count_distinct = COUNT_DISTINCT(esql.source.ip.azure_signin.case), - esql.source.ip.network_alert.case.count_distinct = COUNT_DISTINCT(esql.source.ip.network_alert.case), - esql.event.dataset.count_distinct = COUNT_DISTINCT(event.dataset), - esql.event.dataset.values = VALUES(event.dataset), - esql.kibana.alert.rule.name.values = VALUES(kibana.alert.rule.name), - esql.event.category.values = VALUES(event.category) - by esql.source.ip = TO_IP(source.ip) + Esql.source.ip.alerts.count = count(*), + Esql.source.ip.mail_access.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.mail_access.case), + Esql.source.ip.azure_signin.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.azure_signin.case), + Esql.source.ip.network_alert.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.network_alert.case), + Esql.event.dataset.count_distinct = COUNT_DISTINCT(event.dataset), + Esql.event.dataset.values = VALUES(event.dataset), + Esql.kibana.alert.rule.name.values = VALUES(kibana.alert.rule.name), + Esql.event.category.values = VALUES(event.category) + by Esql.source.ip = TO_IP(source.ip) // correlation condition | where - esql.source.ip.network_alert.case.count_distinct > 0 - and esql.event.dataset.count_distinct >= 2 - and (esql.source.ip.mail_access.case.count_distinct > 0 or esql.source.ip.azure_signin.case.count_distinct > 0) - and esql.source.ip.alerts.count <= 100 + Esql.source.ip.network_alert.case.count_distinct > 0 + and Esql.event.dataset.count_distinct >= 2 + and (Esql.source.ip.mail_access.case.count_distinct > 0 or Esql.source.ip.azure_signin.case.count_distinct > 0) + and Esql.source.ip.alerts.count <= 100 ''' diff --git a/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml b/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml index 15f0b19168c..69be7fe8bb1 100644 --- a/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml +++ b/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml @@ -94,22 +94,22 @@ FROM logs-aws.cloudtrail-* and event.action == "DescribeInstances" // truncate the timestamp to a 30-second window -| eval esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) +| eval Esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) // keep only the relevant raw fields -| keep esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn, cloud.region +| keep Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn, cloud.region // count the number of unique regions and total API calls within the 30-second window | stats - esql.cloud.region.count_distinct = COUNT_DISTINCT(cloud.region), - esql.event.count = COUNT(*) - by esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn + Esql.cloud.region.count_distinct = COUNT_DISTINCT(cloud.region), + Esql.event.count = COUNT(*) + by Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn // filter for resources making DescribeInstances API calls in more than 10 regions within the 30-second window -| where esql.cloud.region.count_distinct >= 10 and esql.event.count >= 10 +| where Esql.cloud.region.count_distinct >= 10 and Esql.event.count >= 10 // sort the results by time window in descending order -| sort esql.time_window.date_trunc desc +| sort Esql.time_window.date_trunc desc ''' [rule.investigation_fields] diff --git a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml index b6aa9efd6e3..9cfae804a79 100644 --- a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml +++ b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml @@ -83,7 +83,7 @@ query = ''' FROM logs-aws.cloudtrail* // create time window buckets of 10 seconds -| eval esql.time_window.date_trunc = DATE_TRUNC(10 seconds, @timestamp) +| eval Esql.time_window.date_trunc = DATE_TRUNC(10 seconds, @timestamp) | where event.dataset == "aws.cloudtrail" @@ -118,22 +118,22 @@ FROM logs-aws.cloudtrail* ) // extract owner, identity type, and actor from the ARN -| dissect aws.cloudtrail.user_identity.arn "%{}::%{esql.owner}:%{esql.identity.type}/%{esql.user.roles}" -| where STARTS_WITH(esql.user.roles, "AWSServiceRoleForConfig") != true +| dissect aws.cloudtrail.user_identity.arn "%{}::%{Esql.owner}:%{Esql.identity.type}/%{Esql.user.roles}" +| where STARTS_WITH(Esql.user.roles, "AWSServiceRoleForConfig") != true // keep relevant fields (preserving ECS fields and computed time window) -| keep @timestamp, esql.time_window.date_trunc, event.action, aws.cloudtrail.user_identity.arn +| keep @timestamp, Esql.time_window.date_trunc, event.action, aws.cloudtrail.user_identity.arn // count the number of unique API calls per time window and actor | stats - esql.event.action.count_distinct = COUNT_DISTINCT(event.action) - by esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn + Esql.event.action.count_distinct = COUNT_DISTINCT(event.action) + by Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn // filter for more than 5 unique API calls per 10s window -| where esql.event.action.count_distinct > 5 +| where Esql.event.action.count_distinct > 5 // sort the results by the number of unique API calls in descending order -| sort esql.event.action.count_distinct desc +| sort Esql.event.action.count_distinct desc ''' diff --git a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml index f748b929adf..ef2233760fa 100644 --- a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml +++ b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml @@ -44,35 +44,35 @@ FROM logs-aws.cloudtrail-* and event.action == "GetServiceQuota" // truncate the timestamp to a 30-second window -| eval esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) +| eval Esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) // dissect request parameters to extract service and quota code -| dissect aws.cloudtrail.request_parameters "{%{?esql.aws.cloudtrail.request_parameters.service_code.key}=%{esql.aws.cloudtrail.request_parameters.service_code}, %{?quota_code_key}=%{esql.aws.cloudtrail.request_parameters.quota_code}}" +| dissect aws.cloudtrail.request_parameters "{%{?Esql.aws.cloudtrail.request_parameters.service_code.key}=%{Esql.aws.cloudtrail.request_parameters.service_code}, %{?quota_code_key}=%{Esql.aws.cloudtrail.request_parameters.quota_code}}" // filter for EC2 service quota L-1216C47A (vCPU on-demand instances) -| where esql.aws.cloudtrail.request_parameters.service_code == "ec2" and esql.aws.cloudtrail.request_parameters.quota_code == "L-1216C47A" +| where Esql.aws.cloudtrail.request_parameters.service_code == "ec2" and Esql.aws.cloudtrail.request_parameters.quota_code == "L-1216C47A" // keep only the relevant fields | keep - esql.time_window.date_trunc, + Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn, cloud.region, - esql.aws.cloudtrail.request_parameters.service_code, - esql.aws.cloudtrail.request_parameters.quota_code + Esql.aws.cloudtrail.request_parameters.service_code, + Esql.aws.cloudtrail.request_parameters.quota_code // count the number of unique regions and total API calls within the time window | stats - esql.cloud.region.count_distinct = COUNT_DISTINCT(cloud.region), - esql.event.count = COUNT(*) - by esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn + Esql.cloud.region.count_distinct = COUNT_DISTINCT(cloud.region), + Esql.event.count = COUNT(*) + by Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn // filter for API calls in more than 10 regions within the 30-second window | where - esql.cloud.region.count_distinct >= 10 - and esql.event.count >= 10 + Esql.cloud.region.count_distinct >= 10 + and Esql.event.count >= 10 // sort by time window descending -| sort esql.time_window.date_trunc desc +| sort Esql.time_window.date_trunc desc ''' note = """## Triage and analysis diff --git a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml index d662de1441a..3f6114e8c55 100644 --- a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml +++ b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml @@ -88,12 +88,12 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract snapshotId, attribute type, operation type, and userId | dissect aws.cloudtrail.request_parameters - "{%{?snapshotId}=%{esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{esql.attribute.type},%{?createVolumePermission}={%{esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{esql.aws.cloudtrail.request_parameters.user.id}}]}}}" + "{%{?snapshotId}=%{Esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{Esql.attribute.type},%{?createVolumePermission}={%{Esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{Esql.aws.cloudtrail.request_parameters.user.id}}]}}}" // Check for snapshot permission added for another AWS account | where - esql.aws.cloudtrail.request_parameters.operation.type == "add" - and cloud.account.id != esql.aws.cloudtrail.request_parameters.user.id + Esql.aws.cloudtrail.request_parameters.operation.type == "add" + and cloud.account.id != Esql.aws.cloudtrail.request_parameters.user.id // Keep ECS and derived fields | keep @@ -101,10 +101,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - esql.aws.cloudtrail.request_parameters.snapshot.id, - esql.aws.cloudtrail.request_parameters.attribute.type, - esql.aws.cloudtrail.request_parameters.operation.type, - esql.aws.cloudtrail.request_parameters.user.id, + Esql.aws.cloudtrail.request_parameters.snapshot.id, + Esql.aws.cloudtrail.request_parameters.attribute.type, + Esql.aws.cloudtrail.request_parameters.operation.type, + Esql.aws.cloudtrail.request_parameters.user.id, source.ip ''' diff --git a/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml b/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml index 578323a5996..919650f1345 100644 --- a/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml +++ b/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml @@ -101,14 +101,14 @@ FROM logs-aws.cloudtrail* // Count access denied requests per server_name, source, and account | stats - esql.event.count = COUNT(*) + Esql.event.count = COUNT(*) by tls.client.server_name, source.address, cloud.account.id // Threshold: more than 40 denied requests -| where esql.event.count > 40 +| where Esql.event.count > 40 ''' diff --git a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml index 868b284a870..cc3baa2dbaa 100644 --- a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml +++ b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml @@ -85,10 +85,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Dissect parameters to extract key fields | dissect aws.cloudtrail.request_parameters - "{%{?snapshotId}=%{esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{esql.aws.cloudtrail.request_parameters.attribute.type},%{?createVolumePermission}={%{esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{esql.aws.cloudtrail.request_parameters.user.id}}]}}}" + "{%{?snapshotId}=%{Esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{Esql.aws.cloudtrail.request_parameters.attribute.type},%{?createVolumePermission}={%{Esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{Esql.aws.cloudtrail.request_parameters.user.id}}]}}}" // Match on snapshot permission **removal** -| where esql.aws.cloudtrail.request_parameters.operation.type == "remove" +| where Esql.aws.cloudtrail.request_parameters.operation.type == "remove" // Keep ECS and derived fields | keep @@ -96,10 +96,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - esql.aws.cloudtrail.request_parameters.snapshot.id, - esql.aws.cloudtrail.request_parameters.attribute.type, - esql.aws.cloudtrail.request_parameters.operation.type, - esql.aws.cloudtrail.request_parameters.user.id, + Esql.aws.cloudtrail.request_parameters.snapshot.id, + Esql.aws.cloudtrail.request_parameters.attribute.type, + Esql.aws.cloudtrail.request_parameters.operation.type, + Esql.aws.cloudtrail.request_parameters.user.id, source.address ''' diff --git a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml index b3cf027354e..85377ecb857 100644 --- a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml +++ b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml @@ -91,29 +91,29 @@ FROM logs-aws.cloudtrail-* and event.outcome == "success" // extract object key from API request parameters -| dissect aws.cloudtrail.request_parameters "%{?ignore_values}key=%{esql.aws.cloudtrail.request_parameters.object.key}}" +| dissect aws.cloudtrail.request_parameters "%{?ignore_values}key=%{Esql.aws.cloudtrail.request_parameters.object.key}}" // regex match against common ransomware naming patterns | where - esql.aws.cloudtrail.request_parameters.object.key rlike "(.*)(ransom|lock|crypt|enc|readme|how_to_decrypt|decrypt_instructions|recovery|datarescue)(.*)" - and not esql.aws.cloudtrail.request_parameters.object.key rlike "(.*)(AWSLogs|CloudTrail|access-logs)(.*)" + Esql.aws.cloudtrail.request_parameters.object.key rlike "(.*)(ransom|lock|crypt|enc|readme|how_to_decrypt|decrypt_instructions|recovery|datarescue)(.*)" + and not Esql.aws.cloudtrail.request_parameters.object.key rlike "(.*)(AWSLogs|CloudTrail|access-logs)(.*)" // keep relevant ECS and derived fields | keep tls.client.server_name, aws.cloudtrail.user_identity.arn, - esql.aws.cloudtrail.request_parameters.object.key + Esql.aws.cloudtrail.request_parameters.object.key // aggregate by server name, actor, and object key | stats - esql.event.count = COUNT(*) + Esql.event.count = COUNT(*) by tls.client.server_name, aws.cloudtrail.user_identity.arn, - esql.aws.cloudtrail.request_parameters.object.key + Esql.aws.cloudtrail.request_parameters.object.key // filter for rare single uploads (likely test/detonation) -| where esql.event.count == 1 +| where Esql.event.count == 1 ''' diff --git a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml index 95e042f502f..f4e25d37e4b 100644 --- a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml +++ b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml @@ -94,10 +94,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // dissect request parameters to extract KMS key info and target object info | dissect aws.cloudtrail.request_parameters - "{%{?bucketName}=%{esql.aws.cloudtrail.request_parameters.target.bucket.name},%{?x-amz-server-side-encryption-aws-kms-key-id}=%{?arn}:%{?aws}:%{?kms}:%{?region}:%{esql.aws.cloudtrail.request_parameters.kms.key.account.id}:%{?key}/%{esql.aws.cloudtrail.request_parameters.kms.key.id},%{?Host}=%{?tls.client.server.name},%{?x-amz-server-side-encryption}=%{?server_side_encryption},%{?x-amz-copy-source}=%{?bucket.object.name},%{?key}=%{esql.aws.cloudtrail.request_parameters.target.object.key}}" + "{%{?bucketName}=%{Esql.aws.cloudtrail.request_parameters.target.bucket.name},%{?x-amz-server-side-encryption-aws-kms-key-id}=%{?arn}:%{?aws}:%{?kms}:%{?region}:%{Esql.aws.cloudtrail.request_parameters.kms.key.account.id}:%{?key}/%{Esql.aws.cloudtrail.request_parameters.kms.key.id},%{?Host}=%{?tls.client.server.name},%{?x-amz-server-side-encryption}=%{?server_side_encryption},%{?x-amz-copy-source}=%{?bucket.object.name},%{?key}=%{Esql.aws.cloudtrail.request_parameters.target.object.key}}" // detect cross-account key usage -| where cloud.account.id != esql.aws.cloudtrail.request_parameters.kms.key.account.id +| where cloud.account.id != Esql.aws.cloudtrail.request_parameters.kms.key.account.id // keep ECS and dissected fields | keep @@ -105,10 +105,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - esql.aws.cloudtrail.request_parameters.target.bucket.name, - esql.aws.cloudtrail.request_parameters.kms.key.account.id, - esql.aws.cloudtrail.request_parameters.kms.key.id, - esql.aws.cloudtrail.request_parameters.target.object.key + Esql.aws.cloudtrail.request_parameters.target.bucket.name, + Esql.aws.cloudtrail.request_parameters.kms.key.account.id, + Esql.aws.cloudtrail.request_parameters.kms.key.id, + Esql.aws.cloudtrail.request_parameters.target.object.key ''' diff --git a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml index 334f6223546..f70709701c8 100644 --- a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml +++ b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml @@ -93,13 +93,13 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index // Extract fields from request parameters | dissect aws.cloudtrail.request_parameters - "%{{?bucket.name.key}=%{esql.aws.cloudtrail.request_parameters.bucket.name}, %{?host.key}=%{esql.aws.cloudtrail.request_parameters.host}, %{?bucket.object.location.key}=%{esql.aws.cloudtrail.request_parameters.bucket.object.location}}" + "%{{?bucket.name.key}=%{Esql.aws.cloudtrail.request_parameters.bucket.name}, %{?host.key}=%{Esql.aws.cloudtrail.request_parameters.host}, %{?bucket.object.location.key}=%{Esql.aws.cloudtrail.request_parameters.bucket.object.location}}" // Extract file name portion from full object path -| dissect esql.aws.cloudtrail.request_parameters.object.location "%{}static/js/%{esql.aws.cloudtrail.request_parameters.object.key}" +| dissect Esql.aws.cloudtrail.request_parameters.object.location "%{}static/js/%{Esql.aws.cloudtrail.request_parameters.object.key}" // Match on JavaScript files -| where ENDS_WITH(esql.aws.cloudtrail.request_parameters.object.key, ".js") +| where ENDS_WITH(Esql.aws.cloudtrail.request_parameters.object.key, ".js") // Retain relevant ECS and dissected fields | keep @@ -107,8 +107,8 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index aws.cloudtrail.user_identity.access_key_id, aws.cloudtrail.user_identity.type, aws.cloudtrail.request_parameters, - esql.aws.cloudtrail.request_parameters.bucket.name, - esql.aws.cloudtrail.request_parameters.object.key, + Esql.aws.cloudtrail.request_parameters.bucket.name, + Esql.aws.cloudtrail.request_parameters.object.key, user_agent.original, source.ip, event.action, diff --git a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml index 7d5217c9102..9f3e8461df1 100644 --- a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml +++ b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml @@ -99,78 +99,78 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index ) | EVAL - esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), - esql.user.id = aws.cloudtrail.user_identity.arn, - esql.user.access_key_id = aws.cloudtrail.user_identity.access_key_id, - esql.source.ip = source.ip, - esql.user_agent.original = user_agent.original, - esql.source.ip_string = TO_STRING(source.ip), - esql.source.ip_user_agent_pair = CONCAT(esql.source.ip_string, " - ", user_agent.original), - esql.source.ip_city_pair = CONCAT(esql.source.ip_string, " - ", source.geo.city_name), - esql.source.geo.city_name = source.geo.city_name, - esql.event.timestamp = @timestamp, - esql.source.network.org_name = `source.as.organization.name` + Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), + Esql.user.id = aws.cloudtrail.user_identity.arn, + Esql.user.access_key_id = aws.cloudtrail.user_identity.access_key_id, + Esql.source.ip = source.ip, + Esql.user_agent.original = user_agent.original, + Esql.source.ip_string = TO_STRING(source.ip), + Esql.source.ip_user_agent_pair = CONCAT(Esql.source.ip_string, " - ", user_agent.original), + Esql.source.ip_city_pair = CONCAT(Esql.source.ip_string, " - ", source.geo.city_name), + Esql.source.geo.city_name = source.geo.city_name, + Esql.event.timestamp = @timestamp, + Esql.source.network.org_name = `source.as.organization.name` | STATS - esql.event.action.values = VALUES(event.action), - esql.event.provider.values = VALUES(event.provider), - esql.user.access_key_id.values = VALUES(esql.user.access_key_id), - esql.user.id.values = VALUES(esql.user.id), - esql.source.ip.values = VALUES(esql.source.ip), - esql.user_agent.original.values = VALUES(esql.user_agent.original), - esql.source.ip_user_agent_pair.values = VALUES(esql.source.ip_user_agent_pair), - esql.source.geo.city_name.values = VALUES(esql.source.geo.city_name), - esql.source.ip_city_pair.values = VALUES(esql.source.ip_city_pair), - esql.source.network.org_name.values = VALUES(esql.source.network.org_name), - esql.source.ip.count_distinct = COUNT_DISTINCT(esql.source.ip), - esql.user_agent.original.count_distinct = COUNT_DISTINCT(esql.user_agent.original), - esql.source.geo.city_name.count_distinct = COUNT_DISTINCT(esql.source.geo.city_name), - esql.source.network.org_name.count_distinct = COUNT_DISTINCT(esql.source.network.org_name), - esql.timestamp.first_seen = MIN(esql.event.timestamp), - esql.timestamp.last_seen = MAX(esql.event.timestamp), - esql.event.count = COUNT() - BY esql.time_window.date_trunc, esql.user.access_key_id + Esql.event.action.values = VALUES(event.action), + Esql.event.provider.values = VALUES(event.provider), + Esql.user.access_key_id.values = VALUES(Esql.user.access_key_id), + Esql.user.id.values = VALUES(Esql.user.id), + Esql.source.ip.values = VALUES(Esql.source.ip), + Esql.user_agent.original.values = VALUES(Esql.user_agent.original), + Esql.source.ip_user_agent_pair.values = VALUES(Esql.source.ip_user_agent_pair), + Esql.source.geo.city_name.values = VALUES(Esql.source.geo.city_name), + Esql.source.ip_city_pair.values = VALUES(Esql.source.ip_city_pair), + Esql.source.network.org_name.values = VALUES(Esql.source.network.org_name), + Esql.source.ip.count_distinct = COUNT_DISTINCT(Esql.source.ip), + Esql.user_agent.original.count_distinct = COUNT_DISTINCT(Esql.user_agent.original), + Esql.source.geo.city_name.count_distinct = COUNT_DISTINCT(Esql.source.geo.city_name), + Esql.source.network.org_name.count_distinct = COUNT_DISTINCT(Esql.source.network.org_name), + Esql.timestamp.first_seen = MIN(Esql.event.timestamp), + Esql.timestamp.last_seen = MAX(Esql.event.timestamp), + Esql.event.count = COUNT() + BY Esql.time_window.date_trunc, Esql.user.access_key_id | EVAL - esql.activity.type = CASE( - esql.source.ip.count_distinct >= 2 AND esql.source.network.org_name.count_distinct >= 2 AND esql.source.geo.city_name.count_distinct >= 2 AND esql.user_agent.original.count_distinct >= 2, "multiple_ip_network_city_user_agent", - esql.source.ip.count_distinct >= 2 AND esql.source.network.org_name.count_distinct >= 2 AND esql.source.geo.city_name.count_distinct >= 2, "multiple_ip_network_city", - esql.source.ip.count_distinct >= 2 AND esql.source.geo.city_name.count_distinct >= 2, "multiple_ip_and_city", - esql.source.ip.count_distinct >= 2 AND esql.source.network.org_name.count_distinct >= 2, "multiple_ip_and_network", - esql.source.ip.count_distinct >= 2 AND esql.user_agent.original.count_distinct >= 2, "multiple_ip_and_user_agent", + Esql.activity.type = CASE( + Esql.source.ip.count_distinct >= 2 AND Esql.source.network.org_name.count_distinct >= 2 AND Esql.source.geo.city_name.count_distinct >= 2 AND Esql.user_agent.original.count_distinct >= 2, "multiple_ip_network_city_user_agent", + Esql.source.ip.count_distinct >= 2 AND Esql.source.network.org_name.count_distinct >= 2 AND Esql.source.geo.city_name.count_distinct >= 2, "multiple_ip_network_city", + Esql.source.ip.count_distinct >= 2 AND Esql.source.geo.city_name.count_distinct >= 2, "multiple_ip_and_city", + Esql.source.ip.count_distinct >= 2 AND Esql.source.network.org_name.count_distinct >= 2, "multiple_ip_and_network", + Esql.source.ip.count_distinct >= 2 AND Esql.user_agent.original.count_distinct >= 2, "multiple_ip_and_user_agent", "normal_activity" ), - esql.activity.fidelity_score = CASE( - esql.activity.type == "multiple_ip_network_city_user_agent", "high", - esql.activity.type == "multiple_ip_network_city", "high", - esql.activity.type == "multiple_ip_and_city", "medium", - esql.activity.type == "multiple_ip_and_network", "medium", - esql.activity.type == "multiple_ip_and_user_agent", "low" + Esql.activity.fidelity_score = CASE( + Esql.activity.type == "multiple_ip_network_city_user_agent", "high", + Esql.activity.type == "multiple_ip_network_city", "high", + Esql.activity.type == "multiple_ip_and_city", "medium", + Esql.activity.type == "multiple_ip_and_network", "medium", + Esql.activity.type == "multiple_ip_and_user_agent", "low" ) | KEEP - esql.time_window.date_trunc, - esql.activity.type, - esql.activity.fidelity_score, - esql.event.count, - esql.timestamp.first_seen, - esql.timestamp.last_seen, - esql.user.id.values, - esql.user.access_key_id.values, - esql.event.action.values, - esql.event.provider.values, - esql.source.ip.values, - esql.user_agent.original.values, - esql.source.ip_user_agent_pair.values, - esql.source.geo.city_name.values, - esql.source.ip_city_pair.values, - esql.source.network.org_name.values, - esql.source.ip.count_distinct, - esql.user_agent.original.count_distinct, - esql.source.geo.city_name.count_distinct, - esql.source.network.org_name.count_distinct - -| WHERE esql.activity.type != "normal_activity" + Esql.time_window.date_trunc, + Esql.activity.type, + Esql.activity.fidelity_score, + Esql.event.count, + Esql.timestamp.first_seen, + Esql.timestamp.last_seen, + Esql.user.id.values, + Esql.user.access_key_id.values, + Esql.event.action.values, + Esql.event.provider.values, + Esql.source.ip.values, + Esql.user_agent.original.values, + Esql.source.ip_user_agent_pair.values, + Esql.source.geo.city_name.values, + Esql.source.ip_city_pair.values, + Esql.source.network.org_name.values, + Esql.source.ip.count_distinct, + Esql.user_agent.original.count_distinct, + Esql.source.geo.city_name.count_distinct, + Esql.source.network.org_name.count_distinct + +| WHERE Esql.activity.type != "normal_activity" ''' diff --git a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml index 32050469927..41eb74dc65c 100644 --- a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml +++ b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml @@ -78,10 +78,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract mobile version and MFA usage | dissect aws.cloudtrail.additional_eventdata - "{%{?mobile_version_key}=%{esql.device.version}, %{?mfa_used_key}=%{esql.auth.mfa.used}}" + "{%{?mobile_version_key}=%{Esql.device.version}, %{?mfa_used_key}=%{Esql.auth.mfa.used}}" // Only keep events where MFA was not used -| where esql.auth.mfa.used == "No" +| where Esql.auth.mfa.used == "No" // Keep relevant ECS and dissected fields | keep @@ -89,8 +89,8 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.action, aws.cloudtrail.event_type, aws.cloudtrail.user_identity.type, - esql.device.version, - esql.auth.mfa.used + Esql.device.version, + Esql.auth.mfa.used ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml index 484bef9840d..19b9559f3ec 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml @@ -107,10 +107,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy and group details from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.aws.cloudtrail.request_parameters.policy.name},%{?groupName}=%{esql.aws.cloudtrail.request_parameters.group.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws.cloudtrail.request_parameters.policy.name},%{?groupName}=%{Esql.aws.cloudtrail.request_parameters.group.name}}" // Filter for attachment of AdministratorAccess policy -| where esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" +| where Esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" // Keep ECS and derived fields | keep @@ -118,8 +118,8 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - esql.aws.cloudtrail.request_parameters.policy.name, - esql.aws.cloudtrail.request_parameters.group.name + Esql.aws.cloudtrail.request_parameters.policy.name, + Esql.aws.cloudtrail.request_parameters.group.name ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml index 0318174eb6c..2eb6a4269a5 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml @@ -106,10 +106,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy name and role name from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.aws.cloudtrail.request_parameters.policy.name},%{?roleName}=%{esql.aws.cloudtrail.request_parameters.role.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws.cloudtrail.request_parameters.policy.name},%{?roleName}=%{Esql.aws.cloudtrail.request_parameters.role.name}}" // Filter for AdministratorAccess policy attachment -| where esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" +| where Esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" // Keep relevant ECS and dynamic fields | keep @@ -117,8 +117,8 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - esql.aws.cloudtrail.request_parameters.policy.name, - esql.aws.cloudtrail.request_parameters.role.name + Esql.aws.cloudtrail.request_parameters.policy.name, + Esql.aws.cloudtrail.request_parameters.role.name ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml index 5f1e7db14b1..c775e49efbf 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml @@ -106,10 +106,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy name and user name from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{esql.aws.cloudtrail.request_parameters.policy.name},%{?userName}=%{esql.aws.cloudtrail.request_parameters.target.user.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws.cloudtrail.request_parameters.policy.name},%{?userName}=%{Esql.aws.cloudtrail.request_parameters.target.user.name}}" // Filter for AdministratorAccess policy -| where esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" +| where Esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" // Keep ECS and parsed fields | keep @@ -118,8 +118,8 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - esql.aws.cloudtrail.request_parameters.policy.name, - esql.aws.cloudtrail.request_parameters.target.user.name, + Esql.aws.cloudtrail.request_parameters.policy.name, + Esql.aws.cloudtrail.request_parameters.target.user.name, aws.cloudtrail.request_parameters, aws.cloudtrail.user_identity.arn, related.user, diff --git a/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml b/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml index efd4eb679af..946767ac700 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml @@ -82,7 +82,7 @@ query = ''' FROM logs-aws_bedrock.invocation-* // Create 1-minute time buckets -| eval esql.time_window.date_trunc = DATE_TRUNC(1 minute, @timestamp) +| eval Esql.time_window.date_trunc = DATE_TRUNC(1 minute, @timestamp) // Filter for invocations without guardrails | where gen_ai.guardrail_id IS NULL and user.id IS NOT NULL @@ -90,19 +90,19 @@ FROM logs-aws_bedrock.invocation-* // Keep only relevant fields | keep @timestamp, - esql.time_window.date_trunc, + Esql.time_window.date_trunc, gen_ai.guardrail_id, user.id // Count number of unsafe invocations per user | stats - esql.ml.invocations.no_guardrails.count = COUNT() + Esql.ml.invocations.no_guardrails.count = COUNT() by user.id // Filter for suspicious volume -| where esql.ml.invocations.no_guardrails.count > 5 +| where Esql.ml.invocations.no_guardrails.count > 5 // Sort descending -| sort esql.ml.invocations.no_guardrails.count desc +| sort Esql.ml.invocations.no_guardrails.count desc ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml index 9e9ecdcd295..bda1d1da474 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml @@ -93,16 +93,16 @@ FROM logs-aws_bedrock.invocation-* // Count violations by user, model, and account | stats - esql.ml.violations.count = COUNT(*) + Esql.ml.violations.count = COUNT(*) by user.id, gen_ai.request.model.id, cloud.account.id // Filter for repeated violations -| where esql.ml.violations.count > 1 +| where Esql.ml.violations.count > 1 // Sort descending by violation volume -| sort esql.ml.violations.count desc +| sort Esql.ml.violations.count desc ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml index 28d134b16e5..c94ca58c4e4 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml @@ -86,29 +86,29 @@ FROM logs-aws_bedrock.invocation-* | where gen_ai.policy.action == "BLOCKED" // Count number of policy matches per request (multi-valued) -| eval esql.ml.policy.violations.mv_count = MV_COUNT(gen_ai.policy.name) +| eval Esql.ml.policy.violations.mv_count = MV_COUNT(gen_ai.policy.name) // Filter for requests with more than one policy match -| where esql.ml.policy.violations.mv_count > 1 +| where Esql.ml.policy.violations.mv_count > 1 // Keep relevant fields | keep gen_ai.policy.action, - esql.ml.policy.violations.mv_count, + Esql.ml.policy.violations.mv_count, user.id, gen_ai.request.model.id, cloud.account.id // Aggregate requests with multiple violations | stats - esql.ml.policy.violations.total_unique_requests.count = COUNT(*) + Esql.ml.policy.violations.total_unique_requests.count = COUNT(*) by - esql.ml.policy.violations.mv_count, + Esql.ml.policy.violations.mv_count, user.id, gen_ai.request.model.id, cloud.account.id // Sort by number of unique requests -| sort esql.ml.policy.violations.total_unique_requests.count desc +| sort Esql.ml.policy.violations.total_unique_requests.count desc ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml index 5cea1915614..a60b902d9b6 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml @@ -100,21 +100,21 @@ FROM logs-aws_bedrock.invocation-* // Count blocked violations per user per violation type | stats - esql.ml.policy.blocked.violation.count = COUNT() + Esql.ml.policy.blocked.violation.count = COUNT() by user.id, gen_ai.compliance.violation_code // Aggregate all violation types per user | stats - esql.ml.policy.blocked.violation.total.count = SUM(esql.ml.policy.blocked.violation.count) + Esql.ml.policy.blocked.violation.total.count = SUM(Esql.ml.policy.blocked.violation.count) by user.id // Filter for users with more than 5 total violations -| where esql.ml.policy.blocked.violation.total.count > 5 +| where Esql.ml.policy.blocked.violation.total.count > 5 // Sort by violation volume -| sort esql.ml.policy.blocked.violation.total.count desc +| sort Esql.ml.policy.blocked.violation.total.count desc ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml b/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml index e7e5a990d1a..6fbb679fad5 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml @@ -89,28 +89,28 @@ FROM logs-aws_bedrock.invocation-* // Aggregate usage metrics | stats - esql.ml.usage.prompt_tokens.max = MAX(gen_ai.usage.prompt_tokens), - esql.ml.invocations.total.count = COUNT(*), - esql.ml.usage.completion_tokens.avg = AVG(gen_ai.usage.completion_tokens) + Esql.ml.usage.prompt_tokens.max = MAX(gen_ai.usage.prompt_tokens), + Esql.ml.invocations.total.count = COUNT(*), + Esql.ml.usage.completion_tokens.avg = AVG(gen_ai.usage.completion_tokens) by user.id // Filter for suspicious usage patterns | where - esql.ml.usage.prompt_tokens.max > 5000 - and esql.ml.invocations.total.count > 10 - and esql.ml.usage.completion_tokens.avg > 500 + Esql.ml.usage.prompt_tokens.max > 5000 + and Esql.ml.invocations.total.count > 10 + and Esql.ml.usage.completion_tokens.avg > 500 // Calculate a custom risk factor -| eval esql.ml.risk.score = - (esql.ml.usage.prompt_tokens.max / 1000) * - esql.ml.invocations.total.count * - (esql.ml.usage.completion_tokens.avg / 500) +| eval Esql.ml.risk.score = + (Esql.ml.usage.prompt_tokens.max / 1000) * + Esql.ml.invocations.total.count * + (Esql.ml.usage.completion_tokens.avg / 500) // Filter on risk score -| where esql.ml.risk.score > 10 +| where Esql.ml.risk.score > 10 // Sort high risk users to top -| sort esql.ml.risk.score desc +| sort Esql.ml.risk.score desc ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml index c07f43a3231..2a4950ba9a2 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml @@ -90,17 +90,17 @@ FROM logs-aws_bedrock.invocation-* // Count total denials per user/model/account | stats - esql.ml.response.access_denied.count = COUNT(*) + Esql.ml.response.access_denied.count = COUNT(*) by user.id, gen_ai.request.model.id, cloud.account.id // Filter for users with repeated denials -| where esql.ml.response.access_denied.count > 3 +| where Esql.ml.response.access_denied.count > 3 // Sort by volume of denials -| sort esql.ml.response.access_denied.count desc +| sort Esql.ml.response.access_denied.count desc ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml index 29bbf04f808..ce9abe14b4b 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml @@ -94,13 +94,13 @@ FROM logs-aws_bedrock.invocation-* // Count how many times each user triggered a sensitive info block | stats - esql.ml.policy.blocked.sensitive_info.count = COUNT() + Esql.ml.policy.blocked.sensitive_info.count = COUNT() by user.id // Filter for users with more than 5 violations -| where esql.ml.policy.blocked.sensitive_info.count > 5 +| where Esql.ml.policy.blocked.sensitive_info.count > 5 // Sort highest to lowest -| sort esql.ml.policy.blocked.sensitive_info.count desc +| sort Esql.ml.policy.blocked.sensitive_info.count desc ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml index 2bc7c086c1b..6a61c8ed091 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml @@ -94,13 +94,13 @@ FROM logs-aws_bedrock.invocation-* // Count how many times each user triggered a blocked topic policy | stats - esql.ml.policy.blocked.topic.count = COUNT() + Esql.ml.policy.blocked.topic.count = COUNT() by user.id // Filter for excessive violations -| where esql.ml.policy.blocked.topic.count > 5 +| where Esql.ml.policy.blocked.topic.count > 5 // Sort highest to lowest -| sort esql.ml.policy.blocked.topic.count desc +| sort Esql.ml.policy.blocked.topic.count desc ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml index 5541098e2d5..8c6c6d3725d 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml @@ -85,7 +85,7 @@ query = ''' FROM logs-aws_bedrock.invocation-* // Truncate timestamp to 1-minute window -| eval esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) +| eval Esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) // Filter for validation exceptions in responses | where gen_ai.response.error_code == "ValidationException" @@ -96,18 +96,18 @@ FROM logs-aws_bedrock.invocation-* gen_ai.request.model.id, cloud.account.id, gen_ai.response.error_code, - esql.time_window.date_trunc + Esql.time_window.date_trunc // Count number of denials by user/account/time window | stats - esql.ml.response.validation_error.count = COUNT(*) + Esql.ml.response.validation_error.count = COUNT(*) by - esql.time_window.date_trunc, + Esql.time_window.date_trunc, user.id, cloud.account.id // Filter for excessive errors -| where esql.ml.response.validation_error.count > 3 +| where Esql.ml.response.validation_error.count > 3 ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml index 0877e6efcfb..ddc38d17cab 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml @@ -94,13 +94,13 @@ FROM logs-aws_bedrock.invocation-* // Count blocked profanity attempts per user | stats - esql.ml.policy.blocked.profanity.count = COUNT() + Esql.ml.policy.blocked.profanity.count = COUNT() by user.id // Filter for excessive policy violations -| where esql.ml.policy.blocked.profanity.count > 5 +| where Esql.ml.policy.blocked.profanity.count > 5 // Sort by violation volume -| sort esql.ml.policy.blocked.profanity.count desc +| sort Esql.ml.policy.blocked.profanity.count desc ''' diff --git a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml index be3180d8d3b..2c3ae6b19c5 100644 --- a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml +++ b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml @@ -91,19 +91,19 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index // Case classifications for identity usage | eval - esql.auth.device_code.case = case( + Esql.auth.device_code.case = case( azure.signinlogs.properties.authentication_protocol == "deviceCode" and azure.signinlogs.properties.authentication_requirement != "multiFactorAuthentication", azure.signinlogs.identity, null), - esql.auth.visual_studio.case = case( + Esql.auth.visual_studio.case = case( azure.signinlogs.properties.app_id == "aebc6443-996d-45c2-90f0-388ff96faa56" and azure.signinlogs.properties.resource_display_name == "Microsoft Graph", azure.signinlogs.identity, null), - esql.auth.other.case = case( + Esql.auth.other.case = case( azure.signinlogs.properties.authentication_protocol != "deviceCode" and azure.signinlogs.properties.app_id != "aebc6443-996d-45c2-90f0-388ff96faa56", azure.signinlogs.identity, @@ -111,21 +111,21 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index // Aggregate metrics by user identity | stats - esql.event.count = COUNT(*), - esql.auth.device_code.count_distinct = COUNT_DISTINCT(esql.auth.device_code.case), - esql.auth.visual_studio.count_distinct = COUNT_DISTINCT(esql.auth.visual_studio.case), - esql.auth.other.count_distinct = COUNT_DISTINCT(esql.auth.other.case), - esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - esql.source.ip.values = VALUES(source.ip), - esql.auth.client_app.values = VALUES(azure.signinlogs.properties.app_display_name), - esql.resource.display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - esql.auth.requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement) + Esql.event.count = COUNT(*), + Esql.auth.device_code.count_distinct = COUNT_DISTINCT(Esql.auth.device_code.case), + Esql.auth.visual_studio.count_distinct = COUNT_DISTINCT(Esql.auth.visual_studio.case), + Esql.auth.other.count_distinct = COUNT_DISTINCT(Esql.auth.other.case), + Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + Esql.source.ip.values = VALUES(source.ip), + Esql.auth.client_app.values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.resource.display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + Esql.auth.requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement) by azure.signinlogs.identity // Detect multiple unique IPs for one user with signs of deviceCode or VSC OAuth usage | where - esql.source.ip.count_distinct >= 2 - and (esql.auth.device_code.count_distinct > 0 or esql.auth.visual_studio.count_distinct > 0) + Esql.source.ip.count_distinct >= 2 + and (Esql.auth.device_code.count_distinct > 0 or Esql.auth.visual_studio.count_distinct > 0) ''' diff --git a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml index 47295e6828c..3e513047214 100644 --- a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml +++ b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml @@ -127,11 +127,11 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index | KEEP azure.signinlogs.properties.sign_in_identifier | STATS - esql.auth.mfa.totp_failures.count = COUNT(*) + Esql.auth.mfa.totp_failures.count = COUNT(*) BY azure.signinlogs.properties.sign_in_identifier | WHERE - esql.auth.mfa.totp_failures.count > 30 + Esql.auth.mfa.totp_failures.count > 30 ''' diff --git a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml new file mode 100644 index 00000000000..135f07d4780 --- /dev/null +++ b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml @@ -0,0 +1,195 @@ +[metadata] +creation_date = "2025/07/10" +integration = ["azure"] +maturity = "production" +updated_date = "2025/07/10" + +[rule] +author = ["Elastic"] +description = """ +Identifies excessive secret or key retrieval operations from Azure Key Vault. This rule detects when a user principal +retrieves secrets or keys from Azure Key Vault multiple times within a short time frame, which may indicate potential +abuse or unauthorized access attempts. The rule focuses on high-frequency retrieval operations that deviate from normal +user behavior, suggesting possible credential harvesting or misuse of sensitive information. +""" +false_positives = [ + """ + Service accounts or applications that frequently access Azure Key Vault for configuration or operational purposes + may trigger this rule. + """, + """ + Automated scripts or processes that retrieve secrets or keys for legitimate purposes, such as secret rotation or + application configuration, may also lead to false positives. + """, + """ + Security teams performing routine audits or assessments that involve retrieving keys or secrets from Key Vaults may + trigger this rule if they perform multiple retrievals in a short time frame. + """, +] +from = "now-9m" +interval = "8m" +language = "esql" +license = "Elastic License v2" +name = "Excessive Secret or Key Retrieval from Azure Key Vault" +note = """## Triage and analysis + +### Investigating Excessive Secret or Key Retrieval from Azure Key Vault + +Azure Key Vault is a cloud service that safeguards encryption keys and secrets like certificates, connection strings, and passwords. It is crucial for managing sensitive data in Azure environments. Unauthorized modifications to Key Vaults can lead to data breaches or service disruptions. This rule detects excessive secret or key retrieval operations from Azure Key Vault, which may indicate potential abuse or unauthorized access attempts. + +### Possible investigation steps +- Review the `azure.platformlogs.identity.claim.upn` field to identify the user principal making the retrieval requests. This can help determine if the activity is legitimate or suspicious. +- Check the `azure.platformlogs.identity.claim.appid` or `azure.platformlogs.identity.claim.appid_display_name` to identify the application or service making the requests. If the application is not recognized or authorized, it may indicate a potential security incident. It is plausible that the application is a FOCI compliant application, which are commonly abused by adversaries to evade security controls or conditional access policies. +- Analyze the `azure.platformlogs.resource.name` field to determine which Key Vault is being accessed. This can help assess the impact of the retrieval operations and whether they target sensitive resources. +- Review the `event.action` field to confirm the specific actions being performed, such as `KeyGet`, `SecretGet`, or `CertificateGet`. These actions indicate retrieval of keys, secrets, or certificates from the Key Vault. +- Check the `source.ip` or `geo.*` fields to identify the source of the retrieval requests. Look for unusual or unexpected IP addresses, especially those associated with known malicious activity or geographic locations that do not align with the user's typical behavior. +- Use the `time_window` field to analyze the frequency of retrieval operations. If multiple retrievals occur within a short time frame (e.g., within a few minutes), it may indicate excessive or suspicious activity. +- Correlate the retrieval operations with other security events or alerts in the environment to identify any patterns or related incidents. +- Triage the user with Entra ID sign-in logs to gather more context about their authentication behavior and any potential anomalies. + +### False positive analysis +- Routine administrative tasks or automated scripts may trigger excessive retrievals, especially in environments where Key Vaults are heavily utilized for application configurations or secrets management. If this is expected behavior, consider adjusting the rule or adding exceptions for specific applications or user principals. +- Legitimate applications or services may perform frequent retrievals of keys or secrets for operational purposes, such as configuration updates or secret rotation. If this is expected behavior, consider adjusting the rule or adding exceptions for specific applications or user principals. +- Security teams may perform periodic audits or assessments that involve retrieving keys or secrets from Key Vaults. If this is expected behavior, consider adjusting the rule or adding exceptions for specific user principals or applications. +- Some applications may require frequent access to keys or secrets for normal operation, leading to high retrieval counts. If this is expected behavior, consider adjusting the rule or adding exceptions for specific applications or user principals. + +### Response and remediation +- Investigate the user principal making the excessive retrieval requests to determine if they are authorized to access the Key Vault and its contents. If the user is not authorized, take appropriate actions to block their access and prevent further unauthorized retrievals. +- Review the application or service making the requests to ensure it is legitimate and authorized to access the Key Vault. If the application is unauthorized or suspicious, consider blocking it and revoking its permissions to access the Key Vault. +- Assess the impact of the excessive retrieval operations on the Key Vault and its contents. Determine if any sensitive data was accessed or compromised during the retrievals. +- Implement additional monitoring and alerting for the Key Vault to detect any further suspicious activity or unauthorized access attempts. +- Consider implementing stricter access controls or policies for Key Vaults to limit excessive retrievals and ensure that only authorized users and applications can access sensitive keys and secrets. +- Educate users and administrators about the risks associated with excessive retrievals from Key Vaults and encourage them to follow best practices for managing keys and secrets in Azure environments. +""" +references = ["https://www.inversecos.com/2022/05/detection-and-compromise-azure-key.html"] +risk_score = 43 +rule_id = "c07f7898-5dc3-11f0-9f27-f661ea17fbcd" +setup = """#### Required Azure Key Vault Diagnostic Logs + +To ensure this rule functions correctly, the following diagnostic logs must be enabled for Azure Key Vault: +- AuditEvent: This log captures all read and write operations performed on the Key Vault, including secret, key, and certificate retrievals. These logs should be streamed to the Event Hub used for the Azure integration configuration. +""" +severity = "medium" +tags = [ + "Domain: Cloud", + "Domain: Storage", + "Domain: Identity", + "Data Source: Azure", + "Data Source: Azure Platform Logs", + "Data Source: Azure Key Vault", + "Use Case: Threat Detection", + "Use Case: Identity and Access Audit", + "Tactic: Credential Access", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "esql" + +query = ''' +FROM logs-azure.platformlogs-* METADATA _id, _index + +// Filter for Azure Key Vault read operations +| WHERE event.dataset == "azure.platformlogs" + AND event.action IN ( + "VaultGet", + "KeyGet", + "KeyList", + "KeyListVersions", + "KeyGetDeleted", + "KeyListDeleted", + "SecretGet", + "SecretList", + "SecretListVersions", + "SecretGetDeleted", + "SecretListDeleted", + "CertificateGet", + "CertificateList", + "CertificateListVersions", + "CertificateGetDeleted", + "CertificateListDeleted", + "CertificatePolicyGet", + "CertificateContactsGet", + "CertificateIssuerGet", + "CertificateIssuersList" + ) + +// Truncate timestamps into 1-minute windows +| EVAL Esql.time_window.date_trunc = DATE_TRUNC(1 minute, @timestamp) + +// Aggregate identity, geo, resource, and activity info +| STATS + Esql.azure.platformlogs.identity.claim.upn.values = VALUES(azure.platformlogs.identity.claim.upn), + Esql.azure.platformlogs.identity.claim.upn.count_unique = COUNT_DISTINCT(azure.platformlogs.identity.claim.upn), + Esql.azure.platformlogs.identity.claim.appid.values = VALUES(azure.platformlogs.identity.claim.appid), + Esql.azure.platformlogs.identity.claim.objectid.values = VALUES(azure.platformlogs.identity.claim.objectid), + + Esql.source.ip.values = VALUES(source.ip), + Esql.geo.city.values = VALUES(geo.city_name), + Esql.geo.region.values = VALUES(geo.region_name), + Esql.geo.country.values = VALUES(geo.country_name), + Esql.network.as_org.values = VALUES(source.as.organization.name), + + Esql.event.actions.values = VALUES(event.action), + Esql.event.count = COUNT(*), + Esql.event.action.count_distinct = COUNT_DISTINCT(event.action), + Esql.azure.resource.name.count_distinct = COUNT_DISTINCT(azure.resource.name), + Esql.azure.resource.name.values = VALUES(azure.resource.name), + Esql.azure.platformlogs.result_type.values = VALUES(azure.platformlogs.result_type), + Esql.cloud.region.values = VALUES(cloud.region), + + Esql.agent.name.values = VALUES(agent.name), + Esql.azure.subscription_id.values = VALUES(azure.subscription_id), + Esql.azure.resource_group.values = VALUES(azure.resource.group), + Esql.azure.resource_id.values = VALUES(azure.resource.id) + +BY Esql.time_window.date_trunc, azure.platformlogs.identity.claim.upn + +// Keep relevant fields +| KEEP + Esql.time_window.date_trunc, + Esql.azure.platformlogs.identity.claim.upn.values, + Esql.azure.platformlogs.identity.claim.upn.count_unique, + Esql.azure.platformlogs.identity.claim.appid.values, + Esql.azure.platformlogs.identity.claim.objectid.values, + Esql.source.ip.values, + Esql.geo.city.values, + Esql.geo.region.values, + Esql.geo.country.values, + Esql.network.as_org.values, + Esql.event.actions.values, + Esql.event.count, + Esql.event.action.count_distinct, + Esql.azure.resource.name.count_distinct, + Esql.azure.resource.name.values, + Esql.azure.platformlogs.result_type.values, + Esql.cloud.region.values, + Esql.agent.name.values, + Esql.azure.subscription_id.values, + Esql.azure.resource_group.values, + Esql.azure.resource_id.values + +// Filter for suspiciously high volume of distinct Key Vault reads by a single actor +| WHERE Esql.azure.platformlogs.identity.claim.upn.count_unique == 1 AND Esql.event.count >= 10 AND Esql.event.action.count_distinct >= 2 + +| SORT Esql.time_window.date_trunc DESC +''' + + +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1555" +name = "Credentials from Password Stores" +reference = "https://attack.mitre.org/techniques/T1555/" +[[rule.threat.technique.subtechnique]] +id = "T1555.006" +name = "Cloud Secrets Management Stores" +reference = "https://attack.mitre.org/techniques/T1555/006/" + + + +[rule.threat.tactic] +id = "TA0006" +name = "Credential Access" +reference = "https://attack.mitre.org/tactics/TA0006/" + diff --git a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml index 837b9015540..80dce38a6eb 100644 --- a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml +++ b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml @@ -93,7 +93,7 @@ query = ''' FROM logs-azure.signinlogs* // Define a time window for grouping and maintain the original event timestamp -| EVAL esql.time_window.date_trunc = DATE_TRUNC(15 minutes, @timestamp) +| EVAL Esql.time_window.date_trunc = DATE_TRUNC(15 minutes, @timestamp) // Filter relevant failed authentication events with specific error codes | WHERE event.dataset == "azure.signinlogs" @@ -128,102 +128,102 @@ FROM logs-azure.signinlogs* AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" | STATS - esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), - esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), - esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), - esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), - esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), - esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), - esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), - esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), - esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), - esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), - esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), - esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), - esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), - esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), - esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), + Esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), + Esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), + Esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), + Esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + Esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), + Esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), + Esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), + Esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), + Esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), + Esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), + Esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), + Esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), + Esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), + Esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), - esql.azure.signinlogs.properties.user_id.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.user_id), - esql.azure.signinlogs.properties.user_id.list = VALUES(azure.signinlogs.properties.user_id), - esql.azure.signinlogs.result_description.values_all = VALUES(azure.signinlogs.result_description), - esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), - esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - esql.azure.signinlogs.properties.incoming_token_type.values_all = VALUES(azure.signinlogs.properties.incoming_token_type), - esql.azure.signinlogs.properties.app_display_name.values_all = VALUES(azure.signinlogs.properties.app_display_name), - esql.source.ip.values = VALUES(source.ip), - esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - esql.source.geo.country_name.values = VALUES(source.geo.country_name), - esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - esql.source.`as`.organization.name.distinct_count = COUNT_DISTINCT(source.`as`.organization.name), - esql.timestamp.first_seen = MIN(@timestamp), - esql.timestamp.last_seen = MAX(@timestamp), - esql.total_attempts = COUNT() -BY esql.time_window.date_trunc + Esql.azure.signinlogs.properties.user_id.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.user_id), + Esql.azure.signinlogs.properties.user_id.list = VALUES(azure.signinlogs.properties.user_id), + Esql.azure.signinlogs.result_description.values_all = VALUES(azure.signinlogs.result_description), + Esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), + Esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), + Esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + Esql.azure.signinlogs.properties.incoming_token_type.values_all = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure.signinlogs.properties.app_display_name.values_all = VALUES(azure.signinlogs.properties.app_display_name), + Esql.source.ip.values = VALUES(source.ip), + Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source.geo.country_name.values = VALUES(source.geo.country_name), + Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.source.`as`.organization.name.distinct_count = COUNT_DISTINCT(source.`as`.organization.name), + Esql.timestamp.first_seen = MIN(@timestamp), + Esql.timestamp.last_seen = MAX(@timestamp), + Esql.total_attempts = COUNT() +BY Esql.time_window.date_trunc | EVAL - esql.duration.seconds = DATE_DIFF("seconds", esql.timestamp.first_seen, esql.timestamp.last_seen), - esql.brute_force.type = CASE( - esql.azure.signinlogs.properties.user_id.count_distinct >= 10 AND esql.total_attempts >= 30 AND esql.azure.signinlogs.result_description.count_distinct <= 3 - AND esql.source.ip.count_distinct >= 5 - AND esql.duration.seconds <= 600 - AND esql.azure.signinlogs.properties.user_id.count_distinct > esql.source.ip.count_distinct, + Esql.duration.seconds = DATE_DIFF("seconds", Esql.timestamp.first_seen, Esql.timestamp.last_seen), + Esql.brute_force.type = CASE( + Esql.azure.signinlogs.properties.user_id.count_distinct >= 10 AND Esql.total_attempts >= 30 AND Esql.azure.signinlogs.result_description.count_distinct <= 3 + AND Esql.source.ip.count_distinct >= 5 + AND Esql.duration.seconds <= 600 + AND Esql.azure.signinlogs.properties.user_id.count_distinct > Esql.source.ip.count_distinct, "credential_stuffing", - esql.azure.signinlogs.properties.user_id.count_distinct >= 15 AND esql.azure.signinlogs.result_description.count_distinct == 1 AND esql.total_attempts >= 15 AND esql.duration.seconds <= 1800, + Esql.azure.signinlogs.properties.user_id.count_distinct >= 15 AND Esql.azure.signinlogs.result_description.count_distinct == 1 AND Esql.total_attempts >= 15 AND Esql.duration.seconds <= 1800, "password_spraying", - (esql.azure.signinlogs.properties.user_id.count_distinct == 1 AND esql.azure.signinlogs.result_description.count_distinct == 1 AND esql.total_attempts >= 30 AND esql.duration.seconds <= 300) - OR (esql.azure.signinlogs.properties.user_id.count_distinct <= 3 AND esql.source.ip.count_distinct > 30 AND esql.total_attempts >= 100), + (Esql.azure.signinlogs.properties.user_id.count_distinct == 1 AND Esql.azure.signinlogs.result_description.count_distinct == 1 AND Esql.total_attempts >= 30 AND Esql.duration.seconds <= 300) + OR (Esql.azure.signinlogs.properties.user_id.count_distinct <= 3 AND Esql.source.ip.count_distinct > 30 AND Esql.total_attempts >= 100), "password_guessing", "other" ) | KEEP - esql.time_window.date_trunc, - esql.brute_force.type, - esql.duration.seconds, - esql.total_attempts, - esql.timestamp.first_seen, - esql.timestamp.last_seen, - esql.azure.signinlogs.properties.user_id.count_distinct, - esql.azure.signinlogs.properties.user_id.list, - esql.azure.signinlogs.result_description.values_all, - esql.azure.signinlogs.result_description.count_distinct, - esql.azure.signinlogs.properties.status.error_code.count_distinct, - esql.azure.signinlogs.properties.status.error_code.values, - esql.azure.signinlogs.properties.incoming_token_type.values_all, - esql.azure.signinlogs.properties.app_display_name.values_all, - esql.source.ip.values, - esql.source.ip.count_distinct, - esql.source.`as`.organization.name.values, - esql.source.geo.country_name.values, - esql.source.geo.country_name.distinct_count, - esql.source.`as`.organization.name.distinct_count, - esql.azure.signinlogs.properties.authentication_requirement.values, - esql.azure.signinlogs.properties.app_id.values, - esql.azure.signinlogs.properties.app_display_name.values, - esql.azure.signinlogs.properties.resource_id.values, - esql.azure.signinlogs.properties.resource_display_name.values, - esql.azure.signinlogs.properties.conditional_access_status.values, - esql.azure.signinlogs.properties.device_detail.browser.values, - esql.azure.signinlogs.properties.device_detail.device_id.values, - esql.azure.signinlogs.properties.device_detail.operating_system.values, - esql.azure.signinlogs.properties.incoming_token_type.values, - esql.azure.signinlogs.properties.risk_state.values, - esql.azure.signinlogs.properties.session_id.values, - esql.azure.signinlogs.properties.user_id.values, - esql.azure.signinlogs.properties.user_principal_name.values, - esql.azure.signinlogs.result_description.values, - esql.azure.signinlogs.result_signature.values, - esql.azure.signinlogs.result_type.values + Esql.time_window.date_trunc, + Esql.brute_force.type, + Esql.duration.seconds, + Esql.total_attempts, + Esql.timestamp.first_seen, + Esql.timestamp.last_seen, + Esql.azure.signinlogs.properties.user_id.count_distinct, + Esql.azure.signinlogs.properties.user_id.list, + Esql.azure.signinlogs.result_description.values_all, + Esql.azure.signinlogs.result_description.count_distinct, + Esql.azure.signinlogs.properties.status.error_code.count_distinct, + Esql.azure.signinlogs.properties.status.error_code.values, + Esql.azure.signinlogs.properties.incoming_token_type.values_all, + Esql.azure.signinlogs.properties.app_display_name.values_all, + Esql.source.ip.values, + Esql.source.ip.count_distinct, + Esql.source.`as`.organization.name.values, + Esql.source.geo.country_name.values, + Esql.source.geo.country_name.distinct_count, + Esql.source.`as`.organization.name.distinct_count, + Esql.azure.signinlogs.properties.authentication_requirement.values, + Esql.azure.signinlogs.properties.app_id.values, + Esql.azure.signinlogs.properties.app_display_name.values, + Esql.azure.signinlogs.properties.resource_id.values, + Esql.azure.signinlogs.properties.resource_display_name.values, + Esql.azure.signinlogs.properties.conditional_access_status.values, + Esql.azure.signinlogs.properties.device_detail.browser.values, + Esql.azure.signinlogs.properties.device_detail.device_id.values, + Esql.azure.signinlogs.properties.device_detail.operating_system.values, + Esql.azure.signinlogs.properties.incoming_token_type.values, + Esql.azure.signinlogs.properties.risk_state.values, + Esql.azure.signinlogs.properties.session_id.values, + Esql.azure.signinlogs.properties.user_id.values, + Esql.azure.signinlogs.properties.user_principal_name.values, + Esql.azure.signinlogs.result_description.values, + Esql.azure.signinlogs.result_signature.values, + Esql.azure.signinlogs.result_type.values -| WHERE esql.brute_force.type != "other" +| WHERE Esql.brute_force.type != "other" ''' diff --git a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml index 541b9ef5ebe..de188c49828 100644 --- a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml +++ b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml @@ -87,10 +87,10 @@ query = ''' FROM logs-azure.signinlogs* | EVAL - esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), - esql.azure.signinlogs.properties.user_principal_name.lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), - esql.azure.signinlogs.properties.incoming_token_type.lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), - esql.azure.signinlogs.properties.app_display_name.lower = TO_LOWER(azure.signinlogs.properties.app_display_name) + Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), + Esql.azure.signinlogs.properties.user_principal_name.lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), + Esql.azure.signinlogs.properties.incoming_token_type.lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), + Esql.azure.signinlogs.properties.app_display_name.lower = TO_LOWER(azure.signinlogs.properties.app_display_name) | WHERE event.dataset == "azure.signinlogs" AND event.category == "authentication" @@ -103,80 +103,80 @@ FROM logs-azure.signinlogs* AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" | STATS - esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), - esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), - esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), - esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), - esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), - esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), - esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), - esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), - esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), - esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), - esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), - esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), - esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), - esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), - esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), - - esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.properties.user_principal_name.lower), - esql.azure.signinlogs.properties.user_principal_name.lower.values = VALUES(esql.azure.signinlogs.properties.user_principal_name.lower), - esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), - esql.azure.signinlogs.properties.incoming_token_type.lower.values = VALUES(esql.azure.signinlogs.properties.incoming_token_type.lower), - esql.azure.signinlogs.properties.app_display_name.lower.values = VALUES(esql.azure.signinlogs.properties.app_display_name.lower), - esql.source.ip.values = VALUES(source.ip), - esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - esql.source.geo.country_name.values = VALUES(source.geo.country_name), - esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - esql.@timestamp.min = MIN(@timestamp), - esql.@timestamp.max = MAX(@timestamp), - esql.event.count = COUNT() -BY esql.time_window.date_trunc - -| WHERE esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 AND esql.event.count >= 20 + Esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), + Esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), + Esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), + Esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + Esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), + Esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), + Esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), + Esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), + Esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), + Esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), + Esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), + Esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), + Esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), + Esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), + + Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct = COUNT_DISTINCT(Esql.azure.signinlogs.properties.user_principal_name.lower), + Esql.azure.signinlogs.properties.user_principal_name.lower.values = VALUES(Esql.azure.signinlogs.properties.user_principal_name.lower), + Esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), + Esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + Esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), + Esql.azure.signinlogs.properties.incoming_token_type.lower.values = VALUES(Esql.azure.signinlogs.properties.incoming_token_type.lower), + Esql.azure.signinlogs.properties.app_display_name.lower.values = VALUES(Esql.azure.signinlogs.properties.app_display_name.lower), + Esql.source.ip.values = VALUES(source.ip), + Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source.geo.country_name.values = VALUES(source.geo.country_name), + Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.@timestamp.min = MIN(@timestamp), + Esql.@timestamp.max = MAX(@timestamp), + Esql.event.count = COUNT() +BY Esql.time_window.date_trunc + +| WHERE Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 AND Esql.event.count >= 20 | KEEP - esql.time_window.date_trunc, - esql.event.count, - esql.@timestamp.min, - esql.@timestamp.max, - esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct, - esql.azure.signinlogs.properties.user_principal_name.lower.values, - esql.azure.signinlogs.result_description.count_distinct, - esql.azure.signinlogs.result_description.values, - esql.azure.signinlogs.properties.status.error_code.count_distinct, - esql.azure.signinlogs.properties.status.error_code.values, - esql.azure.signinlogs.properties.incoming_token_type.lower.values, - esql.azure.signinlogs.properties.app_display_name.lower.values, - esql.source.ip.values, - esql.source.ip.count_distinct, - esql.source.`as`.organization.name.values, - esql.source.`as`.organization.name.count_distinct, - esql.source.geo.country_name.values, - esql.source.geo.country_name.count_distinct, - esql.azure.signinlogs.properties.authentication_requirement.values, - esql.azure.signinlogs.properties.app_id.values, - esql.azure.signinlogs.properties.app_display_name.values, - esql.azure.signinlogs.properties.resource_id.values, - esql.azure.signinlogs.properties.resource_display_name.values, - esql.azure.signinlogs.properties.conditional_access_status.values, - esql.azure.signinlogs.properties.device_detail.browser.values, - esql.azure.signinlogs.properties.device_detail.device_id.values, - esql.azure.signinlogs.properties.device_detail.operating_system.values, - esql.azure.signinlogs.properties.incoming_token_type.values, - esql.azure.signinlogs.properties.risk_state.values, - esql.azure.signinlogs.properties.session_id.values, - esql.azure.signinlogs.properties.user_id.values, - esql.azure.signinlogs.properties.user_principal_name.values, - esql.azure.signinlogs.result_description.values, - esql.azure.signinlogs.result_signature.values, - esql.azure.signinlogs.result_type.values + Esql.time_window.date_trunc, + Esql.event.count, + Esql.@timestamp.min, + Esql.@timestamp.max, + Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct, + Esql.azure.signinlogs.properties.user_principal_name.lower.values, + Esql.azure.signinlogs.result_description.count_distinct, + Esql.azure.signinlogs.result_description.values, + Esql.azure.signinlogs.properties.status.error_code.count_distinct, + Esql.azure.signinlogs.properties.status.error_code.values, + Esql.azure.signinlogs.properties.incoming_token_type.lower.values, + Esql.azure.signinlogs.properties.app_display_name.lower.values, + Esql.source.ip.values, + Esql.source.ip.count_distinct, + Esql.source.`as`.organization.name.values, + Esql.source.`as`.organization.name.count_distinct, + Esql.source.geo.country_name.values, + Esql.source.geo.country_name.count_distinct, + Esql.azure.signinlogs.properties.authentication_requirement.values, + Esql.azure.signinlogs.properties.app_id.values, + Esql.azure.signinlogs.properties.app_display_name.values, + Esql.azure.signinlogs.properties.resource_id.values, + Esql.azure.signinlogs.properties.resource_display_name.values, + Esql.azure.signinlogs.properties.conditional_access_status.values, + Esql.azure.signinlogs.properties.device_detail.browser.values, + Esql.azure.signinlogs.properties.device_detail.device_id.values, + Esql.azure.signinlogs.properties.device_detail.operating_system.values, + Esql.azure.signinlogs.properties.incoming_token_type.values, + Esql.azure.signinlogs.properties.risk_state.values, + Esql.azure.signinlogs.properties.session_id.values, + Esql.azure.signinlogs.properties.user_id.values, + Esql.azure.signinlogs.properties.user_principal_name.values, + Esql.azure.signinlogs.result_description.values, + Esql.azure.signinlogs.result_signature.values, + Esql.azure.signinlogs.result_type.values ''' diff --git a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml index 71a84191477..acd096eea1e 100644 --- a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml +++ b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml @@ -91,11 +91,11 @@ query = ''' FROM logs-azure.signinlogs* | EVAL - esql.time_window.date_trunc = DATE_TRUNC(15 minutes, @timestamp), - esql.azure.signinlogs.properties.user_principal_name.lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), - esql.azure.signinlogs.properties.incoming_token_type.lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), - esql.azure.signinlogs.properties.app_display_name.lower = TO_LOWER(azure.signinlogs.properties.app_display_name), - esql.user_agent.original = user_agent.original + Esql.time_window.date_trunc = DATE_TRUNC(15 minutes, @timestamp), + Esql.azure.signinlogs.properties.user_principal_name.lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), + Esql.azure.signinlogs.properties.incoming_token_type.lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), + Esql.azure.signinlogs.properties.app_display_name.lower = TO_LOWER(azure.signinlogs.properties.app_display_name), + Esql.user_agent.original = user_agent.original | WHERE event.dataset == "azure.signinlogs" AND event.category == "authentication" @@ -130,108 +130,108 @@ FROM logs-azure.signinlogs* AND user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" | STATS - esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), - esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), - esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), - esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), - esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), - esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), - esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), - esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), - esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), - esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), - esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), - esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), - esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), - esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), - esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), + Esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), + Esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), + Esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), + Esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + Esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), + Esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), + Esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), + Esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), + Esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), + Esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), + Esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), + Esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), + Esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), + Esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), - esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.properties.user_principal_name.lower), - esql.azure.signinlogs.properties.user_principal_name.lower.values = VALUES(esql.azure.signinlogs.properties.user_principal_name.lower), - esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), - esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), - esql.azure.signinlogs.properties.incoming_token_type.lower.values = VALUES(esql.azure.signinlogs.properties.incoming_token_type.lower), - esql.azure.signinlogs.properties.app_display_name.lower.values = VALUES(esql.azure.signinlogs.properties.app_display_name.lower), - esql.source.ip.values = VALUES(source.ip), - esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - esql.source.geo.country_name.values = VALUES(source.geo.country_name), - esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - esql.@timestamp.min = MIN(@timestamp), - esql.@timestamp.max = MAX(@timestamp), - esql.event.count = COUNT() -BY esql.time_window.date_trunc + Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct = COUNT_DISTINCT(Esql.azure.signinlogs.properties.user_principal_name.lower), + Esql.azure.signinlogs.properties.user_principal_name.lower.values = VALUES(Esql.azure.signinlogs.properties.user_principal_name.lower), + Esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), + Esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), + Esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + Esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), + Esql.azure.signinlogs.properties.incoming_token_type.lower.values = VALUES(Esql.azure.signinlogs.properties.incoming_token_type.lower), + Esql.azure.signinlogs.properties.app_display_name.lower.values = VALUES(Esql.azure.signinlogs.properties.app_display_name.lower), + Esql.source.ip.values = VALUES(source.ip), + Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source.geo.country_name.values = VALUES(source.geo.country_name), + Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.@timestamp.min = MIN(@timestamp), + Esql.@timestamp.max = MAX(@timestamp), + Esql.event.count = COUNT() +BY Esql.time_window.date_trunc | EVAL - esql.event.duration.seconds = DATE_DIFF("seconds", esql.@timestamp.min, esql.@timestamp.max), - esql.event.bf_type = CASE( - esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 10 - AND esql.event.count >= 30 - AND esql.azure.signinlogs.result_description.count_distinct <= 3 - AND esql.source.ip.count_distinct >= 5 - AND esql.event.duration.seconds <= 600 - AND esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct > esql.source.ip.count_distinct, + Esql.event.duration.seconds = DATE_DIFF("seconds", Esql.@timestamp.min, Esql.@timestamp.max), + Esql.event.bf_type = CASE( + Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 10 + AND Esql.event.count >= 30 + AND Esql.azure.signinlogs.result_description.count_distinct <= 3 + AND Esql.source.ip.count_distinct >= 5 + AND Esql.event.duration.seconds <= 600 + AND Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct > Esql.source.ip.count_distinct, "credential_stuffing", - esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 - AND esql.azure.signinlogs.result_description.count_distinct == 1 - AND esql.event.count >= 15 - AND esql.event.duration.seconds <= 1800, + Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 + AND Esql.azure.signinlogs.result_description.count_distinct == 1 + AND Esql.event.count >= 15 + AND Esql.event.duration.seconds <= 1800, "password_spraying", - (esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct == 1 - AND esql.azure.signinlogs.result_description.count_distinct == 1 - AND esql.event.count >= 30 - AND esql.event.duration.seconds <= 300) - OR (esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct <= 3 - AND esql.source.ip.count_distinct > 30 - AND esql.event.count >= 100), + (Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct == 1 + AND Esql.azure.signinlogs.result_description.count_distinct == 1 + AND Esql.event.count >= 30 + AND Esql.event.duration.seconds <= 300) + OR (Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct <= 3 + AND Esql.source.ip.count_distinct > 30 + AND Esql.event.count >= 100), "password_guessing", "other" ) -| WHERE esql.event.bf_type != "other" +| WHERE Esql.event.bf_type != "other" | KEEP - esql.time_window.date_trunc, - esql.event.bf_type, - esql.event.duration.seconds, - esql.event.count, - esql.@timestamp.min, - esql.@timestamp.max, - esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct, - esql.azure.signinlogs.properties.user_principal_name.lower.values, - esql.azure.signinlogs.result_description.count_distinct, - esql.azure.signinlogs.result_description.values, - esql.azure.signinlogs.properties.status.error_code.count_distinct, - esql.azure.signinlogs.properties.status.error_code.values, - esql.azure.signinlogs.properties.incoming_token_type.lower.values, - esql.azure.signinlogs.properties.app_display_name.lower.values, - esql.source.ip.values, - esql.source.ip.count_distinct, - esql.source.`as`.organization.name.values, - esql.source.`as`.organization.name.count_distinct, - esql.source.geo.country_name.values, - esql.source.geo.country_name.count_distinct, - esql.azure.signinlogs.properties.authentication_requirement.values, - esql.azure.signinlogs.properties.app_id.values, - esql.azure.signinlogs.properties.app_display_name.values, - esql.azure.signinlogs.properties.resource_id.values, - esql.azure.signinlogs.properties.resource_display_name.values, - esql.azure.signinlogs.properties.conditional_access_status.values, - esql.azure.signinlogs.properties.device_detail.browser.values, - esql.azure.signinlogs.properties.device_detail.device_id.values, - esql.azure.signinlogs.properties.device_detail.operating_system.values, - esql.azure.signinlogs.properties.incoming_token_type.values, - esql.azure.signinlogs.properties.risk_state.values, - esql.azure.signinlogs.properties.session_id.values, - esql.azure.signinlogs.properties.user_id.values + Esql.time_window.date_trunc, + Esql.event.bf_type, + Esql.event.duration.seconds, + Esql.event.count, + Esql.@timestamp.min, + Esql.@timestamp.max, + Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct, + Esql.azure.signinlogs.properties.user_principal_name.lower.values, + Esql.azure.signinlogs.result_description.count_distinct, + Esql.azure.signinlogs.result_description.values, + Esql.azure.signinlogs.properties.status.error_code.count_distinct, + Esql.azure.signinlogs.properties.status.error_code.values, + Esql.azure.signinlogs.properties.incoming_token_type.lower.values, + Esql.azure.signinlogs.properties.app_display_name.lower.values, + Esql.source.ip.values, + Esql.source.ip.count_distinct, + Esql.source.`as`.organization.name.values, + Esql.source.`as`.organization.name.count_distinct, + Esql.source.geo.country_name.values, + Esql.source.geo.country_name.count_distinct, + Esql.azure.signinlogs.properties.authentication_requirement.values, + Esql.azure.signinlogs.properties.app_id.values, + Esql.azure.signinlogs.properties.app_display_name.values, + Esql.azure.signinlogs.properties.resource_id.values, + Esql.azure.signinlogs.properties.resource_display_name.values, + Esql.azure.signinlogs.properties.conditional_access_status.values, + Esql.azure.signinlogs.properties.device_detail.browser.values, + Esql.azure.signinlogs.properties.device_detail.device_id.values, + Esql.azure.signinlogs.properties.device_detail.operating_system.values, + Esql.azure.signinlogs.properties.incoming_token_type.values, + Esql.azure.signinlogs.properties.risk_state.values, + Esql.azure.signinlogs.properties.session_id.values, + Esql.azure.signinlogs.properties.user_id.values ''' diff --git a/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml b/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml index 318abdfa028..5e758594be2 100644 --- a/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml +++ b/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml @@ -99,54 +99,54 @@ FROM logs-azure.* AND azure.graphactivitylogs.properties.c_sid IS NOT NULL) | EVAL - esql.azure.signinlogs.properties.session_id.coalesce = COALESCE(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid), - esql.azure.signinlogs.properties.user_id.coalesce = COALESCE(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id), - esql.azure.signinlogs.properties.app_id.coalesce = COALESCE(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id), - esql.source.ip = source.ip, - esql.@timestamp = @timestamp, - esql.event.type.case = CASE( + Esql.azure.signinlogs.properties.session_id.coalesce = COALESCE(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid), + Esql.azure.signinlogs.properties.user_id.coalesce = COALESCE(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id), + Esql.azure.signinlogs.properties.app_id.coalesce = COALESCE(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id), + Esql.source.ip = source.ip, + Esql.@timestamp = @timestamp, + Esql.event.type.case = CASE( event.dataset == "azure.signinlogs", "signin", event.dataset == "azure.graphactivitylogs", "graph", "other" ), - esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp) + Esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp) | KEEP - esql.azure.signinlogs.properties.session_id.coalesce, - esql.source.ip, - esql.@timestamp, - esql.event.type.case, - esql.time_window.date_trunc, - esql.azure.signinlogs.properties.user_id.coalesce, - esql.azure.signinlogs.properties.app_id.coalesce + Esql.azure.signinlogs.properties.session_id.coalesce, + Esql.source.ip, + Esql.@timestamp, + Esql.event.type.case, + Esql.time_window.date_trunc, + Esql.azure.signinlogs.properties.user_id.coalesce, + Esql.azure.signinlogs.properties.app_id.coalesce | STATS - esql.azure.signinlogs.properties.user_id.coalesce.values = VALUES(esql.azure.signinlogs.properties.user_id.coalesce), - esql.azure.signinlogs.properties.session_id.coalesce.values = VALUES(esql.azure.signinlogs.properties.session_id.coalesce), - esql.source.ip.values = VALUES(esql.source.ip), - esql.source.ip.count_distinct = COUNT_DISTINCT(esql.source.ip), - esql.azure.signinlogs.properties.app_id.coalesce.values = VALUES(esql.azure.signinlogs.properties.app_id.coalesce), - esql.azure.signinlogs.properties.app_id.coalesce.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.properties.app_id.coalesce), - esql.event.type.case.values = VALUES(esql.event.type.case), - esql.event.type.case.count_distinct = COUNT_DISTINCT(esql.event.type.case), - esql.@timestamp.min = MIN(esql.@timestamp), - esql.@timestamp.max = MAX(esql.@timestamp), - esql.signin.time.min = MIN(CASE(esql.event.type.case == "signin", esql.@timestamp, NULL)), - esql.graph.time.min = MIN(CASE(esql.event.type.case == "graph", esql.@timestamp, NULL)), - esql.event.count = COUNT() - BY esql.azure.signinlogs.properties.session_id.coalesce, esql.time_window.date_trunc + Esql.azure.signinlogs.properties.user_id.coalesce.values = VALUES(Esql.azure.signinlogs.properties.user_id.coalesce), + Esql.azure.signinlogs.properties.session_id.coalesce.values = VALUES(Esql.azure.signinlogs.properties.session_id.coalesce), + Esql.source.ip.values = VALUES(Esql.source.ip), + Esql.source.ip.count_distinct = COUNT_DISTINCT(Esql.source.ip), + Esql.azure.signinlogs.properties.app_id.coalesce.values = VALUES(Esql.azure.signinlogs.properties.app_id.coalesce), + Esql.azure.signinlogs.properties.app_id.coalesce.count_distinct = COUNT_DISTINCT(Esql.azure.signinlogs.properties.app_id.coalesce), + Esql.event.type.case.values = VALUES(Esql.event.type.case), + Esql.event.type.case.count_distinct = COUNT_DISTINCT(Esql.event.type.case), + Esql.@timestamp.min = MIN(Esql.@timestamp), + Esql.@timestamp.max = MAX(Esql.@timestamp), + Esql.signin.time.min = MIN(CASE(Esql.event.type.case == "signin", Esql.@timestamp, NULL)), + Esql.graph.time.min = MIN(CASE(Esql.event.type.case == "graph", Esql.@timestamp, NULL)), + Esql.event.count = COUNT() + BY Esql.azure.signinlogs.properties.session_id.coalesce, Esql.time_window.date_trunc | EVAL - esql.event.duration_minutes.date_diff = DATE_DIFF("minutes", esql.@timestamp.min, esql.@timestamp.max), - esql.event.signin_to_graph_delay_minutes.date_diff = DATE_DIFF("minutes", esql.signin.time.min, esql.graph.time.min) + Esql.event.duration_minutes.date_diff = DATE_DIFF("minutes", Esql.@timestamp.min, Esql.@timestamp.max), + Esql.event.signin_to_graph_delay_minutes.date_diff = DATE_DIFF("minutes", Esql.signin.time.min, Esql.graph.time.min) | WHERE - esql.event.type.case.count_distinct > 1 AND - esql.source.ip.count_distinct > 1 AND - esql.event.duration_minutes.date_diff <= 5 AND - esql.signin.time.min IS NOT NULL AND - esql.graph.time.min IS NOT NULL AND - esql.event.signin_to_graph_delay_minutes.date_diff >= 0 + Esql.event.type.case.count_distinct > 1 AND + Esql.source.ip.count_distinct > 1 AND + Esql.event.duration_minutes.date_diff <= 5 AND + Esql.signin.time.min IS NOT NULL AND + Esql.graph.time.min IS NOT NULL AND + Esql.event.signin_to_graph_delay_minutes.date_diff >= 0 ''' diff --git a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml index a6b3b2022d0..b5699b2f55d 100644 --- a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml +++ b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml @@ -101,93 +101,93 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index azure.signinlogs.properties.resource_id == "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9" // DRS | EVAL - esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), - esql.azure.signinlogs.properties.session_id = azure.signinlogs.properties.session_id, - esql.is_browser.case = CASE( + Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), + Esql.azure.signinlogs.properties.session_id = azure.signinlogs.properties.session_id, + Esql.is_browser.case = CASE( TO_LOWER(azure.signinlogs.properties.device_detail.browser) RLIKE "(chrome|firefox|edge|safari).*", 1, 0 ) | STATS - esql.azure.signinlogs.properties.user_display_name.values = VALUES(azure.signinlogs.properties.user_display_name), - esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), - esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), - esql.azure.signinlogs.properties.unique_token_identifier.values = VALUES(azure.signinlogs.properties.unique_token_identifier), - - esql.source.geo.city_name.values = VALUES(source.geo.city_name), - esql.source.geo.country_name.values = VALUES(source.geo.country_name), - esql.source.geo.region_name.values = VALUES(source.geo.region_name), - esql.source.address.values = VALUES(source.address), - esql.source.address.count_distinct = COUNT_DISTINCT(source.address), - esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - - esql.azure.signinlogs.properties.authentication_protocol.values = VALUES(azure.signinlogs.properties.authentication_protocol), - esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), - esql.azure.signinlogs.properties.is_interactive.values = VALUES(azure.signinlogs.properties.is_interactive), - - esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), - esql.azure.signinlogs.properties.token_protection_status_details.sign_in_session_status.values = VALUES(azure.signinlogs.properties.token_protection_status_details.sign_in_session_status), - esql.azure.signinlogs.properties.session_id.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.session_id), - esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), - esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), - esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), - esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - - esql.azure.signinlogs.properties.app_owner_tenant_id.values = VALUES(azure.signinlogs.properties.app_owner_tenant_id), - esql.azure.signinlogs.properties.resource_owner_tenant_id.values = VALUES(azure.signinlogs.properties.resource_owner_tenant_id), - - esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), - esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), - esql.azure.signinlogs.properties.risk_level_aggregated.values = VALUES(azure.signinlogs.properties.risk_level_aggregated), - - esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), - esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - esql.user_agent.original.values = VALUES(user_agent.original), - esql.is_browser.case.max = MAX(esql.is_browser.case), - - esql.event.count = COUNT(*) + Esql.azure.signinlogs.properties.user_display_name.values = VALUES(azure.signinlogs.properties.user_display_name), + Esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), + Esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), + Esql.azure.signinlogs.properties.unique_token_identifier.values = VALUES(azure.signinlogs.properties.unique_token_identifier), + + Esql.source.geo.city_name.values = VALUES(source.geo.city_name), + Esql.source.geo.country_name.values = VALUES(source.geo.country_name), + Esql.source.geo.region_name.values = VALUES(source.geo.region_name), + Esql.source.address.values = VALUES(source.address), + Esql.source.address.count_distinct = COUNT_DISTINCT(source.address), + Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + + Esql.azure.signinlogs.properties.authentication_protocol.values = VALUES(azure.signinlogs.properties.authentication_protocol), + Esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), + Esql.azure.signinlogs.properties.is_interactive.values = VALUES(azure.signinlogs.properties.is_interactive), + + Esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure.signinlogs.properties.token_protection_status_details.sign_in_session_status.values = VALUES(azure.signinlogs.properties.token_protection_status_details.sign_in_session_status), + Esql.azure.signinlogs.properties.session_id.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.session_id), + Esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), + Esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), + Esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + + Esql.azure.signinlogs.properties.app_owner_tenant_id.values = VALUES(azure.signinlogs.properties.app_owner_tenant_id), + Esql.azure.signinlogs.properties.resource_owner_tenant_id.values = VALUES(azure.signinlogs.properties.resource_owner_tenant_id), + + Esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), + Esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), + Esql.azure.signinlogs.properties.risk_level_aggregated.values = VALUES(azure.signinlogs.properties.risk_level_aggregated), + + Esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), + Esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + Esql.user_agent.original.values = VALUES(user_agent.original), + Esql.is_browser.case.max = MAX(Esql.is_browser.case), + + Esql.event.count = COUNT(*) BY - esql.time_window.date_trunc, + Esql.time_window.date_trunc, azure.signinlogs.properties.user_principal_name, azure.signinlogs.properties.session_id | KEEP - esql.time_window.date_trunc, - esql.azure.signinlogs.properties.user_display_name.values, - esql.azure.signinlogs.properties.user_principal_name.values, - esql.azure.signinlogs.properties.session_id.values, - esql.azure.signinlogs.properties.unique_token_identifier.values, - esql.source.geo.city_name.values, - esql.source.geo.country_name.values, - esql.source.geo.region_name.values, - esql.source.address.values, - esql.source.address.count_distinct, - esql.source.`as`.organization.name.values, - esql.azure.signinlogs.properties.authentication_protocol.values, - esql.azure.signinlogs.properties.authentication_requirement.values, - esql.azure.signinlogs.properties.is_interactive.values, - esql.azure.signinlogs.properties.incoming_token_type.values, - esql.azure.signinlogs.properties.token_protection_status_details.sign_in_session_status.values, - esql.azure.signinlogs.properties.session_id.count_distinct, - esql.azure.signinlogs.properties.app_display_name.values, - esql.azure.signinlogs.properties.app_id.values, - esql.azure.signinlogs.properties.resource_id.values, - esql.azure.signinlogs.properties.resource_display_name.values, - esql.azure.signinlogs.properties.app_owner_tenant_id.values, - esql.azure.signinlogs.properties.resource_owner_tenant_id.values, - esql.azure.signinlogs.properties.conditional_access_status.values, - esql.azure.signinlogs.properties.risk_state.values, - esql.azure.signinlogs.properties.risk_level_aggregated.values, - esql.azure.signinlogs.properties.device_detail.browser.values, - esql.azure.signinlogs.properties.device_detail.operating_system.values, - esql.user_agent.original.values, - esql.is_browser.case.max, - esql.event.count + Esql.time_window.date_trunc, + Esql.azure.signinlogs.properties.user_display_name.values, + Esql.azure.signinlogs.properties.user_principal_name.values, + Esql.azure.signinlogs.properties.session_id.values, + Esql.azure.signinlogs.properties.unique_token_identifier.values, + Esql.source.geo.city_name.values, + Esql.source.geo.country_name.values, + Esql.source.geo.region_name.values, + Esql.source.address.values, + Esql.source.address.count_distinct, + Esql.source.`as`.organization.name.values, + Esql.azure.signinlogs.properties.authentication_protocol.values, + Esql.azure.signinlogs.properties.authentication_requirement.values, + Esql.azure.signinlogs.properties.is_interactive.values, + Esql.azure.signinlogs.properties.incoming_token_type.values, + Esql.azure.signinlogs.properties.token_protection_status_details.sign_in_session_status.values, + Esql.azure.signinlogs.properties.session_id.count_distinct, + Esql.azure.signinlogs.properties.app_display_name.values, + Esql.azure.signinlogs.properties.app_id.values, + Esql.azure.signinlogs.properties.resource_id.values, + Esql.azure.signinlogs.properties.resource_display_name.values, + Esql.azure.signinlogs.properties.app_owner_tenant_id.values, + Esql.azure.signinlogs.properties.resource_owner_tenant_id.values, + Esql.azure.signinlogs.properties.conditional_access_status.values, + Esql.azure.signinlogs.properties.risk_state.values, + Esql.azure.signinlogs.properties.risk_level_aggregated.values, + Esql.azure.signinlogs.properties.device_detail.browser.values, + Esql.azure.signinlogs.properties.device_detail.operating_system.values, + Esql.user_agent.original.values, + Esql.is_browser.case.max, + Esql.event.count | WHERE - esql.source.address.count_distinct >= 2 AND - esql.azure.signinlogs.properties.session_id.count_distinct == 1 AND - esql.is_browser.case.max >= 1 AND - esql.event.count >= 2 + Esql.source.address.count_distinct >= 2 AND + Esql.azure.signinlogs.properties.session_id.count_distinct == 1 AND + Esql.is_browser.case.max >= 1 AND + Esql.event.count >= 2 ''' diff --git a/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml b/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml index 0660f69f091..0d3bb16bd9e 100644 --- a/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml @@ -78,22 +78,22 @@ type = "esql" query = ''' FROM logs-azure_openai.logs-* | EVAL - esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) + Esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) | WHERE azure.open_ai.operation_name == "ChatCompletions_Create" | KEEP azure.open_ai.properties.request_length, azure.resource.name, cloud.account.id, - esql.time_window.date_trunc + Esql.time_window.date_trunc | STATS - esql.event.count = COUNT(*), - esql.azure.open_ai.properties.request_length.avg = AVG(azure.open_ai.properties.request_length) + Esql.event.count = COUNT(*), + Esql.azure.open_ai.properties.request_length.avg = AVG(azure.open_ai.properties.request_length) BY - esql.time_window.date_trunc, + Esql.time_window.date_trunc, azure.resource.name | WHERE - esql.event.count >= 10 AND - esql.azure.open_ai.properties.request_length.avg >= 5000 -| SORT esql.event.count DESC + Esql.event.count >= 10 AND + Esql.azure.open_ai.properties.request_length.avg >= 5000 +| SORT Esql.event.count DESC ''' diff --git a/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml b/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml index fc21007479e..abef4409258 100644 --- a/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml @@ -82,12 +82,12 @@ FROM logs-azure_openai.logs-* cloud.account.id, azure.resource.name | STATS - esql.event.count = COUNT(*) + Esql.event.count = COUNT(*) BY azure.resource.name | WHERE - esql.event.count >= 10 + Esql.event.count >= 10 | SORT - esql.event.count DESC + Esql.event.count DESC ''' diff --git a/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml b/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml index f707fe9f09f..352660589bb 100644 --- a/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml @@ -85,15 +85,15 @@ FROM logs-azure_openai.logs-* azure.resource.name, azure.open_ai.properties.response_length | STATS - esql.event.count = COUNT(*), - esql.azure.open_ai.properties.response_length.max = MAX(azure.open_ai.properties.response_length) + Esql.event.count = COUNT(*), + Esql.azure.open_ai.properties.response_length.max = MAX(azure.open_ai.properties.response_length) BY azure.resource.group, azure.resource.name | WHERE - esql.event.count >= 100 OR - esql.azure.open_ai.properties.response_length.max >= 1000000 + Esql.event.count >= 100 OR + Esql.azure.open_ai.properties.response_length.max >= 1000000 | SORT - esql.event.count DESC + Esql.event.count DESC ''' diff --git a/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml b/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml index f7ab0e89f0b..6429fe775a3 100644 --- a/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml +++ b/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml @@ -89,21 +89,21 @@ FROM logs-o365.audit-* o365.audit.AuthenticationType == "OAuth" AND event.outcome == "success" | EVAL - esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) + Esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) | KEEP - esql.time_window.date_trunc, + Esql.time_window.date_trunc, o365.audit.UserId, file.name, source.ip | STATS - esql.file.name.count_distinct = COUNT_DISTINCT(file.name), - esql.event.count = COUNT(*) + Esql.file.name.count_distinct = COUNT_DISTINCT(file.name), + Esql.event.count = COUNT(*) BY - esql.time_window.date_trunc, + Esql.time_window.date_trunc, o365.audit.UserId, source.ip | WHERE - esql.file.name.count_distinct >= 25 + Esql.file.name.count_distinct >= 25 ''' diff --git a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml index e58e0cec2a2..d9dd2ab9647 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml @@ -75,7 +75,7 @@ query = ''' FROM logs-o365.audit-* | MV_EXPAND event.category | EVAL - esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp) + Esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp) | WHERE event.dataset == "o365.audit" AND event.category == "authentication" AND @@ -87,40 +87,40 @@ FROM logs-o365.audit-* o365.audit.Target.Type IN ("0", "2", "6", "10") AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" | STATS - esql.o365.audit.UserId.count_distinct = COUNT_DISTINCT(TO_LOWER(o365.audit.UserId)), - esql.o365.audit.UserId.values = VALUES(TO_LOWER(o365.audit.UserId)), - esql.source.ip.values = VALUES(source.ip), - esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - esql.source.geo.country_name.values = VALUES(source.geo.country_name), - esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(TO_LOWER(o365.audit.ExtendedProperties.RequestType)), - esql.timestamp.first_seen = MIN(@timestamp), - esql.timestamp.last_seen = MAX(@timestamp), - esql.event.count = COUNT(*) - BY esql.time_window.date_trunc + Esql.o365.audit.UserId.count_distinct = COUNT_DISTINCT(TO_LOWER(o365.audit.UserId)), + Esql.o365.audit.UserId.values = VALUES(TO_LOWER(o365.audit.UserId)), + Esql.source.ip.values = VALUES(source.ip), + Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source.geo.country_name.values = VALUES(source.geo.country_name), + Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(TO_LOWER(o365.audit.ExtendedProperties.RequestType)), + Esql.timestamp.first_seen = MIN(@timestamp), + Esql.timestamp.last_seen = MAX(@timestamp), + Esql.event.count = COUNT(*) + BY Esql.time_window.date_trunc | EVAL - esql.event.duration.seconds = DATE_DIFF("seconds", esql.timestamp.first_seen, esql.timestamp.last_seen) + Esql.event.duration.seconds = DATE_DIFF("seconds", Esql.timestamp.first_seen, Esql.timestamp.last_seen) | KEEP - esql.time_window.date_trunc, - esql.o365.audit.UserId.count_distinct, - esql.o365.audit.UserId.values, - esql.source.ip.values, - esql.source.ip.count_distinct, - esql.source.`as`.organization.name.values, - esql.source.`as`.organization.name.count_distinct, - esql.source.geo.country_name.values, - esql.source.geo.country_name.count_distinct, - esql.o365.audit.ExtendedProperties.RequestType.values, - esql.timestamp.first_seen, - esql.timestamp.last_seen, - esql.event.count, - esql.event.duration.seconds + Esql.time_window.date_trunc, + Esql.o365.audit.UserId.count_distinct, + Esql.o365.audit.UserId.values, + Esql.source.ip.values, + Esql.source.ip.count_distinct, + Esql.source.`as`.organization.name.values, + Esql.source.`as`.organization.name.count_distinct, + Esql.source.geo.country_name.values, + Esql.source.geo.country_name.count_distinct, + Esql.o365.audit.ExtendedProperties.RequestType.values, + Esql.timestamp.first_seen, + Esql.timestamp.last_seen, + Esql.event.count, + Esql.event.duration.seconds | WHERE - esql.o365.audit.UserId.count_distinct >= 10 AND - esql.event.count >= 10 AND - esql.event.duration.seconds <= 300 + Esql.o365.audit.UserId.count_distinct >= 10 AND + Esql.event.count >= 10 AND + Esql.event.duration.seconds <= 300 ''' diff --git a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml index 026adb853f4..a84fae94d59 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml @@ -81,18 +81,18 @@ query = ''' FROM logs-o365.audit-* | MV_EXPAND event.category | EVAL - esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp), - esql.o365.audit.UserId.lower = TO_LOWER(o365.audit.UserId), - esql.o365.audit.LogonError = o365.audit.LogonError, - esql.o365.audit.ExtendedProperties.RequestType.lower = TO_LOWER(o365.audit.ExtendedProperties.RequestType) + Esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp), + Esql.o365.audit.UserId.lower = TO_LOWER(o365.audit.UserId), + Esql.o365.audit.LogonError = o365.audit.LogonError, + Esql.o365.audit.ExtendedProperties.RequestType.lower = TO_LOWER(o365.audit.ExtendedProperties.RequestType) | WHERE event.dataset == "o365.audit" AND event.category == "authentication" AND event.provider IN ("AzureActiveDirectory", "Exchange") AND event.action IN ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") AND - esql.o365.audit.ExtendedProperties.RequestType.lower RLIKE "(oauth.*||.*login.*)" AND - esql.o365.audit.LogonError != "IdsLocked" AND - esql.o365.audit.LogonError NOT IN ( + Esql.o365.audit.ExtendedProperties.RequestType.lower RLIKE "(oauth.*||.*login.*)" AND + Esql.o365.audit.LogonError != "IdsLocked" AND + Esql.o365.audit.LogonError NOT IN ( "EntitlementGrantsNotFound", "UserStrongAuthEnrollmentRequired", "UserStrongAuthClientAuthNRequired", @@ -103,51 +103,51 @@ FROM logs-o365.audit-* "UserStrongAuthExpired", "CmsiInterrupt" ) AND - esql.o365.audit.UserId.lower != "not available" AND + Esql.o365.audit.UserId.lower != "not available" AND o365.audit.Target.Type IN ("0", "2", "6", "10") | STATS - esql.user.id.count_distinct = COUNT_DISTINCT(esql.o365.audit.UserId.lower), - esql.o365.audit.UserId.lower.values = VALUES(esql.o365.audit.UserId.lower), - esql.o365.audit.LogonError.values = VALUES(esql.o365.audit.LogonError), - esql.o365.audit.LogonError.count_distinct = COUNT_DISTINCT(esql.o365.audit.LogonError), - esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(esql.o365.audit.ExtendedProperties.RequestType.lower), - esql.source.ip.values = VALUES(source.ip), - esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - esql.source.geo.country_name.values = VALUES(source.geo.country_name), - esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - esql.timestamp.first_seen = MIN(@timestamp), - esql.timestamp.last_seen = MAX(@timestamp), - esql.event.count = COUNT(*) - BY esql.time_window.date_trunc + Esql.user.id.count_distinct = COUNT_DISTINCT(Esql.o365.audit.UserId.lower), + Esql.o365.audit.UserId.lower.values = VALUES(Esql.o365.audit.UserId.lower), + Esql.o365.audit.LogonError.values = VALUES(Esql.o365.audit.LogonError), + Esql.o365.audit.LogonError.count_distinct = COUNT_DISTINCT(Esql.o365.audit.LogonError), + Esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(Esql.o365.audit.ExtendedProperties.RequestType.lower), + Esql.source.ip.values = VALUES(source.ip), + Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source.geo.country_name.values = VALUES(source.geo.country_name), + Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.timestamp.first_seen = MIN(@timestamp), + Esql.timestamp.last_seen = MAX(@timestamp), + Esql.event.count = COUNT(*) + BY Esql.time_window.date_trunc | EVAL - esql.event.duration.seconds = DATE_DIFF("seconds", esql.timestamp.first_seen, esql.timestamp.last_seen), - esql.brute_force.type = CASE( - esql.user.id.count_distinct >= 15 AND esql.o365.audit.LogonError.count_distinct == 1 AND esql.event.count >= 10 AND esql.event.duration.seconds <= 1800, "password_spraying", - esql.user.id.count_distinct >= 8 AND esql.event.count >= 15 AND esql.o365.audit.LogonError.count_distinct <= 3 AND esql.source.ip.count_distinct <= 5 AND esql.event.duration.seconds <= 600, "credential_stuffing", - esql.user.id.count_distinct == 1 AND esql.o365.audit.LogonError.count_distinct == 1 AND esql.event.count >= 20 AND esql.event.duration.seconds <= 300, "password_guessing", + Esql.event.duration.seconds = DATE_DIFF("seconds", Esql.timestamp.first_seen, Esql.timestamp.last_seen), + Esql.brute_force.type = CASE( + Esql.user.id.count_distinct >= 15 AND Esql.o365.audit.LogonError.count_distinct == 1 AND Esql.event.count >= 10 AND Esql.event.duration.seconds <= 1800, "password_spraying", + Esql.user.id.count_distinct >= 8 AND Esql.event.count >= 15 AND Esql.o365.audit.LogonError.count_distinct <= 3 AND Esql.source.ip.count_distinct <= 5 AND Esql.event.duration.seconds <= 600, "credential_stuffing", + Esql.user.id.count_distinct == 1 AND Esql.o365.audit.LogonError.count_distinct == 1 AND Esql.event.count >= 20 AND Esql.event.duration.seconds <= 300, "password_guessing", "other" ) | KEEP - esql.time_window.date_trunc, - esql.user.id.count_distinct, - esql.o365.audit.UserId.lower.values, - esql.o365.audit.LogonError.values, - esql.o365.audit.LogonError.count_distinct, - esql.o365.audit.ExtendedProperties.RequestType.values, - esql.source.ip.values, - esql.source.ip.count_distinct, - esql.source.`as`.organization.name.values, - esql.source.geo.country_name.values, - esql.source.geo.country_name.count_distinct, - esql.source.`as`.organization.name.count_distinct, - esql.timestamp.first_seen, - esql.timestamp.last_seen, - esql.event.duration.seconds, - esql.event.count, - esql.brute_force.type -| WHERE esql.brute_force.type != "other" + Esql.time_window.date_trunc, + Esql.user.id.count_distinct, + Esql.o365.audit.UserId.lower.values, + Esql.o365.audit.LogonError.values, + Esql.o365.audit.LogonError.count_distinct, + Esql.o365.audit.ExtendedProperties.RequestType.values, + Esql.source.ip.values, + Esql.source.ip.count_distinct, + Esql.source.`as`.organization.name.values, + Esql.source.geo.country_name.values, + Esql.source.geo.country_name.count_distinct, + Esql.source.`as`.organization.name.count_distinct, + Esql.timestamp.first_seen, + Esql.timestamp.last_seen, + Esql.event.duration.seconds, + Esql.event.count, + Esql.brute_force.type +| WHERE Esql.brute_force.type != "other" ''' diff --git a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml index 5242bfcd201..8f88f025bdf 100644 --- a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml +++ b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml @@ -79,43 +79,43 @@ FROM logs-o365.audit-* o365.audit.ApplicationId IN ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") AND o365.audit.ObjectId IN ("00000003-0000-0000-c000-000000000000") | EVAL - esql.o365.audit.ExtendedProperties.RequestType = o365.audit.ExtendedProperties.RequestType, - esql.o365.audit.ExtendedProperties.ResultStatusDetail = o365.audit.ExtendedProperties.ResultStatusDetail, - esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), - esql.oauth.authorize.user_id.case = CASE( - esql.o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" AND esql.o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", + Esql.o365.audit.ExtendedProperties.RequestType = o365.audit.ExtendedProperties.RequestType, + Esql.o365.audit.ExtendedProperties.ResultStatusDetail = o365.audit.ExtendedProperties.ResultStatusDetail, + Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), + Esql.oauth.authorize.user_id.case = CASE( + Esql.o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" AND Esql.o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", o365.audit.UserId, null ), - esql.oauth.token.user_id.case = CASE( - esql.o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", + Esql.oauth.token.user_id.case = CASE( + Esql.o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", o365.audit.UserId, null ) | STATS - esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - esql.source.ip.values = VALUES(source.ip), - esql.o365.audit.ApplicationId.values = VALUES(o365.audit.ApplicationId), - esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - esql.oauth.token.count_distinct = COUNT_DISTINCT(esql.oauth.token.user_id.case), - esql.oauth.authorize.count_distinct = COUNT_DISTINCT(esql.oauth.authorize.user_id.case) + Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + Esql.source.ip.values = VALUES(source.ip), + Esql.o365.audit.ApplicationId.values = VALUES(o365.audit.ApplicationId), + Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.oauth.token.count_distinct = COUNT_DISTINCT(Esql.oauth.token.user_id.case), + Esql.oauth.authorize.count_distinct = COUNT_DISTINCT(Esql.oauth.authorize.user_id.case) BY o365.audit.UserId, - esql.time_window.date_trunc, + Esql.time_window.date_trunc, o365.audit.ApplicationId, o365.audit.ObjectId | KEEP - esql.time_window.date_trunc, - esql.source.ip.values, - esql.source.ip.count_distinct, - esql.o365.audit.ApplicationId.values, - esql.source.`as`.organization.name.values, - esql.oauth.token.count_distinct, - esql.oauth.authorize.count_distinct + Esql.time_window.date_trunc, + Esql.source.ip.values, + Esql.source.ip.count_distinct, + Esql.o365.audit.ApplicationId.values, + Esql.source.`as`.organization.name.values, + Esql.oauth.token.count_distinct, + Esql.oauth.authorize.count_distinct | WHERE - esql.source.ip.count_distinct >= 2 AND - esql.oauth.token.count_distinct > 0 AND - esql.oauth.authorize.count_distinct > 0 + Esql.source.ip.count_distinct >= 2 AND + Esql.oauth.token.count_distinct > 0 AND + Esql.oauth.authorize.count_distinct > 0 ''' diff --git a/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml b/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml index 7cde6e2964d..0f6b0030b5f 100644 --- a/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml +++ b/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml @@ -94,14 +94,14 @@ FROM logs-okta* okta.authentication_context.external_session_id, okta.debug_context.debug_data.dt_hash | STATS - esql.okta.debug_context.debug_data.dt_hash.count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) + Esql.okta.debug_context.debug_data.dt_hash.count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) BY okta.actor.alternate_id, okta.authentication_context.external_session_id | WHERE - esql.okta.debug_context.debug_data.dt_hash.count_distinct >= 2 + Esql.okta.debug_context.debug_data.dt_hash.count_distinct >= 2 | SORT - esql.okta.debug_context.debug_data.dt_hash.count_distinct DESC + Esql.okta.debug_context.debug_data.dt_hash.count_distinct DESC ''' diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml index 8c5ea8cd1ed..271b54da350 100644 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml +++ b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml @@ -102,14 +102,14 @@ FROM logs-okta* event.action, okta.outcome.reason | STATS - esql.okta.actor.id.count_distinct = COUNT_DISTINCT(okta.actor.id) + Esql.okta.actor.id.count_distinct = COUNT_DISTINCT(okta.actor.id) BY okta.client.ip, okta.actor.alternate_id | WHERE - esql.okta.actor.id.count_distinct > 5 + Esql.okta.actor.id.count_distinct > 5 | SORT - esql.okta.actor.id.count_distinct DESC + Esql.okta.actor.id.count_distinct DESC ''' diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml index 4a6f16c90f5..de2f6fac456 100644 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml +++ b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml @@ -100,14 +100,14 @@ FROM logs-okta* okta.actor.alternate_id, okta.outcome.reason | STATS - esql.okta.actor.id.count_distinct = COUNT_DISTINCT(okta.actor.id) + Esql.okta.actor.id.count_distinct = COUNT_DISTINCT(okta.actor.id) BY okta.debug_context.debug_data.dt_hash, okta.actor.alternate_id | WHERE - esql.okta.actor.id.count_distinct > 20 + Esql.okta.actor.id.count_distinct > 20 | SORT - esql.okta.actor.id.count_distinct DESC + Esql.okta.actor.id.count_distinct DESC ''' diff --git a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml index bcf26c4cb37..1b4d33f09ab 100644 --- a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml +++ b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml @@ -105,14 +105,14 @@ FROM logs-okta* okta.debug_context.debug_data.request_uri, okta.outcome.reason | STATS - esql.okta.debug_context.debug_data.dt_hash.count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) + Esql.okta.debug_context.debug_data.dt_hash.count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) BY okta.client.ip, okta.actor.alternate_id | WHERE - esql.okta.debug_context.debug_data.dt_hash.count_distinct >= 30 + Esql.okta.debug_context.debug_data.dt_hash.count_distinct >= 30 | SORT - esql.okta.debug_context.debug_data.dt_hash.count_distinct DESC + Esql.okta.debug_context.debug_data.dt_hash.count_distinct DESC ''' diff --git a/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml b/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml index ac73641bcd7..3482b6558ab 100644 --- a/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml +++ b/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml @@ -93,12 +93,12 @@ FROM logs-okta* event.outcome, client.geo.country_name | STATS - esql.client.geo.country_name.count_distinct = COUNT_DISTINCT(client.geo.country_name) + Esql.client.geo.country_name.count_distinct = COUNT_DISTINCT(client.geo.country_name) BY okta.actor.id, okta.actor.alternate_id | WHERE - esql.client.geo.country_name.count_distinct >= 2 + Esql.client.geo.country_name.count_distinct >= 2 | SORT - esql.client.geo.country_name.count_distinct DESC + Esql.client.geo.country_name.count_distinct DESC ''' diff --git a/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml b/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml index 6c0eb3952c3..329c5960eb6 100644 --- a/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml +++ b/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml @@ -129,15 +129,15 @@ FROM logs-endpoint.events.network-* agent.id, host.name | STATS - esql.event.count = COUNT(), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), - esql.host.name.values = VALUES(host.name), - esql.agent.id.values = VALUES(agent.id) + Esql.event.count = COUNT(), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.host.name.values = VALUES(host.name), + Esql.agent.id.values = VALUES(agent.id) BY process.executable | WHERE - esql.agent.id.count_distinct == 1 AND - esql.event.count > 15 -| SORT esql.event.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.event.count > 15 +| SORT Esql.event.count ASC | LIMIT 100 ''' diff --git a/rules/linux/defense_evasion_base64_decoding_activity.toml b/rules/linux/defense_evasion_base64_decoding_activity.toml index 8273c1287a7..6b20afabfe5 100644 --- a/rules/linux/defense_evasion_base64_decoding_activity.toml +++ b/rules/linux/defense_evasion_base64_decoding_activity.toml @@ -143,15 +143,15 @@ FROM logs-endpoint.events.process-* agent.id, host.name | STATS - esql.event.count = COUNT(), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), - esql.host.name.values = VALUES(host.name), - esql.agent.id.values = VALUES(agent.id) + Esql.event.count = COUNT(), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.host.name.values = VALUES(host.name), + Esql.agent.id.values = VALUES(agent.id) BY process.name, process.command_line | WHERE - esql.agent.id.count_distinct == 1 AND - esql.event.count < 15 -| SORT esql.event.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.event.count < 15 +| SORT Esql.event.count ASC | LIMIT 100 ''' diff --git a/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml index 0670600c69d..4c153bf5561 100644 --- a/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml @@ -112,16 +112,16 @@ FROM logs-endpoint.events.network-* agent.id, host.name | STATS - esql.event.count = COUNT(), - esql.destination.port.count_distinct = COUNT_DISTINCT(destination.port), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), - esql.host.name.values = VALUES(host.name), - esql.agent.id.values = VALUES(agent.id) + Esql.event.count = COUNT(), + Esql.destination.port.count_distinct = COUNT_DISTINCT(destination.port), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.host.name.values = VALUES(host.name), + Esql.agent.id.values = VALUES(agent.id) BY process.executable, destination.ip | WHERE - esql.agent.id.count_distinct == 1 AND - esql.destination.port.count_distinct > 100 -| SORT esql.event.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.destination.port.count_distinct > 100 +| SORT Esql.event.count ASC | LIMIT 100 ''' diff --git a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml index 89fec2254cf..765ba1c7963 100644 --- a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml @@ -102,16 +102,16 @@ FROM logs-endpoint.events.network-* event.type == "start" AND event.action == "connection_attempted" | STATS - esql.connection.count = COUNT(), - esql.destination.ip.count_distinct = COUNT_DISTINCT(destination.ip), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.connection.count = COUNT(), + Esql.destination.ip.count_distinct = COUNT_DISTINCT(destination.ip), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable | WHERE - esql.agent.id.count_distinct == 1 AND - esql.destination.ip.count_distinct > 250 -| SORT esql.connection.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.destination.ip.count_distinct > 250 +| SORT Esql.connection.count ASC | LIMIT 100 ''' diff --git a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml index c96db12650d..894597feaa4 100644 --- a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml +++ b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml @@ -102,15 +102,15 @@ FROM logs-endpoint.events.process-* event.action == "exec" AND process.name IN ("scp", "ftp", "sftp", "vsftpd", "sftp-server", "rsync") | STATS - esql.event.count = COUNT(), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event.count = COUNT(), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable, process.parent.executable, process.command_line | WHERE - esql.agent.id.count_distinct == 1 AND - esql.event.count < 5 -| SORT esql.event.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.event.count < 5 +| SORT Esql.event.count ASC | LIMIT 100 ''' diff --git a/rules/linux/impact_potential_bruteforce_malware_infection.toml b/rules/linux/impact_potential_bruteforce_malware_infection.toml index 7cafb07e37f..2fba52d92be 100644 --- a/rules/linux/impact_potential_bruteforce_malware_infection.toml +++ b/rules/linux/impact_potential_bruteforce_malware_infection.toml @@ -115,15 +115,15 @@ FROM logs-endpoint.events.network-* "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1", "FE80::/10", "FF00::/8" ) | STATS - esql.event.count = COUNT(), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event.count = COUNT(), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable, destination.port | WHERE - esql.agent.id.count_distinct == 1 AND - esql.event.count > 15 -| SORT esql.event.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.event.count > 15 +| SORT Esql.event.count ASC | LIMIT 100 ''' diff --git a/rules/linux/persistence_web_server_sus_child_spawned.toml b/rules/linux/persistence_web_server_sus_child_spawned.toml index 6b4e294ae84..2a0469b5569 100644 --- a/rules/linux/persistence_web_server_sus_child_spawned.toml +++ b/rules/linux/persistence_web_server_sus_child_spawned.toml @@ -137,15 +137,15 @@ FROM logs-endpoint.events.process-* process.parent.executable LIKE "/vscode/vscode-server/*" ) | STATS - esql.event.count = COUNT(), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event.count = COUNT(), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable, process.working_directory, process.parent.executable | WHERE - esql.agent.id.count_distinct == 1 AND - esql.event.count < 5 -| SORT esql.event.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.event.count < 5 +| SORT Esql.event.count ASC | LIMIT 100 ''' diff --git a/rules/linux/persistence_web_server_sus_command_execution.toml b/rules/linux/persistence_web_server_sus_command_execution.toml index 0b881bf66bf..0f8765de6ba 100644 --- a/rules/linux/persistence_web_server_sus_command_execution.toml +++ b/rules/linux/persistence_web_server_sus_command_execution.toml @@ -147,15 +147,15 @@ FROM logs-endpoint.events.process-* process.parent.executable == "/usr/bin/xfce4-terminal" ) | STATS - esql.event.count = COUNT(), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event.count = COUNT(), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.command_line, process.working_directory, process.parent.executable | WHERE - esql.agent.id.count_distinct == 1 AND - esql.event.count < 5 -| SORT esql.event.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.event.count < 5 +| SORT Esql.event.count ASC | LIMIT 100 ''' diff --git a/rules/windows/credential_access_rare_webdav_destination.toml b/rules/windows/credential_access_rare_webdav_destination.toml index 9786204ae4b..a74be2b0cfc 100644 --- a/rules/windows/credential_access_rare_webdav_destination.toml +++ b/rules/windows/credential_access_rare_webdav_destination.toml @@ -63,23 +63,23 @@ FROM logs-* process.command_line LIKE "*DavSetCookie*" | KEEP host.id, process.command_line, user.name | GROK - process.command_line """(?DavSetCookie .* http)""" + process.command_line """(?DavSetCookie .* http)""" | EVAL - esql.server.webdav.cookie.replace = REPLACE(esql.server.webdav.cookie, "(DavSetCookie | http)", "") + Esql.server.webdav.cookie.replace = REPLACE(Esql.server.webdav.cookie, "(DavSetCookie | http)", "") | WHERE - esql.server.webdav.cookie.replace IS NOT NULL AND - esql.server.webdav.cookie.replace RLIKE """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" AND - NOT esql.server.webdav.cookie.replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND - NOT esql.server.webdav.cookie.replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" + Esql.server.webdav.cookie.replace IS NOT NULL AND + Esql.server.webdav.cookie.replace RLIKE """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" AND + NOT Esql.server.webdav.cookie.replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND + NOT Esql.server.webdav.cookie.replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" | STATS - esql.total.count = COUNT(*), - esql.host.id.count_distinct = COUNT_DISTINCT(host.id), + Esql.total.count = COUNT(*), + Esql.host.id.count_distinct = COUNT_DISTINCT(host.id), host.id.values = VALUES(host.id), user.name.values = VALUES(user.name) - BY esql.server.webdav.cookie.replace + BY Esql.server.webdav.cookie.replace | WHERE - esql.host.id.count_distinct == 1 AND - esql.total.count <= 3 + Esql.host.id.count_distinct == 1 AND + Esql.total.count <= 3 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml index c9736c061e1..e6a86b84651 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml @@ -97,15 +97,15 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") +| EVAL Esql.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.script_block_text.character.count = LENGTH(esql.script_block_text.replace) - LENGTH(REPLACE(esql.script_block_text.replace, "🔥", "")) +| EVAL Esql.script_block_text.character.count = LENGTH(Esql.script_block_text.replace) - LENGTH(REPLACE(Esql.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.script_block_text.character.count, - esql.script_block_text.replace, + Esql.script_block_text.character.count, + Esql.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.name, @@ -118,7 +118,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id, user.id -| WHERE esql.script_block_text.character.count >= 10 +| WHERE Esql.script_block_text.character.count >= 10 // Filter FPs, and due to the behavior of the LIKE operator, allow null values | WHERE (file.name NOT LIKE "TSS_*.psm1" OR file.name IS NULL) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index 7fb56031298..4c9d8ef2998 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -88,21 +88,21 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL esql.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE esql.script_block_text.length > 500 +| EVAL Esql.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.script_block_text.length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.replace.length, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.replace.length, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -115,7 +115,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id, user.id -| WHERE esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell.file.script_block_text.character.count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml index b7b42860f4b..59cf18827da 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml @@ -94,19 +94,19 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( powershell.file.script_block_text, """(char\[\]\]\(\d+,\d+[^)]+|(\s?\(\[char\]\d+\s?\)\+){2,})""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -119,7 +119,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Final filter on match count -| WHERE esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell.file.script_block_text.character.count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml index c64ee620629..ac75142d866 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml @@ -88,19 +88,19 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace suspicious method string constructions with 🔥 for entropy-style detection // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( powershell.file.script_block_text, """[.&]\(\s*(['"][A-Za-z0-9.-]+['"]\s*\+\s*)+['"][A-Za-z0-9.-]+['"]\s*\)""", "🔥" ) // Count how many patterns were detected based on 🔥 characters -| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep relevant context fields for triage | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -113,7 +113,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Alert if suspicious pattern is present -| WHERE esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell.file.script_block_text.character.count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml index 0380216db2f..4c2f98d6025 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml @@ -87,22 +87,22 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Measure script length -| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE esql.powershell.file.script_block_text.length > 1000 +| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell.file.script_block_text.length > 1000 // Replace digits with 🔥 for numeric pattern density analysis -| EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[0-9]""", "🔥") +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[0-9]""", "🔥") // Count numeric characters and calculate proportion of total -| EVAL esql.powershell.file.script_block_text.character.count = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) -| EVAL esql.powershell.file.script_block_text.character.ratio = esql.powershell.file.script_block_text.character.count::double / esql.powershell.file.script_block_text.length::double +| EVAL Esql.powershell.file.script_block_text.character.count = Esql.powershell.file.script_block_text.length - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.ratio = Esql.powershell.file.script_block_text.character.count::double / Esql.powershell.file.script_block_text.length::double // Retain relevant fields for investigation | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.character.ratio, - esql.powershell.file.script_block_text.length, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.character.ratio, + Esql.powershell.file.script_block_text.length, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -115,7 +115,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for suspiciously high numeric content -| WHERE esql.powershell.file.script_block_text.character.ratio > 0.30 +| WHERE Esql.powershell.file.script_block_text.character.ratio > 0.30 // Exclude noisy patterns such as 64-character hashes | WHERE NOT powershell.file.script_block_text RLIKE """.*\"[a-fA-F0-9]{64}\"\,.*""" diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml index 998704ccff5..86baabf2712 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml @@ -87,24 +87,24 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Evaluate the length of the script block -| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell.file.script_block_text.length > 500 // Replace obfuscated dynamic string construction with 🔥 to count obfuscation attempts -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( powershell.file.script_block_text, """(?i)(\$(?:\w+|\w+\:\w+)\[\d++\]\+\$(?:\w+|\w+\:\w+)\[\d++\]\+['"]x['"]|\$(?:\w+\:\w+)\[\d++,\d++,\d++\]|\.name\[\d++,\d++,\d++\])""", "🔥" ) // Count how many 🔥 were inserted -| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep relevant context fields | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.length, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.length, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -117,7 +117,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for meaningful pattern matches -| WHERE esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell.file.script_block_text.character.count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml index 8ba089fb18a..baf64e93837 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml @@ -88,24 +88,24 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Check script length > 500 -| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell.file.script_block_text.length > 500 // Replace method access patterns with 🔥 for detection -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( powershell.file.script_block_text, """(?i)['"]['"].(Insert|Normalize|Chars|SubString|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", "🔥" ) // Count 🔥 instances to determine presence of suspicious method usage -| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep relevant fields for context and triage | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.length, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.length, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -118,7 +118,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Only return if at least one pattern is found -| WHERE esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell.file.script_block_text.character.count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml index 6f3735d46d8..adbee1e6730 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml @@ -91,25 +91,25 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell.file.script_block_text.length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( powershell.file.script_block_text, """\$\w+\[\-\s?1\.\.""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.length, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.length, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -122,7 +122,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for patterns found at least once -| WHERE esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell.file.script_block_text.character.count >= 1 // FP Patterns | WHERE NOT powershell.file.script_block_text LIKE "*GENESIS-5654*" diff --git a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml index e5621c4a2b7..f9ecf62c07a 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml @@ -93,19 +93,19 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( powershell.file.script_block_text, """(?i)(rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman\.|:vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod)""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -116,7 +116,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id // Filter for scripts with at least 2 suspicious keyword matches -| WHERE esql.powershell.file.script_block_text.character.count >= 2 +| WHERE Esql.powershell.file.script_block_text.character.count >= 2 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml index f79de9064db..bf9d6c1d1e4 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml @@ -89,25 +89,25 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell.file.script_block_text.length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( powershell.file.script_block_text, """['"][A-Za-z0-9.]+['"](\s?\+\s?['"][A-Za-z0-9.,\-\s]+['"]){2,}""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.powershell.file.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.length, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.length, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -120,7 +120,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with at least 2 suspicious string concatenations -| WHERE esql.powershell.file.script_block_text.character.count >= 2 +| WHERE Esql.powershell.file.script_block_text.character.count >= 2 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index 1269e80e628..35ae1e545dd 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -88,26 +88,26 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell.file.script_block_text.length > 500 | WHERE powershell.file.script_block_text LIKE "*{0}*" // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( powershell.file.script_block_text, """((\{\d+\}){2,}["']\s?-f|::Format[^\{]+(\{\d+\}){2,})""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL esql.powershell.script_block_text.character.count = LENGTH(esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.powershell.script_block_text.character.count, - esql.powershell.file.script_block_text.length, - esql.powershell.file.script_block_text.replace, + Esql.powershell.script_block_text.character.count, + Esql.powershell.file.script_block_text.length, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -121,7 +121,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with more than 3 suspicious format patterns -| WHERE esql.powershell.script_block_text.character.count > 3 +| WHERE Esql.powershell.script_block_text.character.count > 3 // Exclude Noisy Patterns diff --git a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml index 5c9c6e125a4..0ebec4050ce 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml @@ -89,30 +89,30 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Replace repeated spaces used for formatting after a new line with a single space to reduce FPs -| EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\n\s+""", "\n ") +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\n\s+""", "\n ") // Look for scripts with more than 1000 chars -| EVAL esql.powershell.file.script_block_text.replace.length = LENGTH(esql.powershell.file.script_block_text.replace) -| WHERE esql.powershell.file.script_block_text.replace.length > 1000 +| EVAL Esql.powershell.file.script_block_text.replace.length = LENGTH(Esql.powershell.file.script_block_text.replace) +| WHERE Esql.powershell.file.script_block_text.replace.length > 1000 // Replace format characters with 🔥 to count suspicious formatting density -| EVAL esql.powershell.file.script_block_text.replace = REPLACE( - esql.powershell.file.script_block_text.replace, +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( + Esql.powershell.file.script_block_text.replace, """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\.]""", "🔥" ) // Count 🔥 and calculate its proportion of the script -| EVAL esql.powershell.file.script_block_text.count = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) -| EVAL esql.powershell.file.script_block_text.ratio = esql.powershell.file.script_block_text.count::double / esql.powershell.file.script_block_text.length::double +| EVAL Esql.powershell.file.script_block_text.count = Esql.powershell.file.script_block_text.length - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.ratio = Esql.powershell.file.script_block_text.count::double / Esql.powershell.file.script_block_text.length::double // Retain fields for context or alert generation | KEEP - esql.powershell.file.script_block_text.count, - esql.powershell.file.script_block_text.length, - esql.powershell.file.script_block_text.ratio, - esql.powershell.file.script_block_text.replace, - esql.powershell.file.script_block_text.replace.length, + Esql.powershell.file.script_block_text.count, + Esql.powershell.file.script_block_text.length, + Esql.powershell.file.script_block_text.ratio, + Esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.replace.length, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -125,7 +125,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for high-ratio suspicious formatting scripts -| WHERE esql.powershell.file.script_block_text.ratio > 0.75 +| WHERE Esql.powershell.file.script_block_text.ratio > 0.75 ''' diff --git a/rules/windows/discovery_command_system_account.toml b/rules/windows/discovery_command_system_account.toml index 03d5ecdf531..15445beca1e 100644 --- a/rules/windows/discovery_command_system_account.toml +++ b/rules/windows/discovery_command_system_account.toml @@ -77,19 +77,19 @@ process where host.os.type == "windows" and event.type == "start" and ( process.name : "net1.exe" and not process.parent.name : "net.exe" and not process.args : ("start", "stop", "/active:*") ) - ) and -process.parent.executable != null and -not (process.name : "net1.exe" and process.working_directory : "C:\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\Downloads\\") and -not process.parent.executable : - ("C:\\Program Files\\Microsoft Monitoring Agent\\Agent\\MonitoringHost.exe", - "C:\\Program Files\\Dell\\SupportAssistAgent\\SRE\\SRE.exe", - "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", - "C:\\Windows\\Temp\\WinGet\\defaultState\\PostgreSQL.PostgreSQL*\\postgresql-*-windows-x64.exe", - "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", - "C:\\Program Files (x86)\\SolarWinds\\Agent\\Plugins\\JobEngine\\SWJobEngineWorker2.exe") and -not (process.parent.executable : "C:\\Windows\\Sys?????\\WindowsPowerShell\\v1.0\\powershell.exe" and - process.parent.args : ("C:\\Program Files (x86)\\Microsoft Intune Management Extension\\*.ps1", - "Agent\\Modules\\AdHealthConfiguration\\AdHealthConfiguration.psd1'")) and + ) and +process.parent.executable != null and +not (process.name : "net1.exe" and process.working_directory : "C:\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\Downloads\\") and +not process.parent.executable : + ("C:\\Program Files\\Microsoft Monitoring Agent\\Agent\\MonitoringHost.exe", + "C:\\Program Files\\Dell\\SupportAssistAgent\\SRE\\SRE.exe", + "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", + "C:\\Windows\\Temp\\WinGet\\defaultState\\PostgrEsql.PostgreSQL*\\postgresql-*-windows-x64.exe", + "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", + "C:\\Program Files (x86)\\SolarWinds\\Agent\\Plugins\\JobEngine\\SWJobEngineWorker2.exe") and +not (process.parent.executable : "C:\\Windows\\Sys?????\\WindowsPowerShell\\v1.0\\powershell.exe" and + process.parent.args : ("C:\\Program Files (x86)\\Microsoft Intune Management Extension\\*.ps1", + "Agent\\Modules\\AdHealthConfiguration\\AdHealthConfiguration.psd1'")) and not (process.parent.name : "cmd.exe" and process.working_directory : "C:\\Program Files\\Infraon Corp\\SecuraAgent\\") ''' diff --git a/rules/windows/execution_posh_malicious_script_agg.toml b/rules/windows/execution_posh_malicious_script_agg.toml index 20706405e33..ba00893252f 100644 --- a/rules/windows/execution_posh_malicious_script_agg.toml +++ b/rules/windows/execution_posh_malicious_script_agg.toml @@ -113,21 +113,21 @@ FROM .alerts-security.* METADATA _id | WHERE kibana.alert.rule.name LIKE "*PowerShell*" // As alerts don't have non-ECS fields, parse the script block ID using GROK -| GROK message "ScriptBlock ID: (?.+)" -| WHERE esql.message.powershell.file.script_block_id IS NOT NULL +| GROK message "ScriptBlock ID: (?.+)" +| WHERE Esql.message.powershell.file.script_block_id IS NOT NULL // Keep relevant fields for further processing -| KEEP kibana.alert.rule.name, esql.message.powershell.file.script_block_id, _id +| KEEP kibana.alert.rule.name, Esql.message.powershell.file.script_block_id, _id // Count distinct alerts and filter for matches above the threshold | STATS - esql.kibana.alert.rule.name.count_distinct = COUNT_DISTINCT(kibana.alert.rule.name), - esql.kibana.alert.rule.name.values = VALUES(kibana.alert.rule.name), - esql._id.values = VALUES(_id) - BY esql.message.powershell.file.script_block_id + Esql.kibana.alert.rule.name.count_distinct = COUNT_DISTINCT(kibana.alert.rule.name), + Esql.kibana.alert.rule.name.values = VALUES(kibana.alert.rule.name), + Esql._id.values = VALUES(_id) + BY Esql.message.powershell.file.script_block_id // Apply detection threshold -| WHERE esql.kibana.alert.rule.name.count_distinct >= 5 +| WHERE Esql.kibana.alert.rule.name.count_distinct >= 5 ''' diff --git a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml index f7a42574af4..af047923c26 100644 --- a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml +++ b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml @@ -53,26 +53,26 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 1000 chars that contain a related keyword -| EVAL esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) // Filter for long scripts -| WHERE esql.powershell.file.script_block_text.length > 1000 +| WHERE Esql.powershell.file.script_block_text.length > 1000 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 // Excludes spaces, #, = and - as they are heavily used in scripts for formatting -| EVAL esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") +| EVAL Esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") // Count the occurrence of special chars and their proportion to the total chars in the script -| EVAL esql.powershell.file.script_block_text.character.count = esql.powershell.file.script_block_text.length - LENGTH(REPLACE(esql.powershell.file.script_block_text.replace, "🔥", "")) -| EVAL esql.powershell.file.script_block_text.character.ratio = esql.powershell.file.script_block_text.character.count::double / esql.powershell.file.script_block_text.length::double +| EVAL Esql.powershell.file.script_block_text.character.count = Esql.powershell.file.script_block_text.length - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell.file.script_block_text.character.ratio = Esql.powershell.file.script_block_text.character.count::double / Esql.powershell.file.script_block_text.length::double // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - esql.powershell.file.script_block_text.character.count, - esql.powershell.file.script_block_text.length, - esql.powershell.file.script_block_text.character.ratio, - esql.powershell.file.script_block_text.replace, + Esql.powershell.file.script_block_text.character.count, + Esql.powershell.file.script_block_text.length, + Esql.powershell.file.script_block_text.character.ratio, + Esql.powershell.file.script_block_text.replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -85,7 +85,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with a 25%+ proportion of special chars -| WHERE esql.powershell.file.script_block_text.character.ratio > 0.25 +| WHERE Esql.powershell.file.script_block_text.character.ratio > 0.25 ''' diff --git a/rules_building_block/persistence_web_server_sus_file_creation.toml b/rules_building_block/persistence_web_server_sus_file_creation.toml index 03a9e92cbc4..864489ecfab 100644 --- a/rules_building_block/persistence_web_server_sus_file_creation.toml +++ b/rules_building_block/persistence_web_server_sus_file_creation.toml @@ -95,15 +95,15 @@ FROM logs-endpoint.events.file-* process.name LIKE "perl*" ) | STATS - esql.event.count = COUNT(), - esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event.count = COUNT(), + Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable, file.path | WHERE - esql.agent.id.count_distinct == 1 AND - esql.event.count < 5 -| SORT esql.event.count ASC + Esql.agent.id.count_distinct == 1 AND + Esql.event.count < 5 +| SORT Esql.event.count ASC | LIMIT 100 ''' From 23c1cd834e533f701070c654296486235e6ebdc0 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Fri, 18 Jul 2025 09:51:21 -0400 Subject: [PATCH 81/94] adjusted several rules for common field names --- ..._access_azure_o365_with_network_alert.toml | 4 +-- ..._multiple_discovery_api_calls_via_cli.toml | 4 +-- ..._snapshot_shared_with_another_account.toml | 2 +- ..._access_azure_entra_suspicious_signin.toml | 31 ++++++++++--------- ...s_azure_key_vault_excessive_retrieval.toml | 10 +++--- ..._access_entra_id_brute_force_activity.toml | 18 +++++------ ...crosoft_365_susp_oauth2_authorization.toml | 6 ++-- ...ential_access_rare_webdav_destination.toml | 4 +-- 8 files changed, 40 insertions(+), 39 deletions(-) diff --git a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml index 34d6f745578..92fb302b80e 100644 --- a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml +++ b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml @@ -103,7 +103,7 @@ FROM logs-*, .alerts-security.* // aggregate by source IP | stats - Esql.source.ip.alerts.count = count(*), + Esql.event.count = count(*), Esql.source.ip.mail_access.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.mail_access.case), Esql.source.ip.azure_signin.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.azure_signin.case), Esql.source.ip.network_alert.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.network_alert.case), @@ -118,7 +118,7 @@ FROM logs-*, .alerts-security.* Esql.source.ip.network_alert.case.count_distinct > 0 and Esql.event.dataset.count_distinct >= 2 and (Esql.source.ip.mail_access.case.count_distinct > 0 or Esql.source.ip.azure_signin.case.count_distinct > 0) - and Esql.source.ip.alerts.count <= 100 + and Esql.event.count <= 100 ''' diff --git a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml index 9cfae804a79..095be3528e9 100644 --- a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml +++ b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml @@ -118,8 +118,8 @@ FROM logs-aws.cloudtrail* ) // extract owner, identity type, and actor from the ARN -| dissect aws.cloudtrail.user_identity.arn "%{}::%{Esql.owner}:%{Esql.identity.type}/%{Esql.user.roles}" -| where STARTS_WITH(Esql.user.roles, "AWSServiceRoleForConfig") != true +| dissect aws.cloudtrail.user_identity.arn "%{}::%{Esql.aws.cloudtrail.user_identity.arn.owner}:%{Esql.aws.cloudtrail.user_identity.arn.type}/%{Esql.aws.cloudtrail.user_identity.arn.roles}" +| where STARTS_WITH(Esql.aws.cloudtrail.user_identity.arn.roles, "AWSServiceRoleForConfig") != true // keep relevant fields (preserving ECS fields and computed time window) | keep @timestamp, Esql.time_window.date_trunc, event.action, aws.cloudtrail.user_identity.arn diff --git a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml index 3f6114e8c55..4288a349a3a 100644 --- a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml +++ b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml @@ -88,7 +88,7 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract snapshotId, attribute type, operation type, and userId | dissect aws.cloudtrail.request_parameters - "{%{?snapshotId}=%{Esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{Esql.attribute.type},%{?createVolumePermission}={%{Esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{Esql.aws.cloudtrail.request_parameters.user.id}}]}}}" + "{%{?snapshotId}=%{Esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{Esql.aws.cloudtrail.request_parameters.attribute.type},%{?createVolumePermission}={%{Esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{Esql.aws.cloudtrail.request_parameters.user.id}}]}}}" // Check for snapshot permission added for another AWS account | where diff --git a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml index 2c3ae6b19c5..90a88032c7a 100644 --- a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml +++ b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml @@ -91,19 +91,19 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index // Case classifications for identity usage | eval - Esql.auth.device_code.case = case( + esql.azure.signinlogs.properties.authentication.device_code.case = case( azure.signinlogs.properties.authentication_protocol == "deviceCode" and azure.signinlogs.properties.authentication_requirement != "multiFactorAuthentication", azure.signinlogs.identity, null), - Esql.auth.visual_studio.case = case( + esql.azure.signinlogs.auth.visual_studio.case = case( azure.signinlogs.properties.app_id == "aebc6443-996d-45c2-90f0-388ff96faa56" and azure.signinlogs.properties.resource_display_name == "Microsoft Graph", azure.signinlogs.identity, null), - Esql.auth.other.case = case( + esql.azure.signinlogs.auth.other.case = case( azure.signinlogs.properties.authentication_protocol != "deviceCode" and azure.signinlogs.properties.app_id != "aebc6443-996d-45c2-90f0-388ff96faa56", azure.signinlogs.identity, @@ -111,21 +111,24 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index // Aggregate metrics by user identity | stats - Esql.event.count = COUNT(*), - Esql.auth.device_code.count_distinct = COUNT_DISTINCT(Esql.auth.device_code.case), - Esql.auth.visual_studio.count_distinct = COUNT_DISTINCT(Esql.auth.visual_studio.case), - Esql.auth.other.count_distinct = COUNT_DISTINCT(Esql.auth.other.case), - Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - Esql.source.ip.values = VALUES(source.ip), - Esql.auth.client_app.values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.resource.display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.auth.requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement) + esql.event.count = COUNT(*), + esql.azure.signinlogs.properties.authentication.device_code.case.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.properties.authentication.device_code.case), + esql.azure.signinlogs.auth.visual_studio.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.auth.visual_studio.case), + esql.azure.signinlogs.auth.other.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.auth.other.case), + esql.azure.signinlogs.source.ip.count_distinct = COUNT_DISTINCT(source.ip), + esql.azure.signinlogs.source.ip.values = VALUES(source.ip), + esql.azure.signinlogs.client_app.values = VALUES(azure.signinlogs.properties.app_display_name), + esql.azure.signinlogs.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), + esql.azure.signinlogs.auth.requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement) by azure.signinlogs.identity // Detect multiple unique IPs for one user with signs of deviceCode or VSC OAuth usage | where - Esql.source.ip.count_distinct >= 2 - and (Esql.auth.device_code.count_distinct > 0 or Esql.auth.visual_studio.count_distinct > 0) + esql.azure.signinlogs.source.ip.count_distinct >= 2 + and ( + esql.azure.signinlogs.auth.device_code.count_distinct > 0 + or esql.azure.signinlogs.auth.visual_studio.count_distinct > 0 + ) ''' diff --git a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml index 135f07d4780..df70626bd3f 100644 --- a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml +++ b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml @@ -119,7 +119,7 @@ FROM logs-azure.platformlogs-* METADATA _id, _index // Aggregate identity, geo, resource, and activity info | STATS Esql.azure.platformlogs.identity.claim.upn.values = VALUES(azure.platformlogs.identity.claim.upn), - Esql.azure.platformlogs.identity.claim.upn.count_unique = COUNT_DISTINCT(azure.platformlogs.identity.claim.upn), + Esql.azure.platformlogs.identity.claim.upn.count_distinct = COUNT_DISTINCT(azure.platformlogs.identity.claim.upn), Esql.azure.platformlogs.identity.claim.appid.values = VALUES(azure.platformlogs.identity.claim.appid), Esql.azure.platformlogs.identity.claim.objectid.values = VALUES(azure.platformlogs.identity.claim.objectid), @@ -129,7 +129,7 @@ FROM logs-azure.platformlogs-* METADATA _id, _index Esql.geo.country.values = VALUES(geo.country_name), Esql.network.as_org.values = VALUES(source.as.organization.name), - Esql.event.actions.values = VALUES(event.action), + Esql.event.action.values = VALUES(event.action), Esql.event.count = COUNT(*), Esql.event.action.count_distinct = COUNT_DISTINCT(event.action), Esql.azure.resource.name.count_distinct = COUNT_DISTINCT(azure.resource.name), @@ -148,7 +148,7 @@ BY Esql.time_window.date_trunc, azure.platformlogs.identity.claim.upn | KEEP Esql.time_window.date_trunc, Esql.azure.platformlogs.identity.claim.upn.values, - Esql.azure.platformlogs.identity.claim.upn.count_unique, + Esql.azure.platformlogs.identity.claim.upn.count_distinct, Esql.azure.platformlogs.identity.claim.appid.values, Esql.azure.platformlogs.identity.claim.objectid.values, Esql.source.ip.values, @@ -156,7 +156,7 @@ BY Esql.time_window.date_trunc, azure.platformlogs.identity.claim.upn Esql.geo.region.values, Esql.geo.country.values, Esql.network.as_org.values, - Esql.event.actions.values, + Esql.event.action.values, Esql.event.count, Esql.event.action.count_distinct, Esql.azure.resource.name.count_distinct, @@ -169,7 +169,7 @@ BY Esql.time_window.date_trunc, azure.platformlogs.identity.claim.upn Esql.azure.resource_id.values // Filter for suspiciously high volume of distinct Key Vault reads by a single actor -| WHERE Esql.azure.platformlogs.identity.claim.upn.count_unique == 1 AND Esql.event.count >= 10 AND Esql.event.action.count_distinct >= 2 +| WHERE Esql.azure.platformlogs.identity.claim.upn.count_distinct == 1 AND Esql.event.count >= 10 AND Esql.event.action.count_distinct >= 2 | SORT Esql.time_window.date_trunc DESC ''' diff --git a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml index 80dce38a6eb..64bf8ecb3e4 100644 --- a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml +++ b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml @@ -159,26 +159,26 @@ FROM logs-azure.signinlogs* Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), Esql.source.geo.country_name.values = VALUES(source.geo.country_name), Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.source.`as`.organization.name.distinct_count = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), Esql.timestamp.first_seen = MIN(@timestamp), Esql.timestamp.last_seen = MAX(@timestamp), - Esql.total_attempts = COUNT() + Esql.event.count = COUNT() BY Esql.time_window.date_trunc | EVAL Esql.duration.seconds = DATE_DIFF("seconds", Esql.timestamp.first_seen, Esql.timestamp.last_seen), Esql.brute_force.type = CASE( - Esql.azure.signinlogs.properties.user_id.count_distinct >= 10 AND Esql.total_attempts >= 30 AND Esql.azure.signinlogs.result_description.count_distinct <= 3 + Esql.azure.signinlogs.properties.user_id.count_distinct >= 10 AND Esql.event.count >= 30 AND Esql.azure.signinlogs.result_description.count_distinct <= 3 AND Esql.source.ip.count_distinct >= 5 AND Esql.duration.seconds <= 600 AND Esql.azure.signinlogs.properties.user_id.count_distinct > Esql.source.ip.count_distinct, "credential_stuffing", - Esql.azure.signinlogs.properties.user_id.count_distinct >= 15 AND Esql.azure.signinlogs.result_description.count_distinct == 1 AND Esql.total_attempts >= 15 AND Esql.duration.seconds <= 1800, + Esql.azure.signinlogs.properties.user_id.count_distinct >= 15 AND Esql.azure.signinlogs.result_description.count_distinct == 1 AND Esql.event.count >= 15 AND Esql.duration.seconds <= 1800, "password_spraying", - (Esql.azure.signinlogs.properties.user_id.count_distinct == 1 AND Esql.azure.signinlogs.result_description.count_distinct == 1 AND Esql.total_attempts >= 30 AND Esql.duration.seconds <= 300) - OR (Esql.azure.signinlogs.properties.user_id.count_distinct <= 3 AND Esql.source.ip.count_distinct > 30 AND Esql.total_attempts >= 100), + (Esql.azure.signinlogs.properties.user_id.count_distinct == 1 AND Esql.azure.signinlogs.result_description.count_distinct == 1 AND Esql.event.count >= 30 AND Esql.duration.seconds <= 300) + OR (Esql.azure.signinlogs.properties.user_id.count_distinct <= 3 AND Esql.source.ip.count_distinct > 30 AND Esql.event.count >= 100), "password_guessing", "other" @@ -188,7 +188,7 @@ BY Esql.time_window.date_trunc Esql.time_window.date_trunc, Esql.brute_force.type, Esql.duration.seconds, - Esql.total_attempts, + Esql.event.count, Esql.timestamp.first_seen, Esql.timestamp.last_seen, Esql.azure.signinlogs.properties.user_id.count_distinct, @@ -203,8 +203,8 @@ BY Esql.time_window.date_trunc Esql.source.ip.count_distinct, Esql.source.`as`.organization.name.values, Esql.source.geo.country_name.values, - Esql.source.geo.country_name.distinct_count, - Esql.source.`as`.organization.name.distinct_count, + Esql.source.geo.country_name.count_distinct, + Esql.source.`as`.organization.name.count_distinct, Esql.azure.signinlogs.properties.authentication_requirement.values, Esql.azure.signinlogs.properties.app_id.values, Esql.azure.signinlogs.properties.app_display_name.values, diff --git a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml index 8f88f025bdf..c0fb3a05395 100644 --- a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml +++ b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml @@ -79,16 +79,14 @@ FROM logs-o365.audit-* o365.audit.ApplicationId IN ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") AND o365.audit.ObjectId IN ("00000003-0000-0000-c000-000000000000") | EVAL - Esql.o365.audit.ExtendedProperties.RequestType = o365.audit.ExtendedProperties.RequestType, - Esql.o365.audit.ExtendedProperties.ResultStatusDetail = o365.audit.ExtendedProperties.ResultStatusDetail, Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), Esql.oauth.authorize.user_id.case = CASE( - Esql.o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" AND Esql.o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", + o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" AND o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", o365.audit.UserId, null ), Esql.oauth.token.user_id.case = CASE( - Esql.o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", + o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", o365.audit.UserId, null ) diff --git a/rules/windows/credential_access_rare_webdav_destination.toml b/rules/windows/credential_access_rare_webdav_destination.toml index a74be2b0cfc..f94f89d989e 100644 --- a/rules/windows/credential_access_rare_webdav_destination.toml +++ b/rules/windows/credential_access_rare_webdav_destination.toml @@ -72,14 +72,14 @@ FROM logs-* NOT Esql.server.webdav.cookie.replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND NOT Esql.server.webdav.cookie.replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" | STATS - Esql.total.count = COUNT(*), + Esql.event.count = COUNT(*), Esql.host.id.count_distinct = COUNT_DISTINCT(host.id), host.id.values = VALUES(host.id), user.name.values = VALUES(user.name) BY Esql.server.webdav.cookie.replace | WHERE Esql.host.id.count_distinct == 1 AND - Esql.total.count <= 3 + Esql.event.count <= 3 ''' From 6c7b39dec2504d7268a46511fb1ed803b14ac99c Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Thu, 24 Jul 2025 11:36:22 -0400 Subject: [PATCH 82/94] updating rules --- ...otential_widespread_malware_infection.toml | 5 +- ..._access_azure_o365_with_network_alert.toml | 33 ++-- ...y_ec2_multi_region_describe_instances.toml | 15 +- ..._multiple_discovery_api_calls_via_cli.toml | 17 +- ...s_multi_region_service_quota_requests.toml | 25 +-- ..._snapshot_shared_with_another_account.toml | 15 +- ..._s3_bucket_enumeration_or_brute_force.toml | 5 +- ...mpact_ec2_ebs_snapshot_access_removed.toml | 13 +- ...object_uploaded_with_ransom_extension.toml | 15 +- ...3_object_encryption_with_external_key.toml | 13 +- ...mpact_s3_static_site_js_file_uploaded.toml | 11 +- ...on_token_used_from_multiple_addresses.toml | 127 +++++++------ ...al_access_signin_console_login_no_mfa.toml | 9 +- ...nce_iam_create_login_profile_for_root.toml | 1 + ..._created_access_keys_for_another_user.toml | 1 + ...tratoraccess_policy_attached_to_group.toml | 9 +- ...stratoraccess_policy_attached_to_role.toml | 9 +- ...stratoraccess_policy_attached_to_user.toml | 9 +- ...rivilege_escalation_sts_role_chaining.toml | 1 + ..._bedrock_execution_without_guardrails.toml | 11 +- ...ls_multiple_violations_by_single_user.toml | 7 +- ...multiple_violations_in_single_request.toml | 13 +- ...confidence_misconduct_blocks_detected.toml | 9 +- ...k_high_resource_consumption_detection.toml | 25 +-- ...attempts_to_use_denied_models_by_user.toml | 7 +- ...ve_information_policy_blocks_detected.toml | 7 +- ...multiple_topic_policy_blocks_detected.toml | 7 +- ...ation_exception_errors_by_single_user.toml | 11 +- ..._multiple_word_policy_blocks_detected.toml | 7 +- ..._access_azure_entra_suspicious_signin.toml | 1 + ...azure_entra_totp_brute_force_attempts.toml | 5 +- ...s_azure_key_vault_excessive_retrieval.toml | 99 +++++----- ..._access_entra_id_brute_force_activity.toml | 167 ++++++++-------- ...s_entra_id_excessive_account_lockouts.toml | 143 +++++++------- ...ntra_signin_brute_force_microsoft_365.toml | 179 +++++++++--------- ...ingle_session_from_multiple_addresses.toml | 65 +++---- ...ous_oauth_flow_via_auth_broker_to_drs.toml | 153 +++++++-------- ...openai_denial_of_ml_service_detection.toml | 17 +- ...ai_insecure_output_handling_detection.toml | 7 +- .../azure_openai_model_theft_detection.toml | 11 +- ...ion_onedrive_excessive_file_downloads.toml | 13 +- ...rosoft_365_excessive_account_lockouts.toml | 65 +++---- ...65_potential_user_account_brute_force.toml | 93 ++++----- ...crosoft_365_susp_oauth2_authorization.toml | 41 ++-- ..._token_hashes_for_single_okta_session.toml | 9 +- ...for_multiple_users_from_single_source.toml | 9 +- ...users_with_the_same_device_token_hash.toml | 9 +- ...e_device_token_hashes_for_single_user.toml | 9 +- ...s_started_from_different_geolocations.toml | 9 +- ...ent_egress_netcon_from_sus_executable.toml | 15 +- ...ense_evasion_base64_decoding_activity.toml | 15 +- ...anning_activity_from_compromised_host.toml | 17 +- ...anning_activity_from_compromised_host.toml | 13 +- ...nusual_file_transfer_utility_launched.toml | 11 +- ...otential_bruteforce_malware_infection.toml | 11 +- ...sistence_web_server_sus_child_spawned.toml | 11 +- ...ence_web_server_sus_command_execution.toml | 11 +- ...ential_access_rare_webdav_destination.toml | 22 +-- ...nse_evasion_posh_obfuscation_backtick.toml | 10 +- ...evasion_posh_obfuscation_backtick_var.toml | 16 +- ..._evasion_posh_obfuscation_char_arrays.toml | 10 +- ...asion_posh_obfuscation_concat_dynamic.toml | 10 +- ...sh_obfuscation_high_number_proportion.toml | 20 +- ...fuscation_iex_env_vars_reconstruction.toml | 16 +- ...obfuscation_iex_string_reconstruction.toml | 16 +- ...asion_posh_obfuscation_index_reversal.toml | 16 +- ...sion_posh_obfuscation_reverse_keyword.toml | 10 +- ...vasion_posh_obfuscation_string_concat.toml | 16 +- ...vasion_posh_obfuscation_string_format.toml | 16 +- ...scation_whitespace_special_proportion.toml | 26 +-- .../execution_posh_malicious_script_agg.toml | 16 +- ..._obfuscation_proportion_special_chars.toml | 20 +- ...sistence_web_server_sus_file_creation.toml | 10 +- 73 files changed, 976 insertions(+), 919 deletions(-) diff --git a/rules/cross-platform/execution_potential_widespread_malware_infection.toml b/rules/cross-platform/execution_potential_widespread_malware_infection.toml index 34abdc7950e..10508a2c703 100644 --- a/rules/cross-platform/execution_potential_widespread_malware_infection.toml +++ b/rules/cross-platform/execution_potential_widespread_malware_infection.toml @@ -67,8 +67,9 @@ query = ''' from logs-endpoint.alerts-* | where event.code in ("malicious_file", "memory_signature", "shellcode_thread") and rule.name is not null | keep host.id, rule.name, event.code -| stats Esql.host.id.count_distinct = count_distinct(host.id) by rule.name, event.code -| where Esql.host.id.count_distinct >= 3 +| stats Esql.host_id_count_distinct = count_distinct(host.id) by rule.name, event.code +| where Esql.host_id_count_distinct >= 3 + ''' diff --git a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml index 92fb302b80e..c3a40ca65af 100644 --- a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml +++ b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml @@ -97,28 +97,29 @@ FROM logs-*, .alerts-security.* // classify each source IP based on alert type | eval - Esql.source.ip.mail_access.case = case(event.dataset == "o365.audit" and event.action == "MailItemsAccessed" and event.outcome == "success", TO_IP(source.ip), null), - Esql.source.ip.azure_signin.case = case(event.dataset == "azure.signinlogs" and event.outcome == "success", TO_IP(source.ip), null), - Esql.source.ip.network_alert.case = case(kibana.alert.rule.name == "External Alerts" and not event.dataset in ("o365.audit", "azure.signinlogs"), TO_IP(source.ip), null) + Esql.source_ip_mail_access_case = case(event.dataset == "o365.audit" and event.action == "MailItemsAccessed" and event.outcome == "success", TO_IP(source.ip), null), + Esql.source_ip_azure_signin_case = case(event.dataset == "azure.signinlogs" and event.outcome == "success", TO_IP(source.ip), null), + Esql.source_ip_network_alert_case = case(kibana.alert.rule.name == "External Alerts" and not event.dataset in ("o365.audit", "azure.signinlogs"), TO_IP(source.ip), null) // aggregate by source IP | stats - Esql.event.count = count(*), - Esql.source.ip.mail_access.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.mail_access.case), - Esql.source.ip.azure_signin.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.azure_signin.case), - Esql.source.ip.network_alert.case.count_distinct = COUNT_DISTINCT(Esql.source.ip.network_alert.case), - Esql.event.dataset.count_distinct = COUNT_DISTINCT(event.dataset), - Esql.event.dataset.values = VALUES(event.dataset), - Esql.kibana.alert.rule.name.values = VALUES(kibana.alert.rule.name), - Esql.event.category.values = VALUES(event.category) - by Esql.source.ip = TO_IP(source.ip) + Esql.event_count = count(*), + Esql.source_ip_mail_access_case_count_distinct = COUNT_DISTINCT(Esql.source_ip_mail_access_case), + Esql.source_ip_azure_signin_case_count_distinct = COUNT_DISTINCT(Esql.source_ip_azure_signin_case), + Esql.source_ip_network_alert_case_count_distinct = COUNT_DISTINCT(Esql.source_ip_network_alert_case), + Esql.event_dataset_count_distinct = COUNT_DISTINCT(event.dataset), + Esql.event_dataset_values = VALUES(event.dataset), + Esql.kibana_alert_rule_name_values = VALUES(kibana.alert.rule.name), + Esql.event_category_values = VALUES(event.category) + by Esql.source_ip = TO_IP(source.ip) // correlation condition | where - Esql.source.ip.network_alert.case.count_distinct > 0 - and Esql.event.dataset.count_distinct >= 2 - and (Esql.source.ip.mail_access.case.count_distinct > 0 or Esql.source.ip.azure_signin.case.count_distinct > 0) - and Esql.event.count <= 100 + Esql.source_ip_network_alert_case_count_distinct > 0 + and Esql.event_dataset_count_distinct >= 2 + and (Esql.source_ip_mail_access_case_count_distinct > 0 or Esql.source_ip_azure_signin_case_count_distinct > 0) + and Esql.event_count <= 100 + ''' diff --git a/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml b/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml index 69be7fe8bb1..ca0d0c84328 100644 --- a/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml +++ b/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml @@ -94,22 +94,23 @@ FROM logs-aws.cloudtrail-* and event.action == "DescribeInstances" // truncate the timestamp to a 30-second window -| eval Esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) +| eval Esql.time_window_date_trunc = DATE_TRUNC(30 seconds, @timestamp) // keep only the relevant raw fields -| keep Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn, cloud.region +| keep Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn, cloud.region // count the number of unique regions and total API calls within the 30-second window | stats - Esql.cloud.region.count_distinct = COUNT_DISTINCT(cloud.region), - Esql.event.count = COUNT(*) - by Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn + Esql.cloud_region_count_distinct = COUNT_DISTINCT(cloud.region), + Esql.event_count = COUNT(*) + by Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn // filter for resources making DescribeInstances API calls in more than 10 regions within the 30-second window -| where Esql.cloud.region.count_distinct >= 10 and Esql.event.count >= 10 +| where Esql.cloud_region_count_distinct >= 10 and Esql.event_count >= 10 // sort the results by time window in descending order -| sort Esql.time_window.date_trunc desc +| sort Esql.time_window_date_trunc desc + ''' [rule.investigation_fields] diff --git a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml index 095be3528e9..38454a75797 100644 --- a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml +++ b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml @@ -83,7 +83,7 @@ query = ''' FROM logs-aws.cloudtrail* // create time window buckets of 10 seconds -| eval Esql.time_window.date_trunc = DATE_TRUNC(10 seconds, @timestamp) +| eval Esql.time_window_date_trunc = DATE_TRUNC(10 seconds, @timestamp) | where event.dataset == "aws.cloudtrail" @@ -118,22 +118,23 @@ FROM logs-aws.cloudtrail* ) // extract owner, identity type, and actor from the ARN -| dissect aws.cloudtrail.user_identity.arn "%{}::%{Esql.aws.cloudtrail.user_identity.arn.owner}:%{Esql.aws.cloudtrail.user_identity.arn.type}/%{Esql.aws.cloudtrail.user_identity.arn.roles}" -| where STARTS_WITH(Esql.aws.cloudtrail.user_identity.arn.roles, "AWSServiceRoleForConfig") != true +| dissect aws.cloudtrail.user_identity.arn "%{}::%{Esql_priv.aws_cloudtrail_user_identity_arn_owner}:%{Esql.aws_cloudtrail_user_identity_arn_type}/%{Esql.aws_cloudtrail_user_identity_arn_roles}" +| where STARTS_WITH(Esql.aws_cloudtrail_user_identity_arn_roles, "AWSServiceRoleForConfig") != true // keep relevant fields (preserving ECS fields and computed time window) -| keep @timestamp, Esql.time_window.date_trunc, event.action, aws.cloudtrail.user_identity.arn +| keep @timestamp, Esql.time_window_date_trunc, event.action, aws.cloudtrail.user_identity.arn // count the number of unique API calls per time window and actor | stats - Esql.event.action.count_distinct = COUNT_DISTINCT(event.action) - by Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn + Esql.event_action_count_distinct = COUNT_DISTINCT(event.action) + by Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn // filter for more than 5 unique API calls per 10s window -| where Esql.event.action.count_distinct > 5 +| where Esql.event_action_count_distinct > 5 // sort the results by the number of unique API calls in descending order -| sort Esql.event.action.count_distinct desc +| sort Esql.event_action_count_distinct desc + ''' diff --git a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml index ef2233760fa..66b31cd3bbe 100644 --- a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml +++ b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml @@ -44,35 +44,36 @@ FROM logs-aws.cloudtrail-* and event.action == "GetServiceQuota" // truncate the timestamp to a 30-second window -| eval Esql.time_window.date_trunc = DATE_TRUNC(30 seconds, @timestamp) +| eval Esql.time_window_date_trunc = DATE_TRUNC(30 seconds, @timestamp) // dissect request parameters to extract service and quota code -| dissect aws.cloudtrail.request_parameters "{%{?Esql.aws.cloudtrail.request_parameters.service_code.key}=%{Esql.aws.cloudtrail.request_parameters.service_code}, %{?quota_code_key}=%{Esql.aws.cloudtrail.request_parameters.quota_code}}" +| dissect aws.cloudtrail.request_parameters "{%{?Esql.aws_cloudtrail_request_parameters_service_code_key}=%{Esql.aws_cloudtrail_request_parameters_service_code}, %{?quota_code_key}=%{Esql.aws_cloudtrail_request_parameters_quota_code}}" // filter for EC2 service quota L-1216C47A (vCPU on-demand instances) -| where Esql.aws.cloudtrail.request_parameters.service_code == "ec2" and Esql.aws.cloudtrail.request_parameters.quota_code == "L-1216C47A" +| where Esql.aws_cloudtrail_request_parameters_service_code == "ec2" and Esql.aws_cloudtrail_request_parameters_quota_code == "L-1216C47A" // keep only the relevant fields | keep - Esql.time_window.date_trunc, + Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn, cloud.region, - Esql.aws.cloudtrail.request_parameters.service_code, - Esql.aws.cloudtrail.request_parameters.quota_code + Esql.aws_cloudtrail_request_parameters_service_code, + Esql.aws_cloudtrail_request_parameters_quota_code // count the number of unique regions and total API calls within the time window | stats - Esql.cloud.region.count_distinct = COUNT_DISTINCT(cloud.region), - Esql.event.count = COUNT(*) - by Esql.time_window.date_trunc, aws.cloudtrail.user_identity.arn + Esql.cloud_region_count_distinct = COUNT_DISTINCT(cloud.region), + Esql.event_count = COUNT(*) + by Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn // filter for API calls in more than 10 regions within the 30-second window | where - Esql.cloud.region.count_distinct >= 10 - and Esql.event.count >= 10 + Esql.cloud_region_count_distinct >= 10 + and Esql.event_count >= 10 // sort by time window descending -| sort Esql.time_window.date_trunc desc +| sort Esql.time_window_date_trunc desc + ''' note = """## Triage and analysis diff --git a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml index 4288a349a3a..8d1b8b4cbf0 100644 --- a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml +++ b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml @@ -88,12 +88,12 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract snapshotId, attribute type, operation type, and userId | dissect aws.cloudtrail.request_parameters - "{%{?snapshotId}=%{Esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{Esql.aws.cloudtrail.request_parameters.attribute.type},%{?createVolumePermission}={%{Esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{Esql.aws.cloudtrail.request_parameters.user.id}}]}}}" + "{%{?snapshotId}=%{Esql.aws_cloudtrail_request_parameters_snapshot_id},%{?attributeType}=%{Esql.aws_cloudtrail_request_parameters_attribute_type},%{?createVolumePermission}={%{Esql.aws_cloudtrail_request_parameters_operation_type}={%{?items}=[{%{?userId}=%{Esql_priv.aws_cloudtrail_request_parameters_user_id}}]}}}" // Check for snapshot permission added for another AWS account | where - Esql.aws.cloudtrail.request_parameters.operation.type == "add" - and cloud.account.id != Esql.aws.cloudtrail.request_parameters.user.id + Esql.aws_cloudtrail_request_parameters_operation_type == "add" + and cloud.account.id != Esql_priv.aws_cloudtrail_request_parameters_user_id // Keep ECS and derived fields | keep @@ -101,11 +101,12 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - Esql.aws.cloudtrail.request_parameters.snapshot.id, - Esql.aws.cloudtrail.request_parameters.attribute.type, - Esql.aws.cloudtrail.request_parameters.operation.type, - Esql.aws.cloudtrail.request_parameters.user.id, + Esql.aws_cloudtrail_request_parameters_snapshot_id, + Esql.aws_cloudtrail_request_parameters_attribute_type, + Esql.aws_cloudtrail_request_parameters_operation_type, + Esql_priv.aws_cloudtrail_request_parameters_user_id, source.ip + ''' diff --git a/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml b/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml index 919650f1345..f8ec5dfda38 100644 --- a/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml +++ b/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml @@ -101,14 +101,15 @@ FROM logs-aws.cloudtrail* // Count access denied requests per server_name, source, and account | stats - Esql.event.count = COUNT(*) + Esql.event_count = COUNT(*) by tls.client.server_name, source.address, cloud.account.id // Threshold: more than 40 denied requests -| where Esql.event.count > 40 +| where Esql.event_count > 40 + ''' diff --git a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml index cc3baa2dbaa..41a9e1f5535 100644 --- a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml +++ b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml @@ -85,10 +85,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Dissect parameters to extract key fields | dissect aws.cloudtrail.request_parameters - "{%{?snapshotId}=%{Esql.aws.cloudtrail.request_parameters.snapshot.id},%{?attributeType}=%{Esql.aws.cloudtrail.request_parameters.attribute.type},%{?createVolumePermission}={%{Esql.aws.cloudtrail.request_parameters.operation.type}={%{?items}=[{%{?userId}=%{Esql.aws.cloudtrail.request_parameters.user.id}}]}}}" + "{%{?snapshotId}=%{Esql.aws_cloudtrail_request_parameters_snapshot_id},%{?attributeType}=%{Esql.aws_cloudtrail_request_parameters_attribute_type},%{?createVolumePermission}={%{Esql.aws_cloudtrail_request_parameters_operation_type}={%{?items}=[{%{?userId}=%{Esql_priv.aws_cloudtrail_request_parameters_user_id}}]}}}" // Match on snapshot permission **removal** -| where Esql.aws.cloudtrail.request_parameters.operation.type == "remove" +| where Esql.aws_cloudtrail_request_parameters_operation_type == "remove" // Keep ECS and derived fields | keep @@ -96,11 +96,12 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - Esql.aws.cloudtrail.request_parameters.snapshot.id, - Esql.aws.cloudtrail.request_parameters.attribute.type, - Esql.aws.cloudtrail.request_parameters.operation.type, - Esql.aws.cloudtrail.request_parameters.user.id, + Esql.aws_cloudtrail_request_parameters_snapshot_id, + Esql.aws_cloudtrail_request_parameters_attribute_type, + Esql.aws_cloudtrail_request_parameters_operation_type, + Esql_priv.aws_cloudtrail_request_parameters_user_id, source.address + ''' diff --git a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml index 85377ecb857..f54c893e25c 100644 --- a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml +++ b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml @@ -91,29 +91,30 @@ FROM logs-aws.cloudtrail-* and event.outcome == "success" // extract object key from API request parameters -| dissect aws.cloudtrail.request_parameters "%{?ignore_values}key=%{Esql.aws.cloudtrail.request_parameters.object.key}}" +| dissect aws.cloudtrail.request_parameters "%{?ignore_values}key=%{Esql.aws_cloudtrail_request_parameters_object_key}}" // regex match against common ransomware naming patterns | where - Esql.aws.cloudtrail.request_parameters.object.key rlike "(.*)(ransom|lock|crypt|enc|readme|how_to_decrypt|decrypt_instructions|recovery|datarescue)(.*)" - and not Esql.aws.cloudtrail.request_parameters.object.key rlike "(.*)(AWSLogs|CloudTrail|access-logs)(.*)" + Esql.aws_cloudtrail_request_parameters_object_key rlike "(.*)(ransom|lock|crypt|enc|readme|how_to_decrypt|decrypt_instructions|recovery|datarescue)(.*)" + and not Esql.aws_cloudtrail_request_parameters_object_key rlike "(.*)(AWSLogs|CloudTrail|access-logs)(.*)" // keep relevant ECS and derived fields | keep tls.client.server_name, aws.cloudtrail.user_identity.arn, - Esql.aws.cloudtrail.request_parameters.object.key + Esql.aws_cloudtrail_request_parameters_object_key // aggregate by server name, actor, and object key | stats - Esql.event.count = COUNT(*) + Esql.event_count = COUNT(*) by tls.client.server_name, aws.cloudtrail.user_identity.arn, - Esql.aws.cloudtrail.request_parameters.object.key + Esql.aws_cloudtrail_request_parameters_object_key // filter for rare single uploads (likely test/detonation) -| where Esql.event.count == 1 +| where Esql.event_count == 1 + ''' diff --git a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml index f4e25d37e4b..020169b26f3 100644 --- a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml +++ b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml @@ -94,10 +94,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // dissect request parameters to extract KMS key info and target object info | dissect aws.cloudtrail.request_parameters - "{%{?bucketName}=%{Esql.aws.cloudtrail.request_parameters.target.bucket.name},%{?x-amz-server-side-encryption-aws-kms-key-id}=%{?arn}:%{?aws}:%{?kms}:%{?region}:%{Esql.aws.cloudtrail.request_parameters.kms.key.account.id}:%{?key}/%{Esql.aws.cloudtrail.request_parameters.kms.key.id},%{?Host}=%{?tls.client.server.name},%{?x-amz-server-side-encryption}=%{?server_side_encryption},%{?x-amz-copy-source}=%{?bucket.object.name},%{?key}=%{Esql.aws.cloudtrail.request_parameters.target.object.key}}" + "{%{?bucketName}=%{Esql.aws_cloudtrail_request_parameters_target_bucket_name},%{?x-amz-server-side-encryption-aws-kms-key-id}=%{?arn}:%{?aws}:%{?kms}:%{?region}:%{Esql.aws_cloudtrail_request_parameters_kms_key_account_id}:%{?key}/%{Esql.aws_cloudtrail_request_parameters_kms_key_id},%{?Host}=%{?tls.client.server.name},%{?x-amz-server-side-encryption}=%{?server_side_encryption},%{?x-amz-copy-source}=%{?bucket.object.name},%{?key}=%{Esql.aws_cloudtrail_request_parameters_target_object_key}}" // detect cross-account key usage -| where cloud.account.id != Esql.aws.cloudtrail.request_parameters.kms.key.account.id +| where cloud.account.id != Esql.aws_cloudtrail_request_parameters_kms_key_account_id // keep ECS and dissected fields | keep @@ -105,10 +105,11 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index aws.cloudtrail.user_identity.arn, cloud.account.id, event.action, - Esql.aws.cloudtrail.request_parameters.target.bucket.name, - Esql.aws.cloudtrail.request_parameters.kms.key.account.id, - Esql.aws.cloudtrail.request_parameters.kms.key.id, - Esql.aws.cloudtrail.request_parameters.target.object.key + Esql.aws_cloudtrail_request_parameters_target_bucket_name, + Esql.aws_cloudtrail_request_parameters_kms_key_account_id, + Esql.aws_cloudtrail_request_parameters_kms_key_id, + Esql.aws_cloudtrail_request_parameters_target_object_key + ''' diff --git a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml index f70709701c8..c67fa905e34 100644 --- a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml +++ b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml @@ -93,13 +93,13 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index // Extract fields from request parameters | dissect aws.cloudtrail.request_parameters - "%{{?bucket.name.key}=%{Esql.aws.cloudtrail.request_parameters.bucket.name}, %{?host.key}=%{Esql.aws.cloudtrail.request_parameters.host}, %{?bucket.object.location.key}=%{Esql.aws.cloudtrail.request_parameters.bucket.object.location}}" + "%{{?bucket.name.key}=%{Esql.aws_cloudtrail_request_parameters_bucket_name}, %{?host.key}=%{Esql_priv.aws_cloudtrail_request_parameters_host}, %{?bucket.object.location.key}=%{Esql.aws_cloudtrail_request_parameters_bucket_object_location}}" // Extract file name portion from full object path -| dissect Esql.aws.cloudtrail.request_parameters.object.location "%{}static/js/%{Esql.aws.cloudtrail.request_parameters.object.key}" +| dissect Esql.aws_cloudtrail_request_parameters_object_location "%{}static/js/%{Esql.aws_cloudtrail_request_parameters_object_key}" // Match on JavaScript files -| where ENDS_WITH(Esql.aws.cloudtrail.request_parameters.object.key, ".js") +| where ENDS_WITH(Esql.aws_cloudtrail_request_parameters_object_key, ".js") // Retain relevant ECS and dissected fields | keep @@ -107,12 +107,13 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index aws.cloudtrail.user_identity.access_key_id, aws.cloudtrail.user_identity.type, aws.cloudtrail.request_parameters, - Esql.aws.cloudtrail.request_parameters.bucket.name, - Esql.aws.cloudtrail.request_parameters.object.key, + Esql.aws_cloudtrail_request_parameters_bucket_name, + Esql.aws_cloudtrail_request_parameters_object_key, user_agent.original, source.ip, event.action, @timestamp + ''' diff --git a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml index 9f3e8461df1..1421b791706 100644 --- a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml +++ b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml @@ -99,78 +99,79 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index ) | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), - Esql.user.id = aws.cloudtrail.user_identity.arn, - Esql.user.access_key_id = aws.cloudtrail.user_identity.access_key_id, - Esql.source.ip = source.ip, - Esql.user_agent.original = user_agent.original, - Esql.source.ip_string = TO_STRING(source.ip), - Esql.source.ip_user_agent_pair = CONCAT(Esql.source.ip_string, " - ", user_agent.original), - Esql.source.ip_city_pair = CONCAT(Esql.source.ip_string, " - ", source.geo.city_name), - Esql.source.geo.city_name = source.geo.city_name, - Esql.event.timestamp = @timestamp, - Esql.source.network.org_name = `source.as.organization.name` + Esql.time_window_date_trunc = DATE_TRUNC(30 minutes, @timestamp), + Esql.aws_cloudtrail_user_identity_arn = aws.cloudtrail.user_identity.arn, + Esql.aws_cloudtrail_user_identity_access_key_id = aws.cloudtrail.user_identity.access_key_id, + Esql.source_ip = source.ip, + Esql.user_agent_original = user_agent.original, + Esql.source_ip_string = TO_STRING(source.ip), + Esql.source_ip_user_agent_pair = CONCAT(Esql.source_ip_string, " - ", user_agent.original), + Esql.source_ip_city_pair = CONCAT(Esql.source_ip_string, " - ", source.geo.city_name), + Esql.source_geo_city_name = source.geo.city_name, + Esql.event_timestamp = @timestamp, + Esql.source_network_org_name = `source.as.organization.name` | STATS - Esql.event.action.values = VALUES(event.action), - Esql.event.provider.values = VALUES(event.provider), - Esql.user.access_key_id.values = VALUES(Esql.user.access_key_id), - Esql.user.id.values = VALUES(Esql.user.id), - Esql.source.ip.values = VALUES(Esql.source.ip), - Esql.user_agent.original.values = VALUES(Esql.user_agent.original), - Esql.source.ip_user_agent_pair.values = VALUES(Esql.source.ip_user_agent_pair), - Esql.source.geo.city_name.values = VALUES(Esql.source.geo.city_name), - Esql.source.ip_city_pair.values = VALUES(Esql.source.ip_city_pair), - Esql.source.network.org_name.values = VALUES(Esql.source.network.org_name), - Esql.source.ip.count_distinct = COUNT_DISTINCT(Esql.source.ip), - Esql.user_agent.original.count_distinct = COUNT_DISTINCT(Esql.user_agent.original), - Esql.source.geo.city_name.count_distinct = COUNT_DISTINCT(Esql.source.geo.city_name), - Esql.source.network.org_name.count_distinct = COUNT_DISTINCT(Esql.source.network.org_name), - Esql.timestamp.first_seen = MIN(Esql.event.timestamp), - Esql.timestamp.last_seen = MAX(Esql.event.timestamp), - Esql.event.count = COUNT() - BY Esql.time_window.date_trunc, Esql.user.access_key_id + Esql.event_action_values = VALUES(event.action), + Esql.event_provider_values = VALUES(event.provider), + Esql.aws_cloudtrail_user_identity_access_key_id_values = VALUES(Esql.aws_cloudtrail_user_identity_access_key_id), + Esql.aws_cloudtrail_user_identity_arn_values = VALUES(Esql.aws_cloudtrail_user_identity_arn), + Esql.source_ip_values = VALUES(Esql.source_ip), + Esql.user_agent_original_values = VALUES(Esql.user_agent_original), + Esql.source_ip_user_agent_pair_values = VALUES(Esql.source_ip_user_agent_pair), + Esql.source_geo_city_name_values = VALUES(Esql.source_geo_city_name), + Esql.source_ip_city_pair_values = VALUES(Esql.source_ip_city_pair), + Esql.source_network_org_name_values = VALUES(Esql.source_network_org_name), + Esql.source_ip_count_distinct = COUNT_DISTINCT(Esql.source_ip), + Esql.user_agent_original_count_distinct = COUNT_DISTINCT(Esql.user_agent_original), + Esql.source_geo_city_name_count_distinct = COUNT_DISTINCT(Esql.source_geo_city_name), + Esql.source_network_org_name_count_distinct = COUNT_DISTINCT(Esql.source_network_org_name), + Esql.timestamp_first_seen = MIN(Esql.event_timestamp), + Esql.timestamp_last_seen = MAX(Esql.event_timestamp), + Esql.event_count = COUNT() + BY Esql.time_window_date_trunc, Esql.aws_cloudtrail_user_identity_access_key_id | EVAL - Esql.activity.type = CASE( - Esql.source.ip.count_distinct >= 2 AND Esql.source.network.org_name.count_distinct >= 2 AND Esql.source.geo.city_name.count_distinct >= 2 AND Esql.user_agent.original.count_distinct >= 2, "multiple_ip_network_city_user_agent", - Esql.source.ip.count_distinct >= 2 AND Esql.source.network.org_name.count_distinct >= 2 AND Esql.source.geo.city_name.count_distinct >= 2, "multiple_ip_network_city", - Esql.source.ip.count_distinct >= 2 AND Esql.source.geo.city_name.count_distinct >= 2, "multiple_ip_and_city", - Esql.source.ip.count_distinct >= 2 AND Esql.source.network.org_name.count_distinct >= 2, "multiple_ip_and_network", - Esql.source.ip.count_distinct >= 2 AND Esql.user_agent.original.count_distinct >= 2, "multiple_ip_and_user_agent", + Esql.activity_type = CASE( + Esql.source_ip_count_distinct >= 2 AND Esql.source_network_org_name_count_distinct >= 2 AND Esql.source_geo_city_name_count_distinct >= 2 AND Esql.user_agent_original_count_distinct >= 2, "multiple_ip_network_city_user_agent", + Esql.source_ip_count_distinct >= 2 AND Esql.source_network_org_name_count_distinct >= 2 AND Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_network_city", + Esql.source_ip_count_distinct >= 2 AND Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_and_city", + Esql.source_ip_count_distinct >= 2 AND Esql.source_network_org_name_count_distinct >= 2, "multiple_ip_and_network", + Esql.source_ip_count_distinct >= 2 AND Esql.user_agent_original_count_distinct >= 2, "multiple_ip_and_user_agent", "normal_activity" ), - Esql.activity.fidelity_score = CASE( - Esql.activity.type == "multiple_ip_network_city_user_agent", "high", - Esql.activity.type == "multiple_ip_network_city", "high", - Esql.activity.type == "multiple_ip_and_city", "medium", - Esql.activity.type == "multiple_ip_and_network", "medium", - Esql.activity.type == "multiple_ip_and_user_agent", "low" + Esql.activity_fidelity_score = CASE( + Esql.activity_type == "multiple_ip_network_city_user_agent", "high", + Esql.activity_type == "multiple_ip_network_city", "high", + Esql.activity_type == "multiple_ip_and_city", "medium", + Esql.activity_type == "multiple_ip_and_network", "medium", + Esql.activity_type == "multiple_ip_and_user_agent", "low" ) | KEEP - Esql.time_window.date_trunc, - Esql.activity.type, - Esql.activity.fidelity_score, - Esql.event.count, - Esql.timestamp.first_seen, - Esql.timestamp.last_seen, - Esql.user.id.values, - Esql.user.access_key_id.values, - Esql.event.action.values, - Esql.event.provider.values, - Esql.source.ip.values, - Esql.user_agent.original.values, - Esql.source.ip_user_agent_pair.values, - Esql.source.geo.city_name.values, - Esql.source.ip_city_pair.values, - Esql.source.network.org_name.values, - Esql.source.ip.count_distinct, - Esql.user_agent.original.count_distinct, - Esql.source.geo.city_name.count_distinct, - Esql.source.network.org_name.count_distinct - -| WHERE Esql.activity.type != "normal_activity" + Esql.time_window_date_trunc, + Esql.activity_type, + Esql.activity_fidelity_score, + Esql.event_count, + Esql.timestamp_first_seen, + Esql.timestamp_last_seen, + Esql.aws_cloudtrail_user_identity_arn_values, + Esql.aws_cloudtrail_user_identity_access_key_id_values, + Esql.event_action_values, + Esql.event_provider_values, + Esql.source_ip_values, + Esql.user_agent_original_values, + Esql.source_ip_user_agent_pair_values, + Esql.source_geo_city_name_values, + Esql.source_ip_city_pair_values, + Esql.source_network_org_name_values, + Esql.source_ip_count_distinct, + Esql.user_agent_original_count_distinct, + Esql.source_geo_city_name_count_distinct, + Esql.source_network_org_name_count_distinct + +| WHERE Esql.activity_type != "normal_activity" + ''' diff --git a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml index 41eb74dc65c..1c2252e7688 100644 --- a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml +++ b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml @@ -78,10 +78,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract mobile version and MFA usage | dissect aws.cloudtrail.additional_eventdata - "{%{?mobile_version_key}=%{Esql.device.version}, %{?mfa_used_key}=%{Esql.auth.mfa.used}}" + "{%{?mobile_version_key}=%{Esql.device_version}, %{?mfa_used_key}=%{Esql.auth_mfa_used}}" // Only keep events where MFA was not used -| where Esql.auth.mfa.used == "No" +| where Esql.auth_mfa_used == "No" // Keep relevant ECS and dissected fields | keep @@ -89,8 +89,9 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.action, aws.cloudtrail.event_type, aws.cloudtrail.user_identity.type, - Esql.device.version, - Esql.auth.mfa.used + Esql.device_version, + Esql.auth_mfa_used + ''' diff --git a/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml b/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml index a7d7733a8b6..7ad38bb5369 100644 --- a/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml +++ b/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml @@ -155,6 +155,7 @@ from logs-aws.cloudtrail* metadata _id, _version, _index cloud.account.id, event.action, source.address + ''' diff --git a/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml b/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml index 36c9ab1caf4..8d1fab06403 100644 --- a/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml +++ b/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml @@ -116,6 +116,7 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index aws.cloudtrail.response_elements, aws.cloudtrail.user_identity.arn, aws.cloudtrail.user_identity.type + ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml index 19b9559f3ec..c59b1912875 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml @@ -107,10 +107,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy and group details from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws.cloudtrail.request_parameters.policy.name},%{?groupName}=%{Esql.aws.cloudtrail.request_parameters.group.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws_cloudtrail_request_parameters_policy_name},%{?groupName}=%{Esql.aws_cloudtrail_request_parameters_group_name}}" // Filter for attachment of AdministratorAccess policy -| where Esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" +| where Esql.aws_cloudtrail_request_parameters_policy_name == "AdministratorAccess" // Keep ECS and derived fields | keep @@ -118,8 +118,9 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - Esql.aws.cloudtrail.request_parameters.policy.name, - Esql.aws.cloudtrail.request_parameters.group.name + Esql.aws_cloudtrail_request_parameters_policy_name, + Esql.aws_cloudtrail_request_parameters_group_name + ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml index 2eb6a4269a5..e62dbb38dc2 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml @@ -106,10 +106,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy name and role name from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws.cloudtrail.request_parameters.policy.name},%{?roleName}=%{Esql.aws.cloudtrail.request_parameters.role.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws_cloudtrail_request_parameters_policy_name},%{?roleName}=%{Esql.aws_cloudtrail_request_parameters_role_name}}" // Filter for AdministratorAccess policy attachment -| where Esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" +| where Esql.aws_cloudtrail_request_parameters_policy_name == "AdministratorAccess" // Keep relevant ECS and dynamic fields | keep @@ -117,8 +117,9 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - Esql.aws.cloudtrail.request_parameters.policy.name, - Esql.aws.cloudtrail.request_parameters.role.name + Esql.aws_cloudtrail_request_parameters_policy_name, + Esql.aws_cloudtrail_request_parameters_role_name + ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml index c775e49efbf..6c4247610c2 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml @@ -106,10 +106,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract policy name and user name from request parameters | dissect aws.cloudtrail.request_parameters - "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws.cloudtrail.request_parameters.policy.name},%{?userName}=%{Esql.aws.cloudtrail.request_parameters.target.user.name}}" + "{%{?policyArn}=%{?arn}:%{?aws}:%{?iam}::%{?aws}:%{?policy}/%{Esql.aws_cloudtrail_request_parameters_policy_name},%{?userName}=%{Esql_priv.aws_cloudtrail_request_parameters_target_user_name}}" // Filter for AdministratorAccess policy -| where Esql.aws.cloudtrail.request_parameters.policy.name == "AdministratorAccess" +| where Esql.aws_cloudtrail_request_parameters_policy_name == "AdministratorAccess" // Keep ECS and parsed fields | keep @@ -118,14 +118,15 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.provider, event.action, event.outcome, - Esql.aws.cloudtrail.request_parameters.policy.name, - Esql.aws.cloudtrail.request_parameters.target.user.name, + Esql.aws_cloudtrail_request_parameters_policy_name, + Esql_priv.aws_cloudtrail_request_parameters_target_user_name, aws.cloudtrail.request_parameters, aws.cloudtrail.user_identity.arn, related.user, user_agent.original, user.name, source.address + ''' diff --git a/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml b/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml index 43c46cd30db..9e0905713d1 100644 --- a/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml +++ b/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml @@ -90,6 +90,7 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index // keep only the relevant fields | keep aws.cloudtrail.user_identity.arn, cloud.region, aws.cloudtrail.resources.account_id, aws.cloudtrail.recipient_account_id, aws.cloudtrail.user_identity.access_key_id + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml b/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml index 946767ac700..ddba5badde3 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml @@ -82,7 +82,7 @@ query = ''' FROM logs-aws_bedrock.invocation-* // Create 1-minute time buckets -| eval Esql.time_window.date_trunc = DATE_TRUNC(1 minute, @timestamp) +| eval Esql.time_window_date_trunc = DATE_TRUNC(1 minute, @timestamp) // Filter for invocations without guardrails | where gen_ai.guardrail_id IS NULL and user.id IS NOT NULL @@ -90,19 +90,20 @@ FROM logs-aws_bedrock.invocation-* // Keep only relevant fields | keep @timestamp, - Esql.time_window.date_trunc, + Esql.time_window_date_trunc, gen_ai.guardrail_id, user.id // Count number of unsafe invocations per user | stats - Esql.ml.invocations.no_guardrails.count = COUNT() + Esql.ml_invocations_no_guardrails_count = COUNT() by user.id // Filter for suspicious volume -| where Esql.ml.invocations.no_guardrails.count > 5 +| where Esql.ml_invocations_no_guardrails_count > 5 // Sort descending -| sort Esql.ml.invocations.no_guardrails.count desc +| sort Esql.ml_invocations_no_guardrails_count desc + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml index bda1d1da474..25644fe7906 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml @@ -93,16 +93,17 @@ FROM logs-aws_bedrock.invocation-* // Count violations by user, model, and account | stats - Esql.ml.violations.count = COUNT(*) + Esql.ml_violations_count = COUNT(*) by user.id, gen_ai.request.model.id, cloud.account.id // Filter for repeated violations -| where Esql.ml.violations.count > 1 +| where Esql.ml_violations_count > 1 // Sort descending by violation volume -| sort Esql.ml.violations.count desc +| sort Esql.ml_violations_count desc + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml index c94ca58c4e4..47fa9f3312d 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml @@ -86,29 +86,30 @@ FROM logs-aws_bedrock.invocation-* | where gen_ai.policy.action == "BLOCKED" // Count number of policy matches per request (multi-valued) -| eval Esql.ml.policy.violations.mv_count = MV_COUNT(gen_ai.policy.name) +| eval Esql.ml_policy_violations_mv_count = MV_COUNT(gen_ai.policy.name) // Filter for requests with more than one policy match -| where Esql.ml.policy.violations.mv_count > 1 +| where Esql.ml_policy_violations_mv_count > 1 // Keep relevant fields | keep gen_ai.policy.action, - Esql.ml.policy.violations.mv_count, + Esql.ml_policy_violations_mv_count, user.id, gen_ai.request.model.id, cloud.account.id // Aggregate requests with multiple violations | stats - Esql.ml.policy.violations.total_unique_requests.count = COUNT(*) + Esql.ml_policy_violations_total_unique_requests_count = COUNT(*) by - Esql.ml.policy.violations.mv_count, + Esql.ml_policy_violations_mv_count, user.id, gen_ai.request.model.id, cloud.account.id // Sort by number of unique requests -| sort Esql.ml.policy.violations.total_unique_requests.count desc +| sort Esql.ml_policy_violations_total_unique_requests_count desc + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml index a60b902d9b6..75f5d15b4b2 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml @@ -100,21 +100,22 @@ FROM logs-aws_bedrock.invocation-* // Count blocked violations per user per violation type | stats - Esql.ml.policy.blocked.violation.count = COUNT() + Esql.ml_policy_blocked_violation_count = COUNT() by user.id, gen_ai.compliance.violation_code // Aggregate all violation types per user | stats - Esql.ml.policy.blocked.violation.total.count = SUM(Esql.ml.policy.blocked.violation.count) + Esql.ml_policy_blocked_violation_total_count = SUM(Esql.ml_policy_blocked_violation_count) by user.id // Filter for users with more than 5 total violations -| where Esql.ml.policy.blocked.violation.total.count > 5 +| where Esql.ml_policy_blocked_violation_total_count > 5 // Sort by violation volume -| sort Esql.ml.policy.blocked.violation.total.count desc +| sort Esql.ml_policy_blocked_violation_total_count desc + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml b/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml index 6fbb679fad5..39ea7491d2c 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml @@ -89,28 +89,29 @@ FROM logs-aws_bedrock.invocation-* // Aggregate usage metrics | stats - Esql.ml.usage.prompt_tokens.max = MAX(gen_ai.usage.prompt_tokens), - Esql.ml.invocations.total.count = COUNT(*), - Esql.ml.usage.completion_tokens.avg = AVG(gen_ai.usage.completion_tokens) + Esql.ml_usage_prompt_tokens_max = MAX(gen_ai.usage.prompt_tokens), + Esql.ml_invocations_total_count = COUNT(*), + Esql.ml_usage_completion_tokens_avg = AVG(gen_ai.usage.completion_tokens) by user.id // Filter for suspicious usage patterns | where - Esql.ml.usage.prompt_tokens.max > 5000 - and Esql.ml.invocations.total.count > 10 - and Esql.ml.usage.completion_tokens.avg > 500 + Esql.ml_usage_prompt_tokens_max > 5000 + and Esql.ml_invocations_total_count > 10 + and Esql.ml_usage_completion_tokens_avg > 500 // Calculate a custom risk factor -| eval Esql.ml.risk.score = - (Esql.ml.usage.prompt_tokens.max / 1000) * - Esql.ml.invocations.total.count * - (Esql.ml.usage.completion_tokens.avg / 500) +| eval Esql.ml_risk_score = + (Esql.ml_usage_prompt_tokens_max / 1000) * + Esql.ml_invocations_total_count * + (Esql.ml_usage_completion_tokens_avg / 500) // Filter on risk score -| where Esql.ml.risk.score > 10 +| where Esql.ml_risk_score > 10 // Sort high risk users to top -| sort Esql.ml.risk.score desc +| sort Esql.ml_risk_score desc + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml index 2a4950ba9a2..edc8f7752ce 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml @@ -90,17 +90,18 @@ FROM logs-aws_bedrock.invocation-* // Count total denials per user/model/account | stats - Esql.ml.response.access_denied.count = COUNT(*) + Esql.ml_response_access_denied_count = COUNT(*) by user.id, gen_ai.request.model.id, cloud.account.id // Filter for users with repeated denials -| where Esql.ml.response.access_denied.count > 3 +| where Esql.ml_response_access_denied_count > 3 // Sort by volume of denials -| sort Esql.ml.response.access_denied.count desc +| sort Esql.ml_response_access_denied_count desc + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml index ce9abe14b4b..f31c8469d6e 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml @@ -94,13 +94,14 @@ FROM logs-aws_bedrock.invocation-* // Count how many times each user triggered a sensitive info block | stats - Esql.ml.policy.blocked.sensitive_info.count = COUNT() + Esql.ml_policy_blocked_sensitive_info_count = COUNT() by user.id // Filter for users with more than 5 violations -| where Esql.ml.policy.blocked.sensitive_info.count > 5 +| where Esql.ml_policy_blocked_sensitive_info_count > 5 // Sort highest to lowest -| sort Esql.ml.policy.blocked.sensitive_info.count desc +| sort Esql.ml_policy_blocked_sensitive_info_count desc + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml index 6a61c8ed091..4db09ccc295 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml @@ -94,13 +94,14 @@ FROM logs-aws_bedrock.invocation-* // Count how many times each user triggered a blocked topic policy | stats - Esql.ml.policy.blocked.topic.count = COUNT() + Esql.ml_policy_blocked_topic_count = COUNT() by user.id // Filter for excessive violations -| where Esql.ml.policy.blocked.topic.count > 5 +| where Esql.ml_policy_blocked_topic_count > 5 // Sort highest to lowest -| sort Esql.ml.policy.blocked.topic.count desc +| sort Esql.ml_policy_blocked_topic_count desc + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml index 8c6c6d3725d..e46f6c1a7d9 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml @@ -85,7 +85,7 @@ query = ''' FROM logs-aws_bedrock.invocation-* // Truncate timestamp to 1-minute window -| eval Esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) +| eval Esql.time_window_date_trunc = DATE_TRUNC(1 minutes, @timestamp) // Filter for validation exceptions in responses | where gen_ai.response.error_code == "ValidationException" @@ -96,18 +96,19 @@ FROM logs-aws_bedrock.invocation-* gen_ai.request.model.id, cloud.account.id, gen_ai.response.error_code, - Esql.time_window.date_trunc + Esql.time_window_date_trunc // Count number of denials by user/account/time window | stats - Esql.ml.response.validation_error.count = COUNT(*) + Esql.ml_response_validation_error_count = COUNT(*) by - Esql.time_window.date_trunc, + Esql.time_window_date_trunc, user.id, cloud.account.id // Filter for excessive errors -| where Esql.ml.response.validation_error.count > 3 +| where Esql.ml_response_validation_error_count > 3 + ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml index ddc38d17cab..df8c2feb062 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml @@ -94,13 +94,14 @@ FROM logs-aws_bedrock.invocation-* // Count blocked profanity attempts per user | stats - Esql.ml.policy.blocked.profanity.count = COUNT() + Esql.ml_policy_blocked_profanity_count = COUNT() by user.id // Filter for excessive policy violations -| where Esql.ml.policy.blocked.profanity.count > 5 +| where Esql.ml_policy_blocked_profanity_count > 5 // Sort by violation volume -| sort Esql.ml.policy.blocked.profanity.count desc +| sort Esql.ml_policy_blocked_profanity_count desc + ''' diff --git a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml index 90a88032c7a..15237659c4a 100644 --- a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml +++ b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml @@ -129,6 +129,7 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index esql.azure.signinlogs.auth.device_code.count_distinct > 0 or esql.azure.signinlogs.auth.visual_studio.count_distinct > 0 ) + ''' diff --git a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml index 3e513047214..daf180ad5b3 100644 --- a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml +++ b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml @@ -127,11 +127,12 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index | KEEP azure.signinlogs.properties.sign_in_identifier | STATS - Esql.auth.mfa.totp_failures.count = COUNT(*) + Esql.auth_mfa_totp_failures_count = COUNT(*) BY azure.signinlogs.properties.sign_in_identifier | WHERE - Esql.auth.mfa.totp_failures.count > 30 + Esql.auth_mfa_totp_failures_count > 30 + ''' diff --git a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml index df70626bd3f..37b9fbdc03f 100644 --- a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml +++ b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml @@ -114,64 +114,65 @@ FROM logs-azure.platformlogs-* METADATA _id, _index ) // Truncate timestamps into 1-minute windows -| EVAL Esql.time_window.date_trunc = DATE_TRUNC(1 minute, @timestamp) +| EVAL Esql.time_window_date_trunc = DATE_TRUNC(1 minute, @timestamp) // Aggregate identity, geo, resource, and activity info | STATS - Esql.azure.platformlogs.identity.claim.upn.values = VALUES(azure.platformlogs.identity.claim.upn), - Esql.azure.platformlogs.identity.claim.upn.count_distinct = COUNT_DISTINCT(azure.platformlogs.identity.claim.upn), - Esql.azure.platformlogs.identity.claim.appid.values = VALUES(azure.platformlogs.identity.claim.appid), - Esql.azure.platformlogs.identity.claim.objectid.values = VALUES(azure.platformlogs.identity.claim.objectid), - - Esql.source.ip.values = VALUES(source.ip), - Esql.geo.city.values = VALUES(geo.city_name), - Esql.geo.region.values = VALUES(geo.region_name), - Esql.geo.country.values = VALUES(geo.country_name), - Esql.network.as_org.values = VALUES(source.as.organization.name), - - Esql.event.action.values = VALUES(event.action), - Esql.event.count = COUNT(*), - Esql.event.action.count_distinct = COUNT_DISTINCT(event.action), - Esql.azure.resource.name.count_distinct = COUNT_DISTINCT(azure.resource.name), - Esql.azure.resource.name.values = VALUES(azure.resource.name), - Esql.azure.platformlogs.result_type.values = VALUES(azure.platformlogs.result_type), - Esql.cloud.region.values = VALUES(cloud.region), - - Esql.agent.name.values = VALUES(agent.name), - Esql.azure.subscription_id.values = VALUES(azure.subscription_id), - Esql.azure.resource_group.values = VALUES(azure.resource.group), - Esql.azure.resource_id.values = VALUES(azure.resource.id) - -BY Esql.time_window.date_trunc, azure.platformlogs.identity.claim.upn + Esql_priv.azure_platformlogs_identity_claim_upn_values = VALUES(azure.platformlogs.identity.claim.upn), + Esql.azure_platformlogs_identity_claim_upn_count_distinct = COUNT_DISTINCT(azure.platformlogs.identity.claim.upn), + Esql.azure_platformlogs_identity_claim_appid_values = VALUES(azure.platformlogs.identity.claim.appid), + Esql.azure_platformlogs_identity_claim_objectid_values = VALUES(azure.platformlogs.identity.claim.objectid), + + Esql.source_ip_values = VALUES(source.ip), + Esql.geo_city_values = VALUES(geo.city_name), + Esql.geo_region_values = VALUES(geo.region_name), + Esql.geo_country_values = VALUES(geo.country_name), + Esql.network_as_org_values = VALUES(source.as.organization.name), + + Esql.event_action_values = VALUES(event.action), + Esql.event_count = COUNT(*), + Esql.event_action_count_distinct = COUNT_DISTINCT(event.action), + Esql.azure_resource_name_count_distinct = COUNT_DISTINCT(azure.resource.name), + Esql.azure_resource_name_values = VALUES(azure.resource.name), + Esql.azure_platformlogs_result_type_values = VALUES(azure.platformlogs.result_type), + Esql.cloud_region_values = VALUES(cloud.region), + + Esql.agent_name_values = VALUES(agent.name), + Esql.azure_subscription_id_values = VALUES(azure.subscription_id), + Esql.azure_resource_group_values = VALUES(azure.resource.group), + Esql.azure_resource_id_values = VALUES(azure.resource.id) + +BY Esql.time_window_date_trunc, azure.platformlogs.identity.claim.upn // Keep relevant fields | KEEP - Esql.time_window.date_trunc, - Esql.azure.platformlogs.identity.claim.upn.values, - Esql.azure.platformlogs.identity.claim.upn.count_distinct, - Esql.azure.platformlogs.identity.claim.appid.values, - Esql.azure.platformlogs.identity.claim.objectid.values, - Esql.source.ip.values, - Esql.geo.city.values, - Esql.geo.region.values, - Esql.geo.country.values, - Esql.network.as_org.values, - Esql.event.action.values, - Esql.event.count, - Esql.event.action.count_distinct, - Esql.azure.resource.name.count_distinct, - Esql.azure.resource.name.values, - Esql.azure.platformlogs.result_type.values, - Esql.cloud.region.values, - Esql.agent.name.values, - Esql.azure.subscription_id.values, - Esql.azure.resource_group.values, - Esql.azure.resource_id.values + Esql.time_window_date_trunc, + Esql_priv.azure_platformlogs_identity_claim_upn_values, + Esql.azure_platformlogs_identity_claim_upn_count_distinct, + Esql.azure_platformlogs_identity_claim_appid_values, + Esql.azure_platformlogs_identity_claim_objectid_values, + Esql.source_ip_values, + Esql.geo_city_values, + Esql.geo_region_values, + Esql.geo_country_values, + Esql.network_as_org_values, + Esql.event_action_values, + Esql.event_count, + Esql.event_action_count_distinct, + Esql.azure_resource_name_count_distinct, + Esql.azure_resource_name_values, + Esql.azure_platformlogs_result_type_values, + Esql.cloud_region_values, + Esql.agent_name_values, + Esql.azure_subscription_id_values, + Esql.azure_resource_group_values, + Esql.azure_resource_id_values // Filter for suspiciously high volume of distinct Key Vault reads by a single actor -| WHERE Esql.azure.platformlogs.identity.claim.upn.count_distinct == 1 AND Esql.event.count >= 10 AND Esql.event.action.count_distinct >= 2 +| WHERE Esql.azure_platformlogs_identity_claim_upn_count_distinct == 1 AND Esql.event_count >= 10 AND Esql.event_action_count_distinct >= 2 + +| SORT Esql.time_window_date_trunc DESC -| SORT Esql.time_window.date_trunc DESC ''' diff --git a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml index 64bf8ecb3e4..63d6a0d9e4f 100644 --- a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml +++ b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml @@ -93,7 +93,7 @@ query = ''' FROM logs-azure.signinlogs* // Define a time window for grouping and maintain the original event timestamp -| EVAL Esql.time_window.date_trunc = DATE_TRUNC(15 minutes, @timestamp) +| EVAL Esql.time_window_date_trunc = DATE_TRUNC(15 minutes, @timestamp) // Filter relevant failed authentication events with specific error codes | WHERE event.dataset == "azure.signinlogs" @@ -128,102 +128,103 @@ FROM logs-azure.signinlogs* AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" | STATS - Esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), - Esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), - Esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - Esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), - Esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), - Esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), - Esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), - Esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), + Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_device_detail_browser_values = VALUES(azure.signinlogs.properties.device_detail.browser), + Esql.azure_signinlogs_properties_device_detail_device_id_values = VALUES(azure.signinlogs.properties.device_detail.device_id), + Esql.azure_signinlogs_properties_device_detail_operating_system_values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure_signinlogs_properties_incoming_token_type_values = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_session_id_values = VALUES(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_user_id_values = VALUES(azure.signinlogs.properties.user_id), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_result_description_values = VALUES(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_signature_values = VALUES(azure.signinlogs.result_signature), + Esql.azure_signinlogs_result_type_values = VALUES(azure.signinlogs.result_type), - Esql.azure.signinlogs.properties.user_id.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.user_id), - Esql.azure.signinlogs.properties.user_id.list = VALUES(azure.signinlogs.properties.user_id), - Esql.azure.signinlogs.result_description.values_all = VALUES(azure.signinlogs.result_description), - Esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - Esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), - Esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - Esql.azure.signinlogs.properties.incoming_token_type.values_all = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure.signinlogs.properties.app_display_name.values_all = VALUES(azure.signinlogs.properties.app_display_name), - Esql.source.ip.values = VALUES(source.ip), - Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.source.geo.country_name.values = VALUES(source.geo.country_name), - Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.timestamp.first_seen = MIN(@timestamp), - Esql.timestamp.last_seen = MAX(@timestamp), - Esql.event.count = COUNT() -BY Esql.time_window.date_trunc + Esql.azure_signinlogs_properties_user_id_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.user_id), + Esql.azure_signinlogs_properties_user_id_list = VALUES(azure.signinlogs.properties.user_id), + Esql.azure_signinlogs_result_description_values_all = VALUES(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_description_count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), + Esql.azure_signinlogs_properties_status_error_code_values = VALUES(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_status_error_code_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_incoming_token_type_values_all = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_app_display_name_values_all = VALUES(azure.signinlogs.properties.app_display_name), + Esql.source_ip_values = VALUES(source.ip), + Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), + Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_geo_country_name_values = VALUES(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.timestamp_first_seen = MIN(@timestamp), + Esql.timestamp_last_seen = MAX(@timestamp), + Esql.event_count = COUNT() +BY Esql.time_window_date_trunc | EVAL - Esql.duration.seconds = DATE_DIFF("seconds", Esql.timestamp.first_seen, Esql.timestamp.last_seen), - Esql.brute_force.type = CASE( - Esql.azure.signinlogs.properties.user_id.count_distinct >= 10 AND Esql.event.count >= 30 AND Esql.azure.signinlogs.result_description.count_distinct <= 3 - AND Esql.source.ip.count_distinct >= 5 - AND Esql.duration.seconds <= 600 - AND Esql.azure.signinlogs.properties.user_id.count_distinct > Esql.source.ip.count_distinct, + Esql.duration_seconds = DATE_DIFF("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen), + Esql.brute_force_type = CASE( + Esql.azure_signinlogs_properties_user_id_count_distinct >= 10 AND Esql.event_count >= 30 AND Esql.azure_signinlogs_result_description_count_distinct <= 3 + AND Esql.source_ip_count_distinct >= 5 + AND Esql.duration_seconds <= 600 + AND Esql.azure_signinlogs_properties_user_id_count_distinct > Esql.source_ip_count_distinct, "credential_stuffing", - Esql.azure.signinlogs.properties.user_id.count_distinct >= 15 AND Esql.azure.signinlogs.result_description.count_distinct == 1 AND Esql.event.count >= 15 AND Esql.duration.seconds <= 1800, + Esql.azure_signinlogs_properties_user_id_count_distinct >= 15 AND Esql.azure_signinlogs_result_description_count_distinct == 1 AND Esql.event_count >= 15 AND Esql.duration_seconds <= 1800, "password_spraying", - (Esql.azure.signinlogs.properties.user_id.count_distinct == 1 AND Esql.azure.signinlogs.result_description.count_distinct == 1 AND Esql.event.count >= 30 AND Esql.duration.seconds <= 300) - OR (Esql.azure.signinlogs.properties.user_id.count_distinct <= 3 AND Esql.source.ip.count_distinct > 30 AND Esql.event.count >= 100), + (Esql.azure_signinlogs_properties_user_id_count_distinct == 1 AND Esql.azure_signinlogs_result_description_count_distinct == 1 AND Esql.event_count >= 30 AND Esql.duration_seconds <= 300) + OR (Esql.azure_signinlogs_properties_user_id_count_distinct <= 3 AND Esql.source_ip_count_distinct > 30 AND Esql.event_count >= 100), "password_guessing", "other" ) | KEEP - Esql.time_window.date_trunc, - Esql.brute_force.type, - Esql.duration.seconds, - Esql.event.count, - Esql.timestamp.first_seen, - Esql.timestamp.last_seen, - Esql.azure.signinlogs.properties.user_id.count_distinct, - Esql.azure.signinlogs.properties.user_id.list, - Esql.azure.signinlogs.result_description.values_all, - Esql.azure.signinlogs.result_description.count_distinct, - Esql.azure.signinlogs.properties.status.error_code.count_distinct, - Esql.azure.signinlogs.properties.status.error_code.values, - Esql.azure.signinlogs.properties.incoming_token_type.values_all, - Esql.azure.signinlogs.properties.app_display_name.values_all, - Esql.source.ip.values, - Esql.source.ip.count_distinct, - Esql.source.`as`.organization.name.values, - Esql.source.geo.country_name.values, - Esql.source.geo.country_name.count_distinct, - Esql.source.`as`.organization.name.count_distinct, - Esql.azure.signinlogs.properties.authentication_requirement.values, - Esql.azure.signinlogs.properties.app_id.values, - Esql.azure.signinlogs.properties.app_display_name.values, - Esql.azure.signinlogs.properties.resource_id.values, - Esql.azure.signinlogs.properties.resource_display_name.values, - Esql.azure.signinlogs.properties.conditional_access_status.values, - Esql.azure.signinlogs.properties.device_detail.browser.values, - Esql.azure.signinlogs.properties.device_detail.device_id.values, - Esql.azure.signinlogs.properties.device_detail.operating_system.values, - Esql.azure.signinlogs.properties.incoming_token_type.values, - Esql.azure.signinlogs.properties.risk_state.values, - Esql.azure.signinlogs.properties.session_id.values, - Esql.azure.signinlogs.properties.user_id.values, - Esql.azure.signinlogs.properties.user_principal_name.values, - Esql.azure.signinlogs.result_description.values, - Esql.azure.signinlogs.result_signature.values, - Esql.azure.signinlogs.result_type.values + Esql.time_window_date_trunc, + Esql.brute_force_type, + Esql.duration_seconds, + Esql.event_count, + Esql.timestamp_first_seen, + Esql.timestamp_last_seen, + Esql.azure_signinlogs_properties_user_id_count_distinct, + Esql.azure_signinlogs_properties_user_id_list, + Esql.azure_signinlogs_result_description_values_all, + Esql.azure_signinlogs_result_description_count_distinct, + Esql.azure_signinlogs_properties_status_error_code_count_distinct, + Esql.azure_signinlogs_properties_status_error_code_values, + Esql.azure_signinlogs_properties_incoming_token_type_values_all, + Esql.azure_signinlogs_properties_app_display_name_values_all, + Esql.source_ip_values, + Esql.source_ip_count_distinct, + Esql.source_`as`.organization.name.values, + Esql.source_geo_country_name_values, + Esql.source_geo_country_name_count_distinct, + Esql.source_`as`.organization.name.count_distinct, + Esql.azure_signinlogs_properties_authentication_requirement_values, + Esql.azure_signinlogs_properties_app_id_values, + Esql.azure_signinlogs_properties_app_display_name_values, + Esql.azure_signinlogs_properties_resource_id_values, + Esql.azure_signinlogs_properties_resource_display_name_values, + Esql.azure_signinlogs_properties_conditional_access_status_values, + Esql.azure_signinlogs_properties_device_detail_browser_values, + Esql.azure_signinlogs_properties_device_detail_device_id_values, + Esql.azure_signinlogs_properties_device_detail_operating_system_values, + Esql.azure_signinlogs_properties_incoming_token_type_values, + Esql.azure_signinlogs_properties_risk_state_values, + Esql.azure_signinlogs_properties_session_id_values, + Esql.azure_signinlogs_properties_user_id_values, + Esql_priv.azure_signinlogs_properties_user_principal_name_values, + Esql.azure_signinlogs_result_description_values, + Esql.azure_signinlogs_result_signature_values, + Esql.azure_signinlogs_result_type_values + +| WHERE Esql.brute_force_type != "other" -| WHERE Esql.brute_force.type != "other" ''' diff --git a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml index de188c49828..18c42b5f6de 100644 --- a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml +++ b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml @@ -87,10 +87,10 @@ query = ''' FROM logs-azure.signinlogs* | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), - Esql.azure.signinlogs.properties.user_principal_name.lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), - Esql.azure.signinlogs.properties.incoming_token_type.lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), - Esql.azure.signinlogs.properties.app_display_name.lower = TO_LOWER(azure.signinlogs.properties.app_display_name) + Esql.time_window_date_trunc = DATE_TRUNC(30 minutes, @timestamp), + Esql_priv.azure_signinlogs_properties_user_principal_name_lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_properties_incoming_token_type_lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_app_display_name_lower = TO_LOWER(azure.signinlogs.properties.app_display_name) | WHERE event.dataset == "azure.signinlogs" AND event.category == "authentication" @@ -103,80 +103,81 @@ FROM logs-azure.signinlogs* AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" | STATS - Esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), - Esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), - Esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - Esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), - Esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), - Esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), - Esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), - Esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), - - Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct = COUNT_DISTINCT(Esql.azure.signinlogs.properties.user_principal_name.lower), - Esql.azure.signinlogs.properties.user_principal_name.lower.values = VALUES(Esql.azure.signinlogs.properties.user_principal_name.lower), - Esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - Esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - Esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), - Esql.azure.signinlogs.properties.incoming_token_type.lower.values = VALUES(Esql.azure.signinlogs.properties.incoming_token_type.lower), - Esql.azure.signinlogs.properties.app_display_name.lower.values = VALUES(Esql.azure.signinlogs.properties.app_display_name.lower), - Esql.source.ip.values = VALUES(source.ip), - Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.source.geo.country_name.values = VALUES(source.geo.country_name), - Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_device_detail_browser_values = VALUES(azure.signinlogs.properties.device_detail.browser), + Esql.azure_signinlogs_properties_device_detail_device_id_values = VALUES(azure.signinlogs.properties.device_detail.device_id), + Esql.azure_signinlogs_properties_device_detail_operating_system_values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure_signinlogs_properties_incoming_token_type_values = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_session_id_values = VALUES(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_user_id_values = VALUES(azure.signinlogs.properties.user_id), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_result_description_values = VALUES(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_signature_values = VALUES(azure.signinlogs.result_signature), + Esql.azure_signinlogs_result_type_values = VALUES(azure.signinlogs.result_type), + + Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = COUNT_DISTINCT(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), + Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = VALUES(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), + Esql.azure_signinlogs_result_description_count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), + Esql.azure_signinlogs_properties_status_error_code_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_status_error_code_values = VALUES(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_incoming_token_type_lower_values = VALUES(Esql.azure_signinlogs_properties_incoming_token_type_lower), + Esql.azure_signinlogs_properties_app_display_name_lower_values = VALUES(Esql.azure_signinlogs_properties_app_display_name_lower), + Esql.source_ip_values = VALUES(source.ip), + Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), + Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source_geo_country_name_values = VALUES(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), Esql.@timestamp.min = MIN(@timestamp), Esql.@timestamp.max = MAX(@timestamp), - Esql.event.count = COUNT() -BY Esql.time_window.date_trunc + Esql.event_count = COUNT() +BY Esql.time_window_date_trunc -| WHERE Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 AND Esql.event.count >= 20 +| WHERE Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 15 AND Esql.event_count >= 20 | KEEP - Esql.time_window.date_trunc, - Esql.event.count, + Esql.time_window_date_trunc, + Esql.event_count, Esql.@timestamp.min, Esql.@timestamp.max, - Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct, - Esql.azure.signinlogs.properties.user_principal_name.lower.values, - Esql.azure.signinlogs.result_description.count_distinct, - Esql.azure.signinlogs.result_description.values, - Esql.azure.signinlogs.properties.status.error_code.count_distinct, - Esql.azure.signinlogs.properties.status.error_code.values, - Esql.azure.signinlogs.properties.incoming_token_type.lower.values, - Esql.azure.signinlogs.properties.app_display_name.lower.values, - Esql.source.ip.values, - Esql.source.ip.count_distinct, - Esql.source.`as`.organization.name.values, - Esql.source.`as`.organization.name.count_distinct, - Esql.source.geo.country_name.values, - Esql.source.geo.country_name.count_distinct, - Esql.azure.signinlogs.properties.authentication_requirement.values, - Esql.azure.signinlogs.properties.app_id.values, - Esql.azure.signinlogs.properties.app_display_name.values, - Esql.azure.signinlogs.properties.resource_id.values, - Esql.azure.signinlogs.properties.resource_display_name.values, - Esql.azure.signinlogs.properties.conditional_access_status.values, - Esql.azure.signinlogs.properties.device_detail.browser.values, - Esql.azure.signinlogs.properties.device_detail.device_id.values, - Esql.azure.signinlogs.properties.device_detail.operating_system.values, - Esql.azure.signinlogs.properties.incoming_token_type.values, - Esql.azure.signinlogs.properties.risk_state.values, - Esql.azure.signinlogs.properties.session_id.values, - Esql.azure.signinlogs.properties.user_id.values, - Esql.azure.signinlogs.properties.user_principal_name.values, - Esql.azure.signinlogs.result_description.values, - Esql.azure.signinlogs.result_signature.values, - Esql.azure.signinlogs.result_type.values + Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct, + Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values, + Esql.azure_signinlogs_result_description_count_distinct, + Esql.azure_signinlogs_result_description_values, + Esql.azure_signinlogs_properties_status_error_code_count_distinct, + Esql.azure_signinlogs_properties_status_error_code_values, + Esql.azure_signinlogs_properties_incoming_token_type_lower_values, + Esql.azure_signinlogs_properties_app_display_name_lower_values, + Esql.source_ip_values, + Esql.source_ip_count_distinct, + Esql.source_`as`.organization.name.values, + Esql.source_`as`.organization.name.count_distinct, + Esql.source_geo_country_name_values, + Esql.source_geo_country_name_count_distinct, + Esql.azure_signinlogs_properties_authentication_requirement_values, + Esql.azure_signinlogs_properties_app_id_values, + Esql.azure_signinlogs_properties_app_display_name_values, + Esql.azure_signinlogs_properties_resource_id_values, + Esql.azure_signinlogs_properties_resource_display_name_values, + Esql.azure_signinlogs_properties_conditional_access_status_values, + Esql.azure_signinlogs_properties_device_detail_browser_values, + Esql.azure_signinlogs_properties_device_detail_device_id_values, + Esql.azure_signinlogs_properties_device_detail_operating_system_values, + Esql.azure_signinlogs_properties_incoming_token_type_values, + Esql.azure_signinlogs_properties_risk_state_values, + Esql.azure_signinlogs_properties_session_id_values, + Esql.azure_signinlogs_properties_user_id_values, + Esql_priv.azure_signinlogs_properties_user_principal_name_values, + Esql.azure_signinlogs_result_description_values, + Esql.azure_signinlogs_result_signature_values, + Esql.azure_signinlogs_result_type_values + ''' diff --git a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml index acd096eea1e..d386fbd0ace 100644 --- a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml +++ b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml @@ -91,11 +91,11 @@ query = ''' FROM logs-azure.signinlogs* | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(15 minutes, @timestamp), - Esql.azure.signinlogs.properties.user_principal_name.lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), - Esql.azure.signinlogs.properties.incoming_token_type.lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), - Esql.azure.signinlogs.properties.app_display_name.lower = TO_LOWER(azure.signinlogs.properties.app_display_name), - Esql.user_agent.original = user_agent.original + Esql.time_window_date_trunc = DATE_TRUNC(15 minutes, @timestamp), + Esql_priv.azure_signinlogs_properties_user_principal_name_lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_properties_incoming_token_type_lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_app_display_name_lower = TO_LOWER(azure.signinlogs.properties.app_display_name), + Esql.user_agent_original = user_agent.original | WHERE event.dataset == "azure.signinlogs" AND event.category == "authentication" @@ -130,108 +130,109 @@ FROM logs-azure.signinlogs* AND user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" | STATS - Esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), - Esql.azure.signinlogs.properties.device_detail.device_id.values = VALUES(azure.signinlogs.properties.device_detail.device_id), - Esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - Esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), - Esql.azure.signinlogs.properties.user_id.values = VALUES(azure.signinlogs.properties.user_id), - Esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), - Esql.azure.signinlogs.result_signature.values = VALUES(azure.signinlogs.result_signature), - Esql.azure.signinlogs.result_type.values = VALUES(azure.signinlogs.result_type), + Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_device_detail_browser_values = VALUES(azure.signinlogs.properties.device_detail.browser), + Esql.azure_signinlogs_properties_device_detail_device_id_values = VALUES(azure.signinlogs.properties.device_detail.device_id), + Esql.azure_signinlogs_properties_device_detail_operating_system_values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure_signinlogs_properties_incoming_token_type_values = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_session_id_values = VALUES(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_user_id_values = VALUES(azure.signinlogs.properties.user_id), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_result_description_values = VALUES(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_signature_values = VALUES(azure.signinlogs.result_signature), + Esql.azure_signinlogs_result_type_values = VALUES(azure.signinlogs.result_type), - Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct = COUNT_DISTINCT(Esql.azure.signinlogs.properties.user_principal_name.lower), - Esql.azure.signinlogs.properties.user_principal_name.lower.values = VALUES(Esql.azure.signinlogs.properties.user_principal_name.lower), - Esql.azure.signinlogs.result_description.count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - Esql.azure.signinlogs.result_description.values = VALUES(azure.signinlogs.result_description), - Esql.azure.signinlogs.properties.status.error_code.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - Esql.azure.signinlogs.properties.status.error_code.values = VALUES(azure.signinlogs.properties.status.error_code), - Esql.azure.signinlogs.properties.incoming_token_type.lower.values = VALUES(Esql.azure.signinlogs.properties.incoming_token_type.lower), - Esql.azure.signinlogs.properties.app_display_name.lower.values = VALUES(Esql.azure.signinlogs.properties.app_display_name.lower), - Esql.source.ip.values = VALUES(source.ip), - Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.source.geo.country_name.values = VALUES(source.geo.country_name), - Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = COUNT_DISTINCT(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), + Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = VALUES(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), + Esql.azure_signinlogs_result_description_count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_description_values = VALUES(azure.signinlogs.result_description), + Esql.azure_signinlogs_properties_status_error_code_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_status_error_code_values = VALUES(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_incoming_token_type_lower_values = VALUES(Esql.azure_signinlogs_properties_incoming_token_type_lower), + Esql.azure_signinlogs_properties_app_display_name_lower_values = VALUES(Esql.azure_signinlogs_properties_app_display_name_lower), + Esql.source_ip_values = VALUES(source.ip), + Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), + Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source_geo_country_name_values = VALUES(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), Esql.@timestamp.min = MIN(@timestamp), Esql.@timestamp.max = MAX(@timestamp), - Esql.event.count = COUNT() -BY Esql.time_window.date_trunc + Esql.event_count = COUNT() +BY Esql.time_window_date_trunc | EVAL - Esql.event.duration.seconds = DATE_DIFF("seconds", Esql.@timestamp.min, Esql.@timestamp.max), - Esql.event.bf_type = CASE( - Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 10 - AND Esql.event.count >= 30 - AND Esql.azure.signinlogs.result_description.count_distinct <= 3 - AND Esql.source.ip.count_distinct >= 5 - AND Esql.event.duration.seconds <= 600 - AND Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct > Esql.source.ip.count_distinct, + Esql.event_duration_seconds = DATE_DIFF("seconds", Esql.@timestamp.min, Esql.@timestamp.max), + Esql.event_bf_type = CASE( + Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 10 + AND Esql.event_count >= 30 + AND Esql.azure_signinlogs_result_description_count_distinct <= 3 + AND Esql.source_ip_count_distinct >= 5 + AND Esql.event_duration_seconds <= 600 + AND Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct > Esql.source_ip_count_distinct, "credential_stuffing", - Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct >= 15 - AND Esql.azure.signinlogs.result_description.count_distinct == 1 - AND Esql.event.count >= 15 - AND Esql.event.duration.seconds <= 1800, + Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 15 + AND Esql.azure_signinlogs_result_description_count_distinct == 1 + AND Esql.event_count >= 15 + AND Esql.event_duration_seconds <= 1800, "password_spraying", - (Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct == 1 - AND Esql.azure.signinlogs.result_description.count_distinct == 1 - AND Esql.event.count >= 30 - AND Esql.event.duration.seconds <= 300) - OR (Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct <= 3 - AND Esql.source.ip.count_distinct > 30 - AND Esql.event.count >= 100), + (Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct == 1 + AND Esql.azure_signinlogs_result_description_count_distinct == 1 + AND Esql.event_count >= 30 + AND Esql.event_duration_seconds <= 300) + OR (Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct <= 3 + AND Esql.source_ip_count_distinct > 30 + AND Esql.event_count >= 100), "password_guessing", "other" ) -| WHERE Esql.event.bf_type != "other" +| WHERE Esql.event_bf_type != "other" | KEEP - Esql.time_window.date_trunc, - Esql.event.bf_type, - Esql.event.duration.seconds, - Esql.event.count, + Esql.time_window_date_trunc, + Esql.event_bf_type, + Esql.event_duration_seconds, + Esql.event_count, Esql.@timestamp.min, Esql.@timestamp.max, - Esql.azure.signinlogs.properties.user_principal_name.lower.count_distinct, - Esql.azure.signinlogs.properties.user_principal_name.lower.values, - Esql.azure.signinlogs.result_description.count_distinct, - Esql.azure.signinlogs.result_description.values, - Esql.azure.signinlogs.properties.status.error_code.count_distinct, - Esql.azure.signinlogs.properties.status.error_code.values, - Esql.azure.signinlogs.properties.incoming_token_type.lower.values, - Esql.azure.signinlogs.properties.app_display_name.lower.values, - Esql.source.ip.values, - Esql.source.ip.count_distinct, - Esql.source.`as`.organization.name.values, - Esql.source.`as`.organization.name.count_distinct, - Esql.source.geo.country_name.values, - Esql.source.geo.country_name.count_distinct, - Esql.azure.signinlogs.properties.authentication_requirement.values, - Esql.azure.signinlogs.properties.app_id.values, - Esql.azure.signinlogs.properties.app_display_name.values, - Esql.azure.signinlogs.properties.resource_id.values, - Esql.azure.signinlogs.properties.resource_display_name.values, - Esql.azure.signinlogs.properties.conditional_access_status.values, - Esql.azure.signinlogs.properties.device_detail.browser.values, - Esql.azure.signinlogs.properties.device_detail.device_id.values, - Esql.azure.signinlogs.properties.device_detail.operating_system.values, - Esql.azure.signinlogs.properties.incoming_token_type.values, - Esql.azure.signinlogs.properties.risk_state.values, - Esql.azure.signinlogs.properties.session_id.values, - Esql.azure.signinlogs.properties.user_id.values + Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct, + Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values, + Esql.azure_signinlogs_result_description_count_distinct, + Esql.azure_signinlogs_result_description_values, + Esql.azure_signinlogs_properties_status_error_code_count_distinct, + Esql.azure_signinlogs_properties_status_error_code_values, + Esql.azure_signinlogs_properties_incoming_token_type_lower_values, + Esql.azure_signinlogs_properties_app_display_name_lower_values, + Esql.source_ip_values, + Esql.source_ip_count_distinct, + Esql.source_`as`.organization.name.values, + Esql.source_`as`.organization.name.count_distinct, + Esql.source_geo_country_name_values, + Esql.source_geo_country_name_count_distinct, + Esql.azure_signinlogs_properties_authentication_requirement_values, + Esql.azure_signinlogs_properties_app_id_values, + Esql.azure_signinlogs_properties_app_display_name_values, + Esql.azure_signinlogs_properties_resource_id_values, + Esql.azure_signinlogs_properties_resource_display_name_values, + Esql.azure_signinlogs_properties_conditional_access_status_values, + Esql.azure_signinlogs_properties_device_detail_browser_values, + Esql.azure_signinlogs_properties_device_detail_device_id_values, + Esql.azure_signinlogs_properties_device_detail_operating_system_values, + Esql.azure_signinlogs_properties_incoming_token_type_values, + Esql.azure_signinlogs_properties_risk_state_values, + Esql.azure_signinlogs_properties_session_id_values, + Esql.azure_signinlogs_properties_user_id_values + ''' diff --git a/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml b/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml index 5e758594be2..a18510b3845 100644 --- a/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml +++ b/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml @@ -99,54 +99,55 @@ FROM logs-azure.* AND azure.graphactivitylogs.properties.c_sid IS NOT NULL) | EVAL - Esql.azure.signinlogs.properties.session_id.coalesce = COALESCE(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid), - Esql.azure.signinlogs.properties.user_id.coalesce = COALESCE(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id), - Esql.azure.signinlogs.properties.app_id.coalesce = COALESCE(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id), - Esql.source.ip = source.ip, + Esql.azure_signinlogs_properties_session_id_coalesce = COALESCE(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid), + Esql.azure_signinlogs_properties_user_id_coalesce = COALESCE(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id), + Esql.azure_signinlogs_properties_app_id_coalesce = COALESCE(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id), + Esql.source_ip = source.ip, Esql.@timestamp = @timestamp, - Esql.event.type.case = CASE( + Esql.event_type_case = CASE( event.dataset == "azure.signinlogs", "signin", event.dataset == "azure.graphactivitylogs", "graph", "other" ), - Esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp) + Esql.time_window_date_trunc = DATE_TRUNC(5 minutes, @timestamp) | KEEP - Esql.azure.signinlogs.properties.session_id.coalesce, - Esql.source.ip, + Esql.azure_signinlogs_properties_session_id_coalesce, + Esql.source_ip, Esql.@timestamp, - Esql.event.type.case, - Esql.time_window.date_trunc, - Esql.azure.signinlogs.properties.user_id.coalesce, - Esql.azure.signinlogs.properties.app_id.coalesce + Esql.event_type_case, + Esql.time_window_date_trunc, + Esql.azure_signinlogs_properties_user_id_coalesce, + Esql.azure_signinlogs_properties_app_id_coalesce | STATS - Esql.azure.signinlogs.properties.user_id.coalesce.values = VALUES(Esql.azure.signinlogs.properties.user_id.coalesce), - Esql.azure.signinlogs.properties.session_id.coalesce.values = VALUES(Esql.azure.signinlogs.properties.session_id.coalesce), - Esql.source.ip.values = VALUES(Esql.source.ip), - Esql.source.ip.count_distinct = COUNT_DISTINCT(Esql.source.ip), - Esql.azure.signinlogs.properties.app_id.coalesce.values = VALUES(Esql.azure.signinlogs.properties.app_id.coalesce), - Esql.azure.signinlogs.properties.app_id.coalesce.count_distinct = COUNT_DISTINCT(Esql.azure.signinlogs.properties.app_id.coalesce), - Esql.event.type.case.values = VALUES(Esql.event.type.case), - Esql.event.type.case.count_distinct = COUNT_DISTINCT(Esql.event.type.case), + Esql.azure_signinlogs_properties_user_id_coalesce_values = VALUES(Esql.azure_signinlogs_properties_user_id_coalesce), + Esql.azure_signinlogs_properties_session_id_coalesce_values = VALUES(Esql.azure_signinlogs_properties_session_id_coalesce), + Esql.source_ip_values = VALUES(Esql.source_ip), + Esql.source_ip_count_distinct = COUNT_DISTINCT(Esql.source_ip), + Esql.azure_signinlogs_properties_app_id_coalesce_values = VALUES(Esql.azure_signinlogs_properties_app_id_coalesce), + Esql.azure_signinlogs_properties_app_id_coalesce_count_distinct = COUNT_DISTINCT(Esql.azure_signinlogs_properties_app_id_coalesce), + Esql.event_type_case_values = VALUES(Esql.event_type_case), + Esql.event_type_case_count_distinct = COUNT_DISTINCT(Esql.event_type_case), Esql.@timestamp.min = MIN(Esql.@timestamp), Esql.@timestamp.max = MAX(Esql.@timestamp), - Esql.signin.time.min = MIN(CASE(Esql.event.type.case == "signin", Esql.@timestamp, NULL)), - Esql.graph.time.min = MIN(CASE(Esql.event.type.case == "graph", Esql.@timestamp, NULL)), - Esql.event.count = COUNT() - BY Esql.azure.signinlogs.properties.session_id.coalesce, Esql.time_window.date_trunc + Esql.signin_time_min = MIN(CASE(Esql.event_type_case == "signin", Esql.@timestamp, NULL)), + Esql.graph_time_min = MIN(CASE(Esql.event_type_case == "graph", Esql.@timestamp, NULL)), + Esql.event_count = COUNT() + BY Esql.azure_signinlogs_properties_session_id_coalesce, Esql.time_window_date_trunc | EVAL - Esql.event.duration_minutes.date_diff = DATE_DIFF("minutes", Esql.@timestamp.min, Esql.@timestamp.max), - Esql.event.signin_to_graph_delay_minutes.date_diff = DATE_DIFF("minutes", Esql.signin.time.min, Esql.graph.time.min) + Esql.event_duration_minutes_date_diff = DATE_DIFF("minutes", Esql.@timestamp.min, Esql.@timestamp.max), + Esql.event_signin_to_graph_delay_minutes_date_diff = DATE_DIFF("minutes", Esql.signin_time_min, Esql.graph_time_min) | WHERE - Esql.event.type.case.count_distinct > 1 AND - Esql.source.ip.count_distinct > 1 AND - Esql.event.duration_minutes.date_diff <= 5 AND - Esql.signin.time.min IS NOT NULL AND - Esql.graph.time.min IS NOT NULL AND - Esql.event.signin_to_graph_delay_minutes.date_diff >= 0 + Esql.event_type_case_count_distinct > 1 AND + Esql.source_ip_count_distinct > 1 AND + Esql.event_duration_minutes_date_diff <= 5 AND + Esql.signin_time_min IS NOT NULL AND + Esql.graph_time_min IS NOT NULL AND + Esql.event_signin_to_graph_delay_minutes_date_diff >= 0 + ''' diff --git a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml index b5699b2f55d..a6ddd5e7adc 100644 --- a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml +++ b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml @@ -101,93 +101,94 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index azure.signinlogs.properties.resource_id == "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9" // DRS | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), - Esql.azure.signinlogs.properties.session_id = azure.signinlogs.properties.session_id, - Esql.is_browser.case = CASE( + Esql.time_window_date_trunc = DATE_TRUNC(30 minutes, @timestamp), + Esql.azure_signinlogs_properties_session_id = azure.signinlogs.properties.session_id, + Esql.is_browser_case = CASE( TO_LOWER(azure.signinlogs.properties.device_detail.browser) RLIKE "(chrome|firefox|edge|safari).*", 1, 0 ) | STATS - Esql.azure.signinlogs.properties.user_display_name.values = VALUES(azure.signinlogs.properties.user_display_name), - Esql.azure.signinlogs.properties.user_principal_name.values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure.signinlogs.properties.session_id.values = VALUES(azure.signinlogs.properties.session_id), - Esql.azure.signinlogs.properties.unique_token_identifier.values = VALUES(azure.signinlogs.properties.unique_token_identifier), - - Esql.source.geo.city_name.values = VALUES(source.geo.city_name), - Esql.source.geo.country_name.values = VALUES(source.geo.country_name), - Esql.source.geo.region_name.values = VALUES(source.geo.region_name), - Esql.source.address.values = VALUES(source.address), - Esql.source.address.count_distinct = COUNT_DISTINCT(source.address), - Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - - Esql.azure.signinlogs.properties.authentication_protocol.values = VALUES(azure.signinlogs.properties.authentication_protocol), - Esql.azure.signinlogs.properties.authentication_requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure.signinlogs.properties.is_interactive.values = VALUES(azure.signinlogs.properties.is_interactive), - - Esql.azure.signinlogs.properties.incoming_token_type.values = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure.signinlogs.properties.token_protection_status_details.sign_in_session_status.values = VALUES(azure.signinlogs.properties.token_protection_status_details.sign_in_session_status), - Esql.azure.signinlogs.properties.session_id.count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.session_id), - Esql.azure.signinlogs.properties.app_display_name.values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure.signinlogs.properties.app_id.values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure.signinlogs.properties.resource_id.values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure.signinlogs.properties.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - - Esql.azure.signinlogs.properties.app_owner_tenant_id.values = VALUES(azure.signinlogs.properties.app_owner_tenant_id), - Esql.azure.signinlogs.properties.resource_owner_tenant_id.values = VALUES(azure.signinlogs.properties.resource_owner_tenant_id), - - Esql.azure.signinlogs.properties.conditional_access_status.values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure.signinlogs.properties.risk_state.values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure.signinlogs.properties.risk_level_aggregated.values = VALUES(azure.signinlogs.properties.risk_level_aggregated), - - Esql.azure.signinlogs.properties.device_detail.browser.values = VALUES(azure.signinlogs.properties.device_detail.browser), - Esql.azure.signinlogs.properties.device_detail.operating_system.values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - Esql.user_agent.original.values = VALUES(user_agent.original), - Esql.is_browser.case.max = MAX(Esql.is_browser.case), - - Esql.event.count = COUNT(*) + Esql_priv.azure_signinlogs_properties_user_display_name_values = VALUES(azure.signinlogs.properties.user_display_name), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_properties_session_id_values = VALUES(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_unique_token_identifier_values = VALUES(azure.signinlogs.properties.unique_token_identifier), + + Esql.source_geo_city_name_values = VALUES(source.geo.city_name), + Esql.source_geo_country_name_values = VALUES(source.geo.country_name), + Esql.source_geo_region_name_values = VALUES(source.geo.region_name), + Esql.source_address_values = VALUES(source.address), + Esql.source_address_count_distinct = COUNT_DISTINCT(source.address), + Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + + Esql.azure_signinlogs_properties_authentication_protocol_values = VALUES(azure.signinlogs.properties.authentication_protocol), + Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_is_interactive_values = VALUES(azure.signinlogs.properties.is_interactive), + + Esql.azure_signinlogs_properties_incoming_token_type_values = VALUES(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_token_protection_status_details_sign_in_session_status_values = VALUES(azure.signinlogs.properties.token_protection_status_details.sign_in_session_status), + Esql.azure_signinlogs_properties_session_id_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), + + Esql.azure_signinlogs_properties_app_owner_tenant_id_values = VALUES(azure.signinlogs.properties.app_owner_tenant_id), + Esql.azure_signinlogs_properties_resource_owner_tenant_id_values = VALUES(azure.signinlogs.properties.resource_owner_tenant_id), + + Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_risk_level_aggregated_values = VALUES(azure.signinlogs.properties.risk_level_aggregated), + + Esql.azure_signinlogs_properties_device_detail_browser_values = VALUES(azure.signinlogs.properties.device_detail.browser), + Esql.azure_signinlogs_properties_device_detail_operating_system_values = VALUES(azure.signinlogs.properties.device_detail.operating_system), + Esql.user_agent_original_values = VALUES(user_agent.original), + Esql.is_browser_case_max = MAX(Esql.is_browser_case), + + Esql.event_count = COUNT(*) BY - Esql.time_window.date_trunc, + Esql.time_window_date_trunc, azure.signinlogs.properties.user_principal_name, azure.signinlogs.properties.session_id | KEEP - Esql.time_window.date_trunc, - Esql.azure.signinlogs.properties.user_display_name.values, - Esql.azure.signinlogs.properties.user_principal_name.values, - Esql.azure.signinlogs.properties.session_id.values, - Esql.azure.signinlogs.properties.unique_token_identifier.values, - Esql.source.geo.city_name.values, - Esql.source.geo.country_name.values, - Esql.source.geo.region_name.values, - Esql.source.address.values, - Esql.source.address.count_distinct, - Esql.source.`as`.organization.name.values, - Esql.azure.signinlogs.properties.authentication_protocol.values, - Esql.azure.signinlogs.properties.authentication_requirement.values, - Esql.azure.signinlogs.properties.is_interactive.values, - Esql.azure.signinlogs.properties.incoming_token_type.values, - Esql.azure.signinlogs.properties.token_protection_status_details.sign_in_session_status.values, - Esql.azure.signinlogs.properties.session_id.count_distinct, - Esql.azure.signinlogs.properties.app_display_name.values, - Esql.azure.signinlogs.properties.app_id.values, - Esql.azure.signinlogs.properties.resource_id.values, - Esql.azure.signinlogs.properties.resource_display_name.values, - Esql.azure.signinlogs.properties.app_owner_tenant_id.values, - Esql.azure.signinlogs.properties.resource_owner_tenant_id.values, - Esql.azure.signinlogs.properties.conditional_access_status.values, - Esql.azure.signinlogs.properties.risk_state.values, - Esql.azure.signinlogs.properties.risk_level_aggregated.values, - Esql.azure.signinlogs.properties.device_detail.browser.values, - Esql.azure.signinlogs.properties.device_detail.operating_system.values, - Esql.user_agent.original.values, - Esql.is_browser.case.max, - Esql.event.count + Esql.time_window_date_trunc, + Esql_priv.azure_signinlogs_properties_user_display_name_values, + Esql_priv.azure_signinlogs_properties_user_principal_name_values, + Esql.azure_signinlogs_properties_session_id_values, + Esql.azure_signinlogs_properties_unique_token_identifier_values, + Esql.source_geo_city_name_values, + Esql.source_geo_country_name_values, + Esql.source_geo_region_name_values, + Esql.source_address_values, + Esql.source_address_count_distinct, + Esql.source_`as`.organization.name.values, + Esql.azure_signinlogs_properties_authentication_protocol_values, + Esql.azure_signinlogs_properties_authentication_requirement_values, + Esql.azure_signinlogs_properties_is_interactive_values, + Esql.azure_signinlogs_properties_incoming_token_type_values, + Esql.azure_signinlogs_properties_token_protection_status_details_sign_in_session_status_values, + Esql.azure_signinlogs_properties_session_id_count_distinct, + Esql.azure_signinlogs_properties_app_display_name_values, + Esql.azure_signinlogs_properties_app_id_values, + Esql.azure_signinlogs_properties_resource_id_values, + Esql.azure_signinlogs_properties_resource_display_name_values, + Esql.azure_signinlogs_properties_app_owner_tenant_id_values, + Esql.azure_signinlogs_properties_resource_owner_tenant_id_values, + Esql.azure_signinlogs_properties_conditional_access_status_values, + Esql.azure_signinlogs_properties_risk_state_values, + Esql.azure_signinlogs_properties_risk_level_aggregated_values, + Esql.azure_signinlogs_properties_device_detail_browser_values, + Esql.azure_signinlogs_properties_device_detail_operating_system_values, + Esql.user_agent_original_values, + Esql.is_browser_case_max, + Esql.event_count | WHERE - Esql.source.address.count_distinct >= 2 AND - Esql.azure.signinlogs.properties.session_id.count_distinct == 1 AND - Esql.is_browser.case.max >= 1 AND - Esql.event.count >= 2 + Esql.source_address_count_distinct >= 2 AND + Esql.azure_signinlogs_properties_session_id_count_distinct == 1 AND + Esql.is_browser_case_max >= 1 AND + Esql.event_count >= 2 + ''' diff --git a/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml b/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml index 0d3bb16bd9e..bbfea7a52f1 100644 --- a/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml @@ -78,22 +78,23 @@ type = "esql" query = ''' FROM logs-azure_openai.logs-* | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) + Esql.time_window_date_trunc = DATE_TRUNC(1 minutes, @timestamp) | WHERE azure.open_ai.operation_name == "ChatCompletions_Create" | KEEP azure.open_ai.properties.request_length, azure.resource.name, cloud.account.id, - Esql.time_window.date_trunc + Esql.time_window_date_trunc | STATS - Esql.event.count = COUNT(*), - Esql.azure.open_ai.properties.request_length.avg = AVG(azure.open_ai.properties.request_length) + Esql.event_count = COUNT(*), + Esql.azure_open_ai_properties_request_length_avg = AVG(azure.open_ai.properties.request_length) BY - Esql.time_window.date_trunc, + Esql.time_window_date_trunc, azure.resource.name | WHERE - Esql.event.count >= 10 AND - Esql.azure.open_ai.properties.request_length.avg >= 5000 -| SORT Esql.event.count DESC + Esql.event_count >= 10 AND + Esql.azure_open_ai_properties_request_length_avg >= 5000 +| SORT Esql.event_count DESC + ''' diff --git a/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml b/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml index abef4409258..c570a581497 100644 --- a/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml @@ -82,12 +82,13 @@ FROM logs-azure_openai.logs-* cloud.account.id, azure.resource.name | STATS - Esql.event.count = COUNT(*) + Esql.event_count = COUNT(*) BY azure.resource.name | WHERE - Esql.event.count >= 10 + Esql.event_count >= 10 | SORT - Esql.event.count DESC + Esql.event_count DESC + ''' diff --git a/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml b/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml index 352660589bb..9caa72ff224 100644 --- a/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml @@ -85,15 +85,16 @@ FROM logs-azure_openai.logs-* azure.resource.name, azure.open_ai.properties.response_length | STATS - Esql.event.count = COUNT(*), - Esql.azure.open_ai.properties.response_length.max = MAX(azure.open_ai.properties.response_length) + Esql.event_count = COUNT(*), + Esql.azure_open_ai_properties_response_length_max = MAX(azure.open_ai.properties.response_length) BY azure.resource.group, azure.resource.name | WHERE - Esql.event.count >= 100 OR - Esql.azure.open_ai.properties.response_length.max >= 1000000 + Esql.event_count >= 100 OR + Esql.azure_open_ai_properties_response_length_max >= 1000000 | SORT - Esql.event.count DESC + Esql.event_count DESC + ''' diff --git a/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml b/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml index 6429fe775a3..f798080c63a 100644 --- a/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml +++ b/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml @@ -89,21 +89,22 @@ FROM logs-o365.audit-* o365.audit.AuthenticationType == "OAuth" AND event.outcome == "success" | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(1 minutes, @timestamp) + Esql.time_window_date_trunc = DATE_TRUNC(1 minutes, @timestamp) | KEEP - Esql.time_window.date_trunc, + Esql.time_window_date_trunc, o365.audit.UserId, file.name, source.ip | STATS - Esql.file.name.count_distinct = COUNT_DISTINCT(file.name), - Esql.event.count = COUNT(*) + Esql.file_name_count_distinct = COUNT_DISTINCT(file.name), + Esql.event_count = COUNT(*) BY - Esql.time_window.date_trunc, + Esql.time_window_date_trunc, o365.audit.UserId, source.ip | WHERE - Esql.file.name.count_distinct >= 25 + Esql.file_name_count_distinct >= 25 + ''' diff --git a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml index d9dd2ab9647..1d933d2b919 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml @@ -75,7 +75,7 @@ query = ''' FROM logs-o365.audit-* | MV_EXPAND event.category | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp) + Esql.time_window_date_trunc = DATE_TRUNC(5 minutes, @timestamp) | WHERE event.dataset == "o365.audit" AND event.category == "authentication" AND @@ -87,40 +87,41 @@ FROM logs-o365.audit-* o365.audit.Target.Type IN ("0", "2", "6", "10") AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" | STATS - Esql.o365.audit.UserId.count_distinct = COUNT_DISTINCT(TO_LOWER(o365.audit.UserId)), - Esql.o365.audit.UserId.values = VALUES(TO_LOWER(o365.audit.UserId)), - Esql.source.ip.values = VALUES(source.ip), - Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.source.geo.country_name.values = VALUES(source.geo.country_name), - Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(TO_LOWER(o365.audit.ExtendedProperties.RequestType)), - Esql.timestamp.first_seen = MIN(@timestamp), - Esql.timestamp.last_seen = MAX(@timestamp), - Esql.event.count = COUNT(*) - BY Esql.time_window.date_trunc + Esql_priv.o365_audit_UserId_count_distinct = COUNT_DISTINCT(TO_LOWER(o365.audit.UserId)), + Esql_priv.o365_audit_UserId_values = VALUES(TO_LOWER(o365.audit.UserId)), + Esql.source_ip_values = VALUES(source.ip), + Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), + Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source_geo_country_name_values = VALUES(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.o365_audit_ExtendedProperties_RequestType_values = VALUES(TO_LOWER(o365.audit.ExtendedProperties.RequestType)), + Esql.timestamp_first_seen = MIN(@timestamp), + Esql.timestamp_last_seen = MAX(@timestamp), + Esql.event_count = COUNT(*) + BY Esql.time_window_date_trunc | EVAL - Esql.event.duration.seconds = DATE_DIFF("seconds", Esql.timestamp.first_seen, Esql.timestamp.last_seen) + Esql.event_duration_seconds = DATE_DIFF("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen) | KEEP - Esql.time_window.date_trunc, - Esql.o365.audit.UserId.count_distinct, - Esql.o365.audit.UserId.values, - Esql.source.ip.values, - Esql.source.ip.count_distinct, - Esql.source.`as`.organization.name.values, - Esql.source.`as`.organization.name.count_distinct, - Esql.source.geo.country_name.values, - Esql.source.geo.country_name.count_distinct, - Esql.o365.audit.ExtendedProperties.RequestType.values, - Esql.timestamp.first_seen, - Esql.timestamp.last_seen, - Esql.event.count, - Esql.event.duration.seconds + Esql.time_window_date_trunc, + Esql_priv.o365_audit_UserId_count_distinct, + Esql_priv.o365_audit_UserId_values, + Esql.source_ip_values, + Esql.source_ip_count_distinct, + Esql.source_`as`.organization.name.values, + Esql.source_`as`.organization.name.count_distinct, + Esql.source_geo_country_name_values, + Esql.source_geo_country_name_count_distinct, + Esql.o365_audit_ExtendedProperties_RequestType_values, + Esql.timestamp_first_seen, + Esql.timestamp_last_seen, + Esql.event_count, + Esql.event_duration_seconds | WHERE - Esql.o365.audit.UserId.count_distinct >= 10 AND - Esql.event.count >= 10 AND - Esql.event.duration.seconds <= 300 + Esql_priv.o365_audit_UserId_count_distinct >= 10 AND + Esql.event_count >= 10 AND + Esql.event_duration_seconds <= 300 + ''' diff --git a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml index a84fae94d59..bea916eea8c 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml @@ -81,18 +81,18 @@ query = ''' FROM logs-o365.audit-* | MV_EXPAND event.category | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(5 minutes, @timestamp), - Esql.o365.audit.UserId.lower = TO_LOWER(o365.audit.UserId), - Esql.o365.audit.LogonError = o365.audit.LogonError, - Esql.o365.audit.ExtendedProperties.RequestType.lower = TO_LOWER(o365.audit.ExtendedProperties.RequestType) + Esql.time_window_date_trunc = DATE_TRUNC(5 minutes, @timestamp), + Esql_priv.o365_audit_UserId_lower = TO_LOWER(o365.audit.UserId), + Esql.o365_audit_LogonError = o365.audit.LogonError, + Esql.o365_audit_ExtendedProperties_RequestType_lower = TO_LOWER(o365.audit.ExtendedProperties.RequestType) | WHERE event.dataset == "o365.audit" AND event.category == "authentication" AND event.provider IN ("AzureActiveDirectory", "Exchange") AND event.action IN ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") AND - Esql.o365.audit.ExtendedProperties.RequestType.lower RLIKE "(oauth.*||.*login.*)" AND - Esql.o365.audit.LogonError != "IdsLocked" AND - Esql.o365.audit.LogonError NOT IN ( + Esql.o365_audit_ExtendedProperties_RequestType_lower RLIKE "(oauth.*||.*login.*)" AND + Esql.o365_audit_LogonError != "IdsLocked" AND + Esql.o365_audit_LogonError NOT IN ( "EntitlementGrantsNotFound", "UserStrongAuthEnrollmentRequired", "UserStrongAuthClientAuthNRequired", @@ -103,51 +103,52 @@ FROM logs-o365.audit-* "UserStrongAuthExpired", "CmsiInterrupt" ) AND - Esql.o365.audit.UserId.lower != "not available" AND + Esql_priv.o365_audit_UserId_lower != "not available" AND o365.audit.Target.Type IN ("0", "2", "6", "10") | STATS - Esql.user.id.count_distinct = COUNT_DISTINCT(Esql.o365.audit.UserId.lower), - Esql.o365.audit.UserId.lower.values = VALUES(Esql.o365.audit.UserId.lower), - Esql.o365.audit.LogonError.values = VALUES(Esql.o365.audit.LogonError), - Esql.o365.audit.LogonError.count_distinct = COUNT_DISTINCT(Esql.o365.audit.LogonError), - Esql.o365.audit.ExtendedProperties.RequestType.values = VALUES(Esql.o365.audit.ExtendedProperties.RequestType.lower), - Esql.source.ip.values = VALUES(source.ip), - Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.source.geo.country_name.values = VALUES(source.geo.country_name), - Esql.source.geo.country_name.count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.source.`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.timestamp.first_seen = MIN(@timestamp), - Esql.timestamp.last_seen = MAX(@timestamp), - Esql.event.count = COUNT(*) - BY Esql.time_window.date_trunc + Esql.o365_audit_UserId_lower_count_distinct = COUNT_DISTINCT(Esql_priv.o365_audit_UserId_lower), + Esql_priv.o365_audit_UserId_lower_values = VALUES(Esql_priv.o365_audit_UserId_lower), + Esql.o365_audit_LogonError_values = VALUES(Esql.o365_audit_LogonError), + Esql.o365_audit_LogonError_count_distinct = COUNT_DISTINCT(Esql.o365_audit_LogonError), + Esql.o365_audit_ExtendedProperties_RequestType_values = VALUES(Esql.o365_audit_ExtendedProperties_RequestType_lower), + Esql.source_ip_values = VALUES(source.ip), + Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), + Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_geo_country_name_values = VALUES(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), + Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.timestamp_first_seen = MIN(@timestamp), + Esql.timestamp_last_seen = MAX(@timestamp), + Esql.event_count = COUNT(*) + BY Esql.time_window_date_trunc | EVAL - Esql.event.duration.seconds = DATE_DIFF("seconds", Esql.timestamp.first_seen, Esql.timestamp.last_seen), - Esql.brute_force.type = CASE( - Esql.user.id.count_distinct >= 15 AND Esql.o365.audit.LogonError.count_distinct == 1 AND Esql.event.count >= 10 AND Esql.event.duration.seconds <= 1800, "password_spraying", - Esql.user.id.count_distinct >= 8 AND Esql.event.count >= 15 AND Esql.o365.audit.LogonError.count_distinct <= 3 AND Esql.source.ip.count_distinct <= 5 AND Esql.event.duration.seconds <= 600, "credential_stuffing", - Esql.user.id.count_distinct == 1 AND Esql.o365.audit.LogonError.count_distinct == 1 AND Esql.event.count >= 20 AND Esql.event.duration.seconds <= 300, "password_guessing", + Esql.event_duration_seconds = DATE_DIFF("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen), + Esql.brute_force_type = CASE( + Esql.o365_audit_UserId_lower_count_distinct >= 15 AND Esql.o365_audit_LogonError_count_distinct == 1 AND Esql.event_count >= 10 AND Esql.event_duration_seconds <= 1800, "password_spraying", + Esql.o365_audit_UserId_lower_count_distinct >= 8 AND Esql.event_count >= 15 AND Esql.o365_audit_LogonError_count_distinct <= 3 AND Esql.source_ip_count_distinct <= 5 AND Esql.event_duration_seconds <= 600, "credential_stuffing", + Esql.o365_audit_UserId_lower_count_distinct == 1 AND Esql.o365_audit_LogonError_count_distinct == 1 AND Esql.event_count >= 20 AND Esql.event_duration_seconds <= 300, "password_guessing", "other" ) | KEEP - Esql.time_window.date_trunc, - Esql.user.id.count_distinct, - Esql.o365.audit.UserId.lower.values, - Esql.o365.audit.LogonError.values, - Esql.o365.audit.LogonError.count_distinct, - Esql.o365.audit.ExtendedProperties.RequestType.values, - Esql.source.ip.values, - Esql.source.ip.count_distinct, - Esql.source.`as`.organization.name.values, - Esql.source.geo.country_name.values, - Esql.source.geo.country_name.count_distinct, - Esql.source.`as`.organization.name.count_distinct, - Esql.timestamp.first_seen, - Esql.timestamp.last_seen, - Esql.event.duration.seconds, - Esql.event.count, - Esql.brute_force.type -| WHERE Esql.brute_force.type != "other" + Esql.time_window_date_trunc, + Esql.o365_audit_UserId_lower_count_distinct, + Esql_priv.o365_audit_UserId_lower_values, + Esql.o365_audit_LogonError_values, + Esql.o365_audit_LogonError_count_distinct, + Esql.o365_audit_ExtendedProperties_RequestType_values, + Esql.source_ip_values, + Esql.source_ip_count_distinct, + Esql.source_`as`.organization.name.values, + Esql.source_geo_country_name_values, + Esql.source_geo_country_name_count_distinct, + Esql.source_`as`.organization.name.count_distinct, + Esql.timestamp_first_seen, + Esql.timestamp_last_seen, + Esql.event_duration_seconds, + Esql.event_count, + Esql.brute_force_type +| WHERE Esql.brute_force_type != "other" + ''' diff --git a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml index c0fb3a05395..3b2e8f80b13 100644 --- a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml +++ b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml @@ -79,41 +79,42 @@ FROM logs-o365.audit-* o365.audit.ApplicationId IN ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") AND o365.audit.ObjectId IN ("00000003-0000-0000-c000-000000000000") | EVAL - Esql.time_window.date_trunc = DATE_TRUNC(30 minutes, @timestamp), - Esql.oauth.authorize.user_id.case = CASE( + Esql.time_window_date_trunc = DATE_TRUNC(30 minutes, @timestamp), + Esql.oauth_authorize_user_id_case = CASE( o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" AND o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", o365.audit.UserId, null ), - Esql.oauth.token.user_id.case = CASE( + Esql.oauth_token_user_id_case = CASE( o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", o365.audit.UserId, null ) | STATS - Esql.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - Esql.source.ip.values = VALUES(source.ip), - Esql.o365.audit.ApplicationId.values = VALUES(o365.audit.ApplicationId), - Esql.source.`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.oauth.token.count_distinct = COUNT_DISTINCT(Esql.oauth.token.user_id.case), - Esql.oauth.authorize.count_distinct = COUNT_DISTINCT(Esql.oauth.authorize.user_id.case) + Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), + Esql.source_ip_values = VALUES(source.ip), + Esql.o365_audit_ApplicationId_values = VALUES(o365.audit.ApplicationId), + Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.oauth_token_count_distinct = COUNT_DISTINCT(Esql.oauth_token_user_id_case), + Esql.oauth_authorize_count_distinct = COUNT_DISTINCT(Esql.oauth_authorize_user_id_case) BY o365.audit.UserId, - Esql.time_window.date_trunc, + Esql.time_window_date_trunc, o365.audit.ApplicationId, o365.audit.ObjectId | KEEP - Esql.time_window.date_trunc, - Esql.source.ip.values, - Esql.source.ip.count_distinct, - Esql.o365.audit.ApplicationId.values, - Esql.source.`as`.organization.name.values, - Esql.oauth.token.count_distinct, - Esql.oauth.authorize.count_distinct + Esql.time_window_date_trunc, + Esql.source_ip_values, + Esql.source_ip_count_distinct, + Esql.o365_audit_ApplicationId_values, + Esql.source_`as`.organization.name.values, + Esql.oauth_token_count_distinct, + Esql.oauth_authorize_count_distinct | WHERE - Esql.source.ip.count_distinct >= 2 AND - Esql.oauth.token.count_distinct > 0 AND - Esql.oauth.authorize.count_distinct > 0 + Esql.source_ip_count_distinct >= 2 AND + Esql.oauth_token_count_distinct > 0 AND + Esql.oauth_authorize_count_distinct > 0 + ''' diff --git a/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml b/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml index 0f6b0030b5f..709170122e1 100644 --- a/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml +++ b/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml @@ -86,7 +86,7 @@ FROM logs-okta* "user.authentication.sso" ) AND okta.actor.alternate_id != "system@okta.com" AND - okta.actor.alternate_id RLIKE "[^@\\s]+\\@[^@\\s]+" AND + okta.actor.alternate_id RLIKE "[^@\s]+\@[^@\s]+" AND okta.authentication_context.external_session_id != "unknown" | KEEP event.action, @@ -94,14 +94,15 @@ FROM logs-okta* okta.authentication_context.external_session_id, okta.debug_context.debug_data.dt_hash | STATS - Esql.okta.debug_context.debug_data.dt_hash.count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) + Esql.okta_debug_context_debug_data_dt_hash_count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) BY okta.actor.alternate_id, okta.authentication_context.external_session_id | WHERE - Esql.okta.debug_context.debug_data.dt_hash.count_distinct >= 2 + Esql.okta_debug_context_debug_data_dt_hash_count_distinct >= 2 | SORT - Esql.okta.debug_context.debug_data.dt_hash.count_distinct DESC + Esql.okta_debug_context_debug_data_dt_hash_count_distinct DESC + ''' diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml index 271b54da350..0b97ad8ec72 100644 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml +++ b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml @@ -93,7 +93,7 @@ query = ''' FROM logs-okta* | WHERE event.dataset == "okta.system" AND - (event.action == "user.session.start" OR event.action RLIKE "user\\.authentication(.*)") AND + (event.action == "user.session.start" OR event.action RLIKE "user\.authentication(.*)") AND okta.outcome.reason == "INVALID_CREDENTIALS" | KEEP okta.client.ip, @@ -102,14 +102,15 @@ FROM logs-okta* event.action, okta.outcome.reason | STATS - Esql.okta.actor.id.count_distinct = COUNT_DISTINCT(okta.actor.id) + Esql.okta_actor_id_count_distinct = COUNT_DISTINCT(okta.actor.id) BY okta.client.ip, okta.actor.alternate_id | WHERE - Esql.okta.actor.id.count_distinct > 5 + Esql.okta_actor_id_count_distinct > 5 | SORT - Esql.okta.actor.id.count_distinct DESC + Esql.okta_actor_id_count_distinct DESC + ''' diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml index de2f6fac456..6ad517f0ae8 100644 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml +++ b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml @@ -90,7 +90,7 @@ query = ''' FROM logs-okta* | WHERE event.dataset == "okta.system" AND - (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") AND + (event.action RLIKE "user\.authentication(.*)" OR event.action == "user.session.start") AND okta.debug_context.debug_data.dt_hash != "-" AND okta.outcome.reason == "INVALID_CREDENTIALS" | KEEP @@ -100,14 +100,15 @@ FROM logs-okta* okta.actor.alternate_id, okta.outcome.reason | STATS - Esql.okta.actor.id.count_distinct = COUNT_DISTINCT(okta.actor.id) + Esql.okta_actor_id_count_distinct = COUNT_DISTINCT(okta.actor.id) BY okta.debug_context.debug_data.dt_hash, okta.actor.alternate_id | WHERE - Esql.okta.actor.id.count_distinct > 20 + Esql.okta_actor_id_count_distinct > 20 | SORT - Esql.okta.actor.id.count_distinct DESC + Esql.okta_actor_id_count_distinct DESC + ''' diff --git a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml index 1b4d33f09ab..0e2748307f7 100644 --- a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml +++ b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml @@ -94,7 +94,7 @@ query = ''' FROM logs-okta* | WHERE event.dataset == "okta.system" AND - (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") AND + (event.action RLIKE "user\.authentication(.*)" OR event.action == "user.session.start") AND okta.debug_context.debug_data.request_uri == "/api/v1/authn" AND okta.outcome.reason == "INVALID_CREDENTIALS" | KEEP @@ -105,14 +105,15 @@ FROM logs-okta* okta.debug_context.debug_data.request_uri, okta.outcome.reason | STATS - Esql.okta.debug_context.debug_data.dt_hash.count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) + Esql.okta_debug_context_debug_data_dt_hash_count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) BY okta.client.ip, okta.actor.alternate_id | WHERE - Esql.okta.debug_context.debug_data.dt_hash.count_distinct >= 30 + Esql.okta_debug_context_debug_data_dt_hash_count_distinct >= 30 | SORT - Esql.okta.debug_context.debug_data.dt_hash.count_distinct DESC + Esql.okta_debug_context_debug_data_dt_hash_count_distinct DESC + ''' diff --git a/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml b/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml index 3482b6558ab..85d346ce886 100644 --- a/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml +++ b/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml @@ -81,7 +81,7 @@ query = ''' FROM logs-okta* | WHERE event.dataset == "okta.system" AND - (event.action RLIKE "user\\.authentication(.*)" OR event.action == "user.session.start") AND + (event.action RLIKE "user\.authentication(.*)" OR event.action == "user.session.start") AND okta.security_context.is_proxy != true AND okta.actor.id != "unknown" AND event.outcome == "success" @@ -93,12 +93,13 @@ FROM logs-okta* event.outcome, client.geo.country_name | STATS - Esql.client.geo.country_name.count_distinct = COUNT_DISTINCT(client.geo.country_name) + Esql.client_geo_country_name_count_distinct = COUNT_DISTINCT(client.geo.country_name) BY okta.actor.id, okta.actor.alternate_id | WHERE - Esql.client.geo.country_name.count_distinct >= 2 + Esql.client_geo_country_name_count_distinct >= 2 | SORT - Esql.client.geo.country_name.count_distinct DESC + Esql.client_geo_country_name_count_distinct DESC + ''' diff --git a/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml b/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml index 329c5960eb6..d4e5f878a01 100644 --- a/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml +++ b/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml @@ -129,16 +129,17 @@ FROM logs-endpoint.events.network-* agent.id, host.name | STATS - Esql.event.count = COUNT(), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), - Esql.host.name.values = VALUES(host.name), - Esql.agent.id.values = VALUES(agent.id) + Esql.event_count = COUNT(), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.executable | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.event.count > 15 -| SORT Esql.event.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.event_count > 15 +| SORT Esql.event_count ASC | LIMIT 100 + ''' diff --git a/rules/linux/defense_evasion_base64_decoding_activity.toml b/rules/linux/defense_evasion_base64_decoding_activity.toml index 6b20afabfe5..9bf4997d72b 100644 --- a/rules/linux/defense_evasion_base64_decoding_activity.toml +++ b/rules/linux/defense_evasion_base64_decoding_activity.toml @@ -143,16 +143,17 @@ FROM logs-endpoint.events.process-* agent.id, host.name | STATS - Esql.event.count = COUNT(), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), - Esql.host.name.values = VALUES(host.name), - Esql.agent.id.values = VALUES(agent.id) + Esql.event_count = COUNT(), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.name, process.command_line | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.event.count < 15 -| SORT Esql.event.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.event_count < 15 +| SORT Esql.event_count ASC | LIMIT 100 + ''' diff --git a/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml index 4c153bf5561..790a6b5fb14 100644 --- a/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml @@ -112,17 +112,18 @@ FROM logs-endpoint.events.network-* agent.id, host.name | STATS - Esql.event.count = COUNT(), - Esql.destination.port.count_distinct = COUNT_DISTINCT(destination.port), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), - Esql.host.name.values = VALUES(host.name), - Esql.agent.id.values = VALUES(agent.id) + Esql.event_count = COUNT(), + Esql.destination_port_count_distinct = COUNT_DISTINCT(destination.port), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.executable, destination.ip | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.destination.port.count_distinct > 100 -| SORT Esql.event.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.destination_port_count_distinct > 100 +| SORT Esql.event_count ASC | LIMIT 100 + ''' diff --git a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml index 765ba1c7963..513557ec27e 100644 --- a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml @@ -102,17 +102,18 @@ FROM logs-endpoint.events.network-* event.type == "start" AND event.action == "connection_attempted" | STATS - Esql.connection.count = COUNT(), - Esql.destination.ip.count_distinct = COUNT_DISTINCT(destination.ip), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.connection_count = COUNT(), + Esql.destination_ip_count_distinct = COUNT_DISTINCT(destination.ip), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.destination.ip.count_distinct > 250 -| SORT Esql.connection.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.destination_ip_count_distinct > 250 +| SORT Esql.connection_count ASC | LIMIT 100 + ''' diff --git a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml index 894597feaa4..012f88378da 100644 --- a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml +++ b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml @@ -102,16 +102,17 @@ FROM logs-endpoint.events.process-* event.action == "exec" AND process.name IN ("scp", "ftp", "sftp", "vsftpd", "sftp-server", "rsync") | STATS - Esql.event.count = COUNT(), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event_count = COUNT(), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable, process.parent.executable, process.command_line | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.event.count < 5 -| SORT Esql.event.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.event_count < 5 +| SORT Esql.event_count ASC | LIMIT 100 + ''' diff --git a/rules/linux/impact_potential_bruteforce_malware_infection.toml b/rules/linux/impact_potential_bruteforce_malware_infection.toml index 2fba52d92be..b76482f2956 100644 --- a/rules/linux/impact_potential_bruteforce_malware_infection.toml +++ b/rules/linux/impact_potential_bruteforce_malware_infection.toml @@ -115,16 +115,17 @@ FROM logs-endpoint.events.network-* "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1", "FE80::/10", "FF00::/8" ) | STATS - Esql.event.count = COUNT(), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event_count = COUNT(), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable, destination.port | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.event.count > 15 -| SORT Esql.event.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.event_count > 15 +| SORT Esql.event_count ASC | LIMIT 100 + ''' diff --git a/rules/linux/persistence_web_server_sus_child_spawned.toml b/rules/linux/persistence_web_server_sus_child_spawned.toml index 2a0469b5569..9527dc485a0 100644 --- a/rules/linux/persistence_web_server_sus_child_spawned.toml +++ b/rules/linux/persistence_web_server_sus_child_spawned.toml @@ -137,16 +137,17 @@ FROM logs-endpoint.events.process-* process.parent.executable LIKE "/vscode/vscode-server/*" ) | STATS - Esql.event.count = COUNT(), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event_count = COUNT(), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable, process.working_directory, process.parent.executable | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.event.count < 5 -| SORT Esql.event.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.event_count < 5 +| SORT Esql.event_count ASC | LIMIT 100 + ''' diff --git a/rules/linux/persistence_web_server_sus_command_execution.toml b/rules/linux/persistence_web_server_sus_command_execution.toml index 0f8765de6ba..d3d1259d0ee 100644 --- a/rules/linux/persistence_web_server_sus_command_execution.toml +++ b/rules/linux/persistence_web_server_sus_command_execution.toml @@ -147,16 +147,17 @@ FROM logs-endpoint.events.process-* process.parent.executable == "/usr/bin/xfce4-terminal" ) | STATS - Esql.event.count = COUNT(), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event_count = COUNT(), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.command_line, process.working_directory, process.parent.executable | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.event.count < 5 -| SORT Esql.event.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.event_count < 5 +| SORT Esql.event_count ASC | LIMIT 100 + ''' diff --git a/rules/windows/credential_access_rare_webdav_destination.toml b/rules/windows/credential_access_rare_webdav_destination.toml index f94f89d989e..c5248abcfcb 100644 --- a/rules/windows/credential_access_rare_webdav_destination.toml +++ b/rules/windows/credential_access_rare_webdav_destination.toml @@ -63,23 +63,23 @@ FROM logs-* process.command_line LIKE "*DavSetCookie*" | KEEP host.id, process.command_line, user.name | GROK - process.command_line """(?DavSetCookie .* http)""" + process.command_line """(?DavSetCookie .* http)""" | EVAL - Esql.server.webdav.cookie.replace = REPLACE(Esql.server.webdav.cookie, "(DavSetCookie | http)", "") + Esql.server_webdav_cookie_replace = REPLACE(Esql.server_webdav_cookie, "(DavSetCookie | http)", "") | WHERE - Esql.server.webdav.cookie.replace IS NOT NULL AND - Esql.server.webdav.cookie.replace RLIKE """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" AND - NOT Esql.server.webdav.cookie.replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND - NOT Esql.server.webdav.cookie.replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" + Esql.server_webdav_cookie_replace IS NOT NULL AND + Esql.server_webdav_cookie_replace RLIKE """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" AND + NOT Esql.server_webdav_cookie_replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND + NOT Esql.server_webdav_cookie_replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" | STATS - Esql.event.count = COUNT(*), - Esql.host.id.count_distinct = COUNT_DISTINCT(host.id), + Esql.event_count = COUNT(*), + Esql.host_id_count_distinct = COUNT_DISTINCT(host.id), host.id.values = VALUES(host.id), user.name.values = VALUES(user.name) - BY Esql.server.webdav.cookie.replace + BY Esql.server_webdav_cookie_replace | WHERE - Esql.host.id.count_distinct == 1 AND - Esql.event.count <= 3 + Esql.host_id_count_distinct == 1 AND + Esql.event_count <= 3 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml index e6a86b84651..83e61322569 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml @@ -97,15 +97,15 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") +| EVAL Esql.script_block_text_replace = REPLACE(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.script_block_text.character.count = LENGTH(Esql.script_block_text.replace) - LENGTH(REPLACE(Esql.script_block_text.replace, "🔥", "")) +| EVAL Esql.script_block_text_character_count = LENGTH(Esql.script_block_text_replace) - LENGTH(REPLACE(Esql.script_block_text_replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - Esql.script_block_text.character.count, - Esql.script_block_text.replace, + Esql.script_block_text_character_count, + Esql.script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.name, @@ -118,7 +118,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id, user.id -| WHERE Esql.script_block_text.character.count >= 10 +| WHERE Esql.script_block_text_character_count >= 10 // Filter FPs, and due to the behavior of the LIKE operator, allow null values | WHERE (file.name NOT LIKE "TSS_*.psm1" OR file.name IS NULL) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index 4c9d8ef2998..0a1a489ab31 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -88,21 +88,21 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL Esql.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.script_block_text.length > 500 +| EVAL Esql.script_block_text_length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.script_block_text_length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.replace.length, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql.powershell_file_script_block_text_replace_length, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -115,7 +115,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id, user.id -| WHERE Esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml index 59cf18827da..0020f79389e 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml @@ -94,19 +94,19 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( powershell.file.script_block_text, """(char\[\]\]\(\d+,\d+[^)]+|(\s?\(\[char\]\d+\s?\)\+){2,})""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -119,7 +119,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Final filter on match count -| WHERE Esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml index ac75142d866..2ca93ddaf08 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml @@ -88,19 +88,19 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace suspicious method string constructions with 🔥 for entropy-style detection // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( powershell.file.script_block_text, """[.&]\(\s*(['"][A-Za-z0-9.-]+['"]\s*\+\s*)+['"][A-Za-z0-9.-]+['"]\s*\)""", "🔥" ) // Count how many patterns were detected based on 🔥 characters -| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep relevant context fields for triage | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -113,7 +113,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Alert if suspicious pattern is present -| WHERE Esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml index 4c2f98d6025..3b748eb9f43 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml @@ -87,22 +87,22 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Measure script length -| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell.file.script_block_text.length > 1000 +| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell_file_script_block_text_length > 1000 // Replace digits with 🔥 for numeric pattern density analysis -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[0-9]""", "🔥") +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE(powershell.file.script_block_text, """[0-9]""", "🔥") // Count numeric characters and calculate proportion of total -| EVAL Esql.powershell.file.script_block_text.character.count = Esql.powershell.file.script_block_text.length - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) -| EVAL Esql.powershell.file.script_block_text.character.ratio = Esql.powershell.file.script_block_text.character.count::double / Esql.powershell.file.script_block_text.length::double +| EVAL Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double // Retain relevant fields for investigation | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.character.ratio, - Esql.powershell.file.script_block_text.length, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql.powershell_file_script_block_text_character_ratio, + Esql.powershell_file_script_block_text_length, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -115,7 +115,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for suspiciously high numeric content -| WHERE Esql.powershell.file.script_block_text.character.ratio > 0.30 +| WHERE Esql.powershell_file_script_block_text_character_ratio > 0.30 // Exclude noisy patterns such as 64-character hashes | WHERE NOT powershell.file.script_block_text RLIKE """.*\"[a-fA-F0-9]{64}\"\,.*""" diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml index 86baabf2712..f1f5f6f38d6 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml @@ -87,24 +87,24 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Evaluate the length of the script block -| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell_file_script_block_text_length > 500 // Replace obfuscated dynamic string construction with 🔥 to count obfuscation attempts -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( powershell.file.script_block_text, """(?i)(\$(?:\w+|\w+\:\w+)\[\d++\]\+\$(?:\w+|\w+\:\w+)\[\d++\]\+['"]x['"]|\$(?:\w+\:\w+)\[\d++,\d++,\d++\]|\.name\[\d++,\d++,\d++\])""", "🔥" ) // Count how many 🔥 were inserted -| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep relevant context fields | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.length, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql.powershell_file_script_block_text_length, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -117,7 +117,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for meaningful pattern matches -| WHERE Esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml index baf64e93837..5d9ef7a2c7b 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml @@ -88,24 +88,24 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Check script length > 500 -| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell_file_script_block_text_length > 500 // Replace method access patterns with 🔥 for detection -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( powershell.file.script_block_text, """(?i)['"]['"].(Insert|Normalize|Chars|SubString|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", "🔥" ) // Count 🔥 instances to determine presence of suspicious method usage -| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep relevant fields for context and triage | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.length, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql.powershell_file_script_block_text_length, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -118,7 +118,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Only return if at least one pattern is found -| WHERE Esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml index adbee1e6730..997f142d3a8 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml @@ -91,25 +91,25 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell_file_script_block_text_length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( powershell.file.script_block_text, """\$\w+\[\-\s?1\.\.""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.length, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql.powershell_file_script_block_text_length, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -122,7 +122,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for patterns found at least once -| WHERE Esql.powershell.file.script_block_text.character.count >= 1 +| WHERE Esql.powershell_file_script_block_text_character_count >= 1 // FP Patterns | WHERE NOT powershell.file.script_block_text LIKE "*GENESIS-5654*" diff --git a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml index f9ecf62c07a..eb4d19d5532 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml @@ -93,19 +93,19 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( powershell.file.script_block_text, """(?i)(rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman\.|:vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod)""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -116,7 +116,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id // Filter for scripts with at least 2 suspicious keyword matches -| WHERE Esql.powershell.file.script_block_text.character.count >= 2 +| WHERE Esql.powershell_file_script_block_text_character_count >= 2 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml index bf9d6c1d1e4..7400d0d97c5 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml @@ -89,25 +89,25 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell_file_script_block_text_length > 500 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( powershell.file.script_block_text, """['"][A-Za-z0-9.]+['"](\s?\+\s?['"][A-Za-z0-9.,\-\s]+['"]){2,}""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell.file.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.length, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql.powershell_file_script_block_text_length, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -120,7 +120,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with at least 2 suspicious string concatenations -| WHERE Esql.powershell.file.script_block_text.character.count >= 2 +| WHERE Esql.powershell_file_script_block_text_character_count >= 2 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index 35ae1e545dd..c7c78d48317 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -88,26 +88,26 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell.file.script_block_text.length > 500 +| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) +| WHERE Esql.powershell_file_script_block_text_length > 500 | WHERE powershell.file.script_block_text LIKE "*{0}*" // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( powershell.file.script_block_text, """((\{\d+\}){2,}["']\s?-f|::Format[^\{]+(\{\d+\}){2,})""", "🔥" ) // Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell.script_block_text.character.count = LENGTH(Esql.powershell.file.script_block_text.replace) - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) +| EVAL Esql.powershell_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - Esql.powershell.script_block_text.character.count, - Esql.powershell.file.script_block_text.length, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_script_block_text_character_count, + Esql.powershell_file_script_block_text_length, + Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -121,7 +121,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with more than 3 suspicious format patterns -| WHERE Esql.powershell.script_block_text.character.count > 3 +| WHERE Esql.powershell_script_block_text_character_count > 3 // Exclude Noisy Patterns diff --git a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml index 0ebec4050ce..a56f4989802 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml @@ -89,30 +89,30 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Replace repeated spaces used for formatting after a new line with a single space to reduce FPs -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """\n\s+""", "\n ") +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE(powershell.file.script_block_text, """\n\s+""", "\n ") // Look for scripts with more than 1000 chars -| EVAL Esql.powershell.file.script_block_text.replace.length = LENGTH(Esql.powershell.file.script_block_text.replace) -| WHERE Esql.powershell.file.script_block_text.replace.length > 1000 +| EVAL Esql.powershell_file_script_block_text_replace_length = LENGTH(Esql_priv.powershell_file_script_block_text_replace) +| WHERE Esql.powershell_file_script_block_text_replace_length > 1000 // Replace format characters with 🔥 to count suspicious formatting density -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE( - Esql.powershell.file.script_block_text.replace, +| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( + Esql_priv.powershell_file_script_block_text_replace, """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\.]""", "🔥" ) // Count 🔥 and calculate its proportion of the script -| EVAL Esql.powershell.file.script_block_text.count = Esql.powershell.file.script_block_text.length - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) -| EVAL Esql.powershell.file.script_block_text.ratio = Esql.powershell.file.script_block_text.count::double / Esql.powershell.file.script_block_text.length::double +| EVAL Esql.powershell_file_script_block_text_count = Esql.powershell_file_script_block_text_length - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_ratio = Esql.powershell_file_script_block_text_count::double / Esql.powershell_file_script_block_text_length::double // Retain fields for context or alert generation | KEEP - Esql.powershell.file.script_block_text.count, - Esql.powershell.file.script_block_text.length, - Esql.powershell.file.script_block_text.ratio, - Esql.powershell.file.script_block_text.replace, - Esql.powershell.file.script_block_text.replace.length, + Esql.powershell_file_script_block_text_count, + Esql.powershell_file_script_block_text_length, + Esql.powershell_file_script_block_text_ratio, + Esql_priv.powershell_file_script_block_text_replace, + Esql.powershell_file_script_block_text_replace_length, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -125,7 +125,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for high-ratio suspicious formatting scripts -| WHERE Esql.powershell.file.script_block_text.ratio > 0.75 +| WHERE Esql.powershell_file_script_block_text_ratio > 0.75 ''' diff --git a/rules/windows/execution_posh_malicious_script_agg.toml b/rules/windows/execution_posh_malicious_script_agg.toml index ba00893252f..f2fd5270e48 100644 --- a/rules/windows/execution_posh_malicious_script_agg.toml +++ b/rules/windows/execution_posh_malicious_script_agg.toml @@ -113,21 +113,21 @@ FROM .alerts-security.* METADATA _id | WHERE kibana.alert.rule.name LIKE "*PowerShell*" // As alerts don't have non-ECS fields, parse the script block ID using GROK -| GROK message "ScriptBlock ID: (?.+)" -| WHERE Esql.message.powershell.file.script_block_id IS NOT NULL +| GROK message "ScriptBlock ID: (?.+)" +| WHERE Esql.message_powershell_file_script_block_id IS NOT NULL // Keep relevant fields for further processing -| KEEP kibana.alert.rule.name, Esql.message.powershell.file.script_block_id, _id +| KEEP kibana.alert.rule.name, Esql.message_powershell_file_script_block_id, _id // Count distinct alerts and filter for matches above the threshold | STATS - Esql.kibana.alert.rule.name.count_distinct = COUNT_DISTINCT(kibana.alert.rule.name), - Esql.kibana.alert.rule.name.values = VALUES(kibana.alert.rule.name), - Esql._id.values = VALUES(_id) - BY Esql.message.powershell.file.script_block_id + Esql.kibana_alert_rule_name_count_distinct = COUNT_DISTINCT(kibana.alert.rule.name), + Esql.kibana_alert_rule_name_values = VALUES(kibana.alert.rule.name), + Esql._id_values = VALUES(_id) + BY Esql.message_powershell_file_script_block_id // Apply detection threshold -| WHERE Esql.kibana.alert.rule.name.count_distinct >= 5 +| WHERE Esql.kibana_alert_rule_name_count_distinct >= 5 ''' diff --git a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml index af047923c26..d00c137bb61 100644 --- a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml +++ b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml @@ -53,26 +53,26 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index | WHERE event.code == "4104" // Look for scripts with more than 1000 chars that contain a related keyword -| EVAL Esql.powershell.file.script_block_text.length = LENGTH(powershell.file.script_block_text) +| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) // Filter for long scripts -| WHERE Esql.powershell.file.script_block_text.length > 1000 +| WHERE Esql.powershell_file_script_block_text_length > 1000 // Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 // Excludes spaces, #, = and - as they are heavily used in scripts for formatting -| EVAL Esql.powershell.file.script_block_text.replace = REPLACE(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") +| EVAL Esql.powershell_file_script_block_text_replace = REPLACE(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") // Count the occurrence of special chars and their proportion to the total chars in the script -| EVAL Esql.powershell.file.script_block_text.character.count = Esql.powershell.file.script_block_text.length - LENGTH(REPLACE(Esql.powershell.file.script_block_text.replace, "🔥", "")) -| EVAL Esql.powershell.file.script_block_text.character.ratio = Esql.powershell.file.script_block_text.character.count::double / Esql.powershell.file.script_block_text.length::double +| EVAL Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - LENGTH(REPLACE(Esql.powershell_file_script_block_text_replace, "🔥", "")) +| EVAL Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double // Keep the fields relevant to the query, although this is not needed as the alert is populated using _id | KEEP - Esql.powershell.file.script_block_text.character.count, - Esql.powershell.file.script_block_text.length, - Esql.powershell.file.script_block_text.character.ratio, - Esql.powershell.file.script_block_text.replace, + Esql.powershell_file_script_block_text_character_count, + Esql.powershell_file_script_block_text_length, + Esql.powershell_file_script_block_text_character_ratio, + Esql.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -85,7 +85,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with a 25%+ proportion of special chars -| WHERE Esql.powershell.file.script_block_text.character.ratio > 0.25 +| WHERE Esql.powershell_file_script_block_text_character_ratio > 0.25 ''' diff --git a/rules_building_block/persistence_web_server_sus_file_creation.toml b/rules_building_block/persistence_web_server_sus_file_creation.toml index 864489ecfab..8fd34b40832 100644 --- a/rules_building_block/persistence_web_server_sus_file_creation.toml +++ b/rules_building_block/persistence_web_server_sus_file_creation.toml @@ -95,15 +95,15 @@ FROM logs-endpoint.events.file-* process.name LIKE "perl*" ) | STATS - Esql.event.count = COUNT(), - Esql.agent.id.count_distinct = COUNT_DISTINCT(agent.id), + Esql.event_count = COUNT(), + Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), host.name.values = VALUES(host.name), agent.id.values = VALUES(agent.id) BY process.executable, file.path | WHERE - Esql.agent.id.count_distinct == 1 AND - Esql.event.count < 5 -| SORT Esql.event.count ASC + Esql.agent_id_count_distinct == 1 AND + Esql.event_count < 5 +| SORT Esql.event_count ASC | LIMIT 100 ''' From 3f44a6739eec0b7098d198cdb9afb04abc18a84f Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Thu, 24 Jul 2025 14:49:49 -0400 Subject: [PATCH 83/94] updated dates --- ...rsistence_iam_user_created_access_keys_for_another_user.toml | 2 +- .../credential_access_azure_key_vault_excessive_retrieval.toml | 2 +- rules/windows/discovery_command_system_account.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml b/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml index 8d1fab06403..bafdeda125d 100644 --- a/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml +++ b/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml @@ -2,7 +2,7 @@ creation_date = "2024/06/13" integration = ["aws"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/24" [rule] author = ["Elastic"] diff --git a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml index 37b9fbdc03f..ab67e077995 100644 --- a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml +++ b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml @@ -2,7 +2,7 @@ creation_date = "2025/07/10" integration = ["azure"] maturity = "production" -updated_date = "2025/07/10" +updated_date = "2025/07/24" [rule] author = ["Elastic"] diff --git a/rules/windows/discovery_command_system_account.toml b/rules/windows/discovery_command_system_account.toml index 15445beca1e..b2a19004a01 100644 --- a/rules/windows/discovery_command_system_account.toml +++ b/rules/windows/discovery_command_system_account.toml @@ -2,7 +2,7 @@ creation_date = "2020/03/18" integration = ["endpoint", "windows"] maturity = "production" -updated_date = "2025/05/20" +updated_date = "2025/07/24" [rule] author = ["Elastic"] From 68c672ddaed383a33d4abef4ce7c8728251efada Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Fri, 25 Jul 2025 10:36:11 -0400 Subject: [PATCH 84/94] updated dates --- .../aws/persistence_iam_create_login_profile_for_root.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml b/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml index 7ad38bb5369..b4d8541ae00 100644 --- a/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml +++ b/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml @@ -2,7 +2,7 @@ creation_date = "2024/12/02" integration = ["aws"] maturity = "production" -updated_date = "2025/03/20" +updated_date = "2025/07/26" [rule] author = ["Elastic"] From a5a5f01248a1c16949ae5aa1be889313c7503858 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 30 Jul 2025 14:16:24 -0400 Subject: [PATCH 85/94] updated ESQL fields --- ...al_access_signin_console_login_no_mfa.toml | 9 +++--- ..._access_azure_entra_suspicious_signin.toml | 31 +++++++++---------- ...s_azure_key_vault_excessive_retrieval.toml | 4 +-- ..._access_entra_id_brute_force_activity.toml | 8 ++--- ...s_entra_id_excessive_account_lockouts.toml | 8 ++--- ...ntra_signin_brute_force_microsoft_365.toml | 8 ++--- ...ous_oauth_flow_via_auth_broker_to_drs.toml | 4 +-- ...ce_entra_id_oidc_discovery_url_change.toml | 16 +++++----- ...rosoft_365_excessive_account_lockouts.toml | 8 ++--- ...65_potential_user_account_brute_force.toml | 8 ++--- ...crosoft_365_susp_oauth2_authorization.toml | 4 +-- ...ense_evasion_base64_decoding_activity.toml | 6 ++-- ...anning_activity_from_compromised_host.toml | 8 ++--- ...nusual_file_transfer_utility_launched.toml | 4 +-- ...otential_bruteforce_malware_infection.toml | 4 +-- ...sistence_web_server_sus_child_spawned.toml | 4 +-- ...ence_web_server_sus_command_execution.toml | 4 +-- ...ential_access_rare_webdav_destination.toml | 4 +-- ...sistence_web_server_sus_file_creation.toml | 4 +-- 19 files changed, 72 insertions(+), 74 deletions(-) diff --git a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml index 1c2252e7688..7e9dc88ea8a 100644 --- a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml +++ b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml @@ -78,10 +78,10 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Extract mobile version and MFA usage | dissect aws.cloudtrail.additional_eventdata - "{%{?mobile_version_key}=%{Esql.device_version}, %{?mfa_used_key}=%{Esql.auth_mfa_used}}" + "{%{?mobile_version_key}=%{Esql.aws_cloudtrail_additional_eventdata_device_version}, %{?mfa_used_key}=%{Esql.aws_cloudtrail_additional_eventdata_auth_mfa_used}}" // Only keep events where MFA was not used -| where Esql.auth_mfa_used == "No" +| where Esql.aws_cloudtrail_additional_eventdata_auth_mfa_used == "No" // Keep relevant ECS and dissected fields | keep @@ -89,9 +89,8 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.action, aws.cloudtrail.event_type, aws.cloudtrail.user_identity.type, - Esql.device_version, - Esql.auth_mfa_used - + Esql.aws_cloudtrail_additional_eventdata_device_version, + Esql.aws_cloudtrail_additional_eventdata_auth_mfa_used ''' diff --git a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml index 15237659c4a..6e0f94ddcf9 100644 --- a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml +++ b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml @@ -91,19 +91,19 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index // Case classifications for identity usage | eval - esql.azure.signinlogs.properties.authentication.device_code.case = case( + Esql.azure_signinlogs_properties_authentication_device_code_case = case( azure.signinlogs.properties.authentication_protocol == "deviceCode" and azure.signinlogs.properties.authentication_requirement != "multiFactorAuthentication", azure.signinlogs.identity, null), - esql.azure.signinlogs.auth.visual_studio.case = case( + Esql.azure_signinlogs_auth_visual_studio_case = case( azure.signinlogs.properties.app_id == "aebc6443-996d-45c2-90f0-388ff96faa56" and azure.signinlogs.properties.resource_display_name == "Microsoft Graph", azure.signinlogs.identity, null), - esql.azure.signinlogs.auth.other.case = case( + Esql.azure_signinlogs_auth_other_case = case( azure.signinlogs.properties.authentication_protocol != "deviceCode" and azure.signinlogs.properties.app_id != "aebc6443-996d-45c2-90f0-388ff96faa56", azure.signinlogs.identity, @@ -111,25 +111,24 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index // Aggregate metrics by user identity | stats - esql.event.count = COUNT(*), - esql.azure.signinlogs.properties.authentication.device_code.case.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.properties.authentication.device_code.case), - esql.azure.signinlogs.auth.visual_studio.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.auth.visual_studio.case), - esql.azure.signinlogs.auth.other.count_distinct = COUNT_DISTINCT(esql.azure.signinlogs.auth.other.case), - esql.azure.signinlogs.source.ip.count_distinct = COUNT_DISTINCT(source.ip), - esql.azure.signinlogs.source.ip.values = VALUES(source.ip), - esql.azure.signinlogs.client_app.values = VALUES(azure.signinlogs.properties.app_display_name), - esql.azure.signinlogs.resource_display_name.values = VALUES(azure.signinlogs.properties.resource_display_name), - esql.azure.signinlogs.auth.requirement.values = VALUES(azure.signinlogs.properties.authentication_requirement) + Esql.event_count = COUNT(*), + Esql.azure_signinlogs_properties_authentication_device_code_case_count_distinct = COUNT_DISTINCT(Esql.azure_signinlogs_properties_authentication_device_code_case), + Esql.azure_signinlogs_properties_auth_visual_studio_count_distinct = COUNT_DISTINCT(Esql.azure_signinlogs_auth_visual_studio_case), + Esql.azure_signinlogs_properties_auth_other_count_distinct = COUNT_DISTINCT(Esql.azure_signinlogs_auth_other_case), + Esql.azure_signinlogs_properties_source_ip_count_distinct = COUNT_DISTINCT(source.ip), + Esql.azure_signinlogs_properties_source_ip_values = VALUES(source.ip), + Esql.azure_signinlogs_properties_client_app_values = VALUES(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_auth_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement) by azure.signinlogs.identity // Detect multiple unique IPs for one user with signs of deviceCode or VSC OAuth usage | where - esql.azure.signinlogs.source.ip.count_distinct >= 2 + Esql.azure_signinlogs_properties_source_ip_count_distinct >= 2 and ( - esql.azure.signinlogs.auth.device_code.count_distinct > 0 - or esql.azure.signinlogs.auth.visual_studio.count_distinct > 0 + Esql.azure_signinlogs_properties_authentication_device_code_case_count_distinct > 0 + or Esql.azure_signinlogs_properties_auth_visual_studio_count_distinct > 0 ) - ''' diff --git a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml index ab67e077995..cec78a72025 100644 --- a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml +++ b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml @@ -127,7 +127,7 @@ FROM logs-azure.platformlogs-* METADATA _id, _index Esql.geo_city_values = VALUES(geo.city_name), Esql.geo_region_values = VALUES(geo.region_name), Esql.geo_country_values = VALUES(geo.country_name), - Esql.network_as_org_values = VALUES(source.as.organization.name), + Esql.source_as_organization_name_values = VALUES(source.as.organization.name), Esql.event_action_values = VALUES(event.action), Esql.event_count = COUNT(*), @@ -155,7 +155,7 @@ BY Esql.time_window_date_trunc, azure.platformlogs.identity.claim.upn Esql.geo_city_values, Esql.geo_region_values, Esql.geo_country_values, - Esql.network_as_org_values, + Esql.source_as_organization_name_values, Esql.event_action_values, Esql.event_count, Esql.event_action_count_distinct, diff --git a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml index 63d6a0d9e4f..25cbab78969 100644 --- a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml +++ b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml @@ -156,10 +156,10 @@ FROM logs-azure.signinlogs* Esql.azure_signinlogs_properties_app_display_name_values_all = VALUES(azure.signinlogs.properties.app_display_name), Esql.source_ip_values = VALUES(source.ip), Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), Esql.source_geo_country_name_values = VALUES(source.geo.country_name), Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), Esql.timestamp_first_seen = MIN(@timestamp), Esql.timestamp_last_seen = MAX(@timestamp), Esql.event_count = COUNT() @@ -201,10 +201,10 @@ BY Esql.time_window_date_trunc Esql.azure_signinlogs_properties_app_display_name_values_all, Esql.source_ip_values, Esql.source_ip_count_distinct, - Esql.source_`as`.organization.name.values, + Esql.source_as_organization_name_values, Esql.source_geo_country_name_values, Esql.source_geo_country_name_count_distinct, - Esql.source_`as`.organization.name.count_distinct, + Esql.source_as_organization_name_count_distinct, Esql.azure_signinlogs_properties_authentication_requirement_values, Esql.azure_signinlogs_properties_app_id_values, Esql.azure_signinlogs_properties_app_display_name_values, diff --git a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml index 18c42b5f6de..94c29ccd982 100644 --- a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml +++ b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml @@ -130,8 +130,8 @@ FROM logs-azure.signinlogs* Esql.azure_signinlogs_properties_app_display_name_lower_values = VALUES(Esql.azure_signinlogs_properties_app_display_name_lower), Esql.source_ip_values = VALUES(source.ip), Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), + Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), Esql.source_geo_country_name_values = VALUES(source.geo.country_name), Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), Esql.@timestamp.min = MIN(@timestamp), @@ -156,8 +156,8 @@ BY Esql.time_window_date_trunc Esql.azure_signinlogs_properties_app_display_name_lower_values, Esql.source_ip_values, Esql.source_ip_count_distinct, - Esql.source_`as`.organization.name.values, - Esql.source_`as`.organization.name.count_distinct, + Esql.source_as_organization_name_values, + Esql.source_as_organization_name_count_distinct, Esql.source_geo_country_name_values, Esql.source_geo_country_name_count_distinct, Esql.azure_signinlogs_properties_authentication_requirement_values, diff --git a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml index d386fbd0ace..cbd602ec4f9 100644 --- a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml +++ b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml @@ -158,8 +158,8 @@ FROM logs-azure.signinlogs* Esql.azure_signinlogs_properties_app_display_name_lower_values = VALUES(Esql.azure_signinlogs_properties_app_display_name_lower), Esql.source_ip_values = VALUES(source.ip), Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), + Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), Esql.source_geo_country_name_values = VALUES(source.geo.country_name), Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), Esql.@timestamp.min = MIN(@timestamp), @@ -215,8 +215,8 @@ BY Esql.time_window_date_trunc Esql.azure_signinlogs_properties_app_display_name_lower_values, Esql.source_ip_values, Esql.source_ip_count_distinct, - Esql.source_`as`.organization.name.values, - Esql.source_`as`.organization.name.count_distinct, + Esql.source_as_organization_name_values, + Esql.source_as_organization_name_count_distinct, Esql.source_geo_country_name_values, Esql.source_geo_country_name_count_distinct, Esql.azure_signinlogs_properties_authentication_requirement_values, diff --git a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml index a6ddd5e7adc..d5f5ad68e06 100644 --- a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml +++ b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml @@ -118,7 +118,7 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index Esql.source_geo_region_name_values = VALUES(source.geo.region_name), Esql.source_address_values = VALUES(source.address), Esql.source_address_count_distinct = COUNT_DISTINCT(source.address), - Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), Esql.azure_signinlogs_properties_authentication_protocol_values = VALUES(azure.signinlogs.properties.authentication_protocol), Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), @@ -161,7 +161,7 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index Esql.source_geo_region_name_values, Esql.source_address_values, Esql.source_address_count_distinct, - Esql.source_`as`.organization.name.values, + Esql.source_as_organization_name_values, Esql.azure_signinlogs_properties_authentication_protocol_values, Esql.azure_signinlogs_properties_authentication_requirement_values, Esql.azure_signinlogs_properties_is_interactive_values, diff --git a/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml b/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml index 9a0b54f4ae9..3b28f7881c8 100644 --- a/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml +++ b/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml @@ -59,12 +59,12 @@ type = "esql" query = ''' FROM logs-azure.auditlogs-* metadata _id, _version, _index | WHERE event.action == "Authentication Methods Policy Update" -| EVAL Esql.azure.auditlogs.properties.target_resources.modified_properties.new_value.replace = REPLACE(`azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`, "\\\\", "") -| EVAL Esql.azure.auditlogs.properties.target_resources.modified_properties.old_value.replace = REPLACE(`azure.auditlogs.properties.target_resources.0.modified_properties.0.old_value`, "\\\\", "") -| DISSECT Esql.azure.auditlogs.properties.target_resources.modified_properties.new_value.replace "%{}discoveryUrl\":\"%{Esql.azure.auditlogs.properties.auth.oidc.discovery.url.new}\"}%{}" -| DISSECT Esql.azure.auditlogs.properties.target_resources.modified_properties.old_value.replace "%{}discoveryUrl\":\"%{Esql.azure.auditlogs.properties.auth.oidc.discovery.url.old}\"}%{}" -| WHERE Esql.azure.auditlogs.properties.auth.oidc.discovery.url.new IS NOT NULL and Esql.azure.auditlogs.properties.auth.oidc.discovery.url.old IS NOT NULL -| WHERE Esql.azure.auditlogs.properties.auth.oidc.discovery.url.new != Esql.azure.auditlogs.properties.auth.oidc.discovery.url.old +| EVAL Esql.azure_auditlogs_properties_target_resources_modified_properties_new_value_replace = REPLACE(`azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`, "\\\\", "") +| EVAL Esql.azure_auditlogs_properties_target_resources_modified_properties_old_value_replace = REPLACE(`azure.auditlogs.properties.target_resources.0.modified_properties.0.old_value`, "\\\\", "") +| DISSECT Esql.azure_auditlogs_properties_target_resources_modified_properties_new_value_replace "%{}discoveryUrl\":\"%{Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new}\"}%{}" +| DISSECT Esql.azure_auditlogs_properties_target_resources_modified_properties_old_value_replace "%{}discoveryUrl\":\"%{Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old}\"}%{}" +| WHERE Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new IS NOT NULL and Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old IS NOT NULL +| WHERE Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new != Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old | KEEP @timestamp, event.action, @@ -79,8 +79,8 @@ FROM logs-azure.auditlogs-* metadata _id, _version, _index source.geo.city_name, source.geo.region_name, source.geo.country_name, - Esql.azure.auditlogs.properties.auth.oidc.discovery.url.new, - Esql.azure.auditlogs.properties.auth.oidc.discovery.url.old + Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new, + Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old ''' diff --git a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml index 1d933d2b919..ba111d73adf 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml @@ -91,8 +91,8 @@ FROM logs-o365.audit-* Esql_priv.o365_audit_UserId_values = VALUES(TO_LOWER(o365.audit.UserId)), Esql.source_ip_values = VALUES(source.ip), Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), - Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), + Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), Esql.source_geo_country_name_values = VALUES(source.geo.country_name), Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), Esql.o365_audit_ExtendedProperties_RequestType_values = VALUES(TO_LOWER(o365.audit.ExtendedProperties.RequestType)), @@ -108,8 +108,8 @@ FROM logs-o365.audit-* Esql_priv.o365_audit_UserId_values, Esql.source_ip_values, Esql.source_ip_count_distinct, - Esql.source_`as`.organization.name.values, - Esql.source_`as`.organization.name.count_distinct, + Esql.source_as_organization_name_values, + Esql.source_as_organization_name_count_distinct, Esql.source_geo_country_name_values, Esql.source_geo_country_name_count_distinct, Esql.o365_audit_ExtendedProperties_RequestType_values, diff --git a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml index bea916eea8c..065434ea3a7 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml @@ -113,10 +113,10 @@ FROM logs-o365.audit-* Esql.o365_audit_ExtendedProperties_RequestType_values = VALUES(Esql.o365_audit_ExtendedProperties_RequestType_lower), Esql.source_ip_values = VALUES(source.ip), Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), Esql.source_geo_country_name_values = VALUES(source.geo.country_name), Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.source_`as`.organization.name.count_distinct = COUNT_DISTINCT(source.`as`.organization.name), + Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), Esql.timestamp_first_seen = MIN(@timestamp), Esql.timestamp_last_seen = MAX(@timestamp), Esql.event_count = COUNT(*) @@ -138,10 +138,10 @@ FROM logs-o365.audit-* Esql.o365_audit_ExtendedProperties_RequestType_values, Esql.source_ip_values, Esql.source_ip_count_distinct, - Esql.source_`as`.organization.name.values, + Esql.source_as_organization_name_values, Esql.source_geo_country_name_values, Esql.source_geo_country_name_count_distinct, - Esql.source_`as`.organization.name.count_distinct, + Esql.source_as_organization_name_count_distinct, Esql.timestamp_first_seen, Esql.timestamp_last_seen, Esql.event_duration_seconds, diff --git a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml index 3b2e8f80b13..6e4d8e4b8f0 100644 --- a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml +++ b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml @@ -94,7 +94,7 @@ FROM logs-o365.audit-* Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), Esql.source_ip_values = VALUES(source.ip), Esql.o365_audit_ApplicationId_values = VALUES(o365.audit.ApplicationId), - Esql.source_`as`.organization.name.values = VALUES(source.`as`.organization.name), + Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), Esql.oauth_token_count_distinct = COUNT_DISTINCT(Esql.oauth_token_user_id_case), Esql.oauth_authorize_count_distinct = COUNT_DISTINCT(Esql.oauth_authorize_user_id_case) BY @@ -107,7 +107,7 @@ FROM logs-o365.audit-* Esql.source_ip_values, Esql.source_ip_count_distinct, Esql.o365_audit_ApplicationId_values, - Esql.source_`as`.organization.name.values, + Esql.source_as_organization_name_values, Esql.oauth_token_count_distinct, Esql.oauth_authorize_count_distinct | WHERE diff --git a/rules/linux/defense_evasion_base64_decoding_activity.toml b/rules/linux/defense_evasion_base64_decoding_activity.toml index 9bf4997d72b..94ff7b72a13 100644 --- a/rules/linux/defense_evasion_base64_decoding_activity.toml +++ b/rules/linux/defense_evasion_base64_decoding_activity.toml @@ -110,7 +110,7 @@ FROM logs-endpoint.events.process-* process.args IN ("-d", "-base64", "-a") ) OR ( - process.name RLIKE "^python.*" AND ( + process.name LIKE "python*" AND ( ( process.args == "base64" AND process.args IN ("-d", "-u", "-t") @@ -123,11 +123,11 @@ FROM logs-endpoint.events.process-* ) ) OR ( - process.name RLIKE "^perl.*" AND + process.name LIKE "perl*" AND process.command_line LIKE "*decode_base64*" ) OR ( - process.name RLIKE "^ruby.*" AND + process.name LIKE "ruby*" AND process.args == "-e" AND process.command_line LIKE "*Base64.decode64*" ) diff --git a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml index 513557ec27e..0e8141f6b8f 100644 --- a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml @@ -102,16 +102,16 @@ FROM logs-endpoint.events.network-* event.type == "start" AND event.action == "connection_attempted" | STATS - Esql.connection_count = COUNT(), + Esql.event_count = COUNT(), Esql.destination_ip_count_distinct = COUNT_DISTINCT(destination.ip), Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - host.name.values = VALUES(host.name), - agent.id.values = VALUES(agent.id) + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.executable | WHERE Esql.agent_id_count_distinct == 1 AND Esql.destination_ip_count_distinct > 250 -| SORT Esql.connection_count ASC +| SORT Esql.event_count ASC | LIMIT 100 ''' diff --git a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml index 012f88378da..8670f17af01 100644 --- a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml +++ b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml @@ -104,8 +104,8 @@ FROM logs-endpoint.events.process-* | STATS Esql.event_count = COUNT(), Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - host.name.values = VALUES(host.name), - agent.id.values = VALUES(agent.id) + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.executable, process.parent.executable, process.command_line | WHERE Esql.agent_id_count_distinct == 1 AND diff --git a/rules/linux/impact_potential_bruteforce_malware_infection.toml b/rules/linux/impact_potential_bruteforce_malware_infection.toml index b76482f2956..8dbd627f076 100644 --- a/rules/linux/impact_potential_bruteforce_malware_infection.toml +++ b/rules/linux/impact_potential_bruteforce_malware_infection.toml @@ -117,8 +117,8 @@ FROM logs-endpoint.events.network-* | STATS Esql.event_count = COUNT(), Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - host.name.values = VALUES(host.name), - agent.id.values = VALUES(agent.id) + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.executable, destination.port | WHERE Esql.agent_id_count_distinct == 1 AND diff --git a/rules/linux/persistence_web_server_sus_child_spawned.toml b/rules/linux/persistence_web_server_sus_child_spawned.toml index 9527dc485a0..60570584f44 100644 --- a/rules/linux/persistence_web_server_sus_child_spawned.toml +++ b/rules/linux/persistence_web_server_sus_child_spawned.toml @@ -139,8 +139,8 @@ FROM logs-endpoint.events.process-* | STATS Esql.event_count = COUNT(), Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - host.name.values = VALUES(host.name), - agent.id.values = VALUES(agent.id) + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.executable, process.working_directory, process.parent.executable | WHERE Esql.agent_id_count_distinct == 1 AND diff --git a/rules/linux/persistence_web_server_sus_command_execution.toml b/rules/linux/persistence_web_server_sus_command_execution.toml index d3d1259d0ee..2c9f4692689 100644 --- a/rules/linux/persistence_web_server_sus_command_execution.toml +++ b/rules/linux/persistence_web_server_sus_command_execution.toml @@ -149,8 +149,8 @@ FROM logs-endpoint.events.process-* | STATS Esql.event_count = COUNT(), Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - host.name.values = VALUES(host.name), - agent.id.values = VALUES(agent.id) + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.command_line, process.working_directory, process.parent.executable | WHERE Esql.agent_id_count_distinct == 1 AND diff --git a/rules/windows/credential_access_rare_webdav_destination.toml b/rules/windows/credential_access_rare_webdav_destination.toml index c5248abcfcb..5966773bc8c 100644 --- a/rules/windows/credential_access_rare_webdav_destination.toml +++ b/rules/windows/credential_access_rare_webdav_destination.toml @@ -74,8 +74,8 @@ FROM logs-* | STATS Esql.event_count = COUNT(*), Esql.host_id_count_distinct = COUNT_DISTINCT(host.id), - host.id.values = VALUES(host.id), - user.name.values = VALUES(user.name) + Esql.host_id_values = VALUES(host.id), + Esql.user_name_values = VALUES(user.name) BY Esql.server_webdav_cookie_replace | WHERE Esql.host_id_count_distinct == 1 AND diff --git a/rules_building_block/persistence_web_server_sus_file_creation.toml b/rules_building_block/persistence_web_server_sus_file_creation.toml index 8fd34b40832..8e683304f23 100644 --- a/rules_building_block/persistence_web_server_sus_file_creation.toml +++ b/rules_building_block/persistence_web_server_sus_file_creation.toml @@ -97,8 +97,8 @@ FROM logs-endpoint.events.file-* | STATS Esql.event_count = COUNT(), Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - host.name.values = VALUES(host.name), - agent.id.values = VALUES(agent.id) + Esql.host_name_values = VALUES(host.name), + Esql.agent_id_values = VALUES(agent.id) BY process.executable, file.path | WHERE Esql.agent_id_count_distinct == 1 AND From 8522cf440111bd8b065b61d461505b548587eff2 Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Wed, 30 Jul 2025 21:56:13 -0400 Subject: [PATCH 86/94] lowercase all functions and logical operators --- ...otential_widespread_malware_infection.toml | 1 - ..._access_azure_o365_with_network_alert.toml | 39 +++-- ...y_ec2_multi_region_describe_instances.toml | 17 +-- ..._multiple_discovery_api_calls_via_cli.toml | 17 +-- ...s_multi_region_service_quota_requests.toml | 76 +++++----- ..._snapshot_shared_with_another_account.toml | 8 +- ..._s3_bucket_enumeration_or_brute_force.toml | 13 +- ...mpact_ec2_ebs_snapshot_access_removed.toml | 30 ++-- ...object_uploaded_with_ransom_extension.toml | 8 +- ...3_object_encryption_with_external_key.toml | 6 +- ...mpact_s3_static_site_js_file_uploaded.toml | 17 +-- ...on_token_used_from_multiple_addresses.toml | 95 ++++++------ ...al_access_signin_console_login_no_mfa.toml | 4 +- ...nce_iam_create_login_profile_for_root.toml | 5 +- ..._created_access_keys_for_another_user.toml | 1 - ...tratoraccess_policy_attached_to_group.toml | 5 +- ...stratoraccess_policy_attached_to_role.toml | 5 +- ...stratoraccess_policy_attached_to_user.toml | 5 +- ...rivilege_escalation_sts_role_chaining.toml | 16 +- ..._bedrock_execution_without_guardrails.toml | 15 +- ...ls_multiple_violations_by_single_user.toml | 11 +- ...multiple_violations_in_single_request.toml | 13 +- ...confidence_misconduct_blocks_detected.toml | 23 ++- ...k_high_resource_consumption_detection.toml | 13 +- ...attempts_to_use_denied_models_by_user.toml | 11 +- ...ve_information_policy_blocks_detected.toml | 13 +- ...multiple_topic_policy_blocks_detected.toml | 13 +- ...ation_exception_errors_by_single_user.toml | 12 +- ..._multiple_word_policy_blocks_detected.toml | 13 +- ..._access_azure_entra_suspicious_signin.toml | 41 ++--- ...azure_entra_totp_brute_force_attempts.toml | 49 +++--- ...s_azure_key_vault_excessive_retrieval.toml | 71 +++++---- ..._access_entra_id_brute_force_activity.toml | 119 ++++++++------- ...s_entra_id_excessive_account_lockouts.toml | 117 +++++++------- ...ntra_signin_brute_force_microsoft_365.toml | 143 +++++++++--------- ...ingle_session_from_multiple_addresses.toml | 81 +++++----- ...ous_oauth_flow_via_auth_broker_to_drs.toml | 115 +++++++------- ...ce_entra_id_oidc_discovery_url_change.toml | 18 +-- ...openai_denial_of_ml_service_detection.toml | 25 ++- ...ai_insecure_output_handling_detection.toml | 23 ++- .../azure_openai_model_theft_detection.toml | 25 ++- ...ion_onedrive_excessive_file_downloads.toml | 31 ++-- ...rosoft_365_excessive_account_lockouts.toml | 69 +++++---- ...65_potential_user_account_brute_force.toml | 83 +++++----- ...crosoft_365_susp_oauth2_authorization.toml | 55 ++++--- ..._token_hashes_for_single_okta_session.toml | 29 ++-- ...for_multiple_users_from_single_source.toml | 23 ++- ...users_with_the_same_device_token_hash.toml | 25 ++- ...e_device_token_hashes_for_single_user.toml | 25 ++- ...s_started_from_different_geolocations.toml | 30 ++-- ...ent_egress_netcon_from_sus_executable.toml | 57 ++++--- ...ense_evasion_base64_decoding_activity.toml | 77 +++++----- ...anning_activity_from_compromised_host.toml | 35 +++-- ...anning_activity_from_compromised_host.toml | 35 +++-- ...nusual_file_transfer_utility_launched.toml | 37 +++-- ...otential_bruteforce_malware_infection.toml | 39 +++-- ...sistence_web_server_sus_child_spawned.toml | 63 ++++---- ...ence_web_server_sus_command_execution.toml | 69 +++++---- ...ential_access_rare_webdav_destination.toml | 48 +++--- ...nse_evasion_posh_obfuscation_backtick.toml | 29 ++-- ...evasion_posh_obfuscation_backtick_var.toml | 22 +-- ..._evasion_posh_obfuscation_char_arrays.toml | 20 +-- ...asion_posh_obfuscation_concat_dynamic.toml | 18 +-- ...sh_obfuscation_high_number_proportion.toml | 24 +-- ...fuscation_iex_env_vars_reconstruction.toml | 22 +-- ...obfuscation_iex_string_reconstruction.toml | 24 +-- ...asion_posh_obfuscation_index_reversal.toml | 25 ++- ...sion_posh_obfuscation_reverse_keyword.toml | 20 +-- ...vasion_posh_obfuscation_string_concat.toml | 22 +-- ...vasion_posh_obfuscation_string_format.toml | 38 ++--- ...scation_whitespace_special_proportion.toml | 26 ++-- .../execution_posh_malicious_script_agg.toml | 36 +++-- ..._obfuscation_proportion_special_chars.toml | 26 ++-- ...sistence_web_server_sus_file_creation.toml | 54 +++---- 74 files changed, 1257 insertions(+), 1311 deletions(-) diff --git a/rules/cross-platform/execution_potential_widespread_malware_infection.toml b/rules/cross-platform/execution_potential_widespread_malware_infection.toml index 10508a2c703..3f52c996a02 100644 --- a/rules/cross-platform/execution_potential_widespread_malware_infection.toml +++ b/rules/cross-platform/execution_potential_widespread_malware_infection.toml @@ -69,7 +69,6 @@ from logs-endpoint.alerts-* | keep host.id, rule.name, event.code | stats Esql.host_id_count_distinct = count_distinct(host.id) by rule.name, event.code | where Esql.host_id_count_distinct >= 3 - ''' diff --git a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml index c3a40ca65af..5632c7110c3 100644 --- a/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml +++ b/rules/cross-platform/initial_access_azure_o365_with_network_alert.toml @@ -77,14 +77,14 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-*, .alerts-security.* +from logs-*, .alerts-security.* // query runs every 1 hour looking for activities occurred during last 8 hours to match on disparate events -| where @timestamp > NOW() - 8 hours -// filter for Azure or M365 sign-in and External Alerts with source.ip not null -| where TO_IP(source.ip) is not null +| where @timestamp > now() - 8 hours +// filter for azure or m365 sign-in and external alerts with source.ip not null +| where to_ip(source.ip) is not null and (event.dataset in ("o365.audit", "azure.signinlogs") or kibana.alert.rule.name == "External Alerts") - and not CIDR_MATCH( - TO_IP(source.ip), + and not cidr_match( + to_ip(source.ip), "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32", "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", "224.0.0.0/4", @@ -95,23 +95,23 @@ FROM logs-*, .alerts-security.* // capture relevant raw fields | keep source.ip, event.action, event.outcome, event.dataset, kibana.alert.rule.name, event.category -// classify each source IP based on alert type +// classify each source ip based on alert type | eval - Esql.source_ip_mail_access_case = case(event.dataset == "o365.audit" and event.action == "MailItemsAccessed" and event.outcome == "success", TO_IP(source.ip), null), - Esql.source_ip_azure_signin_case = case(event.dataset == "azure.signinlogs" and event.outcome == "success", TO_IP(source.ip), null), - Esql.source_ip_network_alert_case = case(kibana.alert.rule.name == "External Alerts" and not event.dataset in ("o365.audit", "azure.signinlogs"), TO_IP(source.ip), null) + Esql.source_ip_mail_access_case = case(event.dataset == "o365.audit" and event.action == "MailItemsAccessed" and event.outcome == "success", to_ip(source.ip), null), + Esql.source_ip_azure_signin_case = case(event.dataset == "azure.signinlogs" and event.outcome == "success", to_ip(source.ip), null), + Esql.source_ip_network_alert_case = case(kibana.alert.rule.name == "external alerts" and not event.dataset in ("o365.audit", "azure.signinlogs"), to_ip(source.ip), null) -// aggregate by source IP +// aggregate by source ip | stats Esql.event_count = count(*), - Esql.source_ip_mail_access_case_count_distinct = COUNT_DISTINCT(Esql.source_ip_mail_access_case), - Esql.source_ip_azure_signin_case_count_distinct = COUNT_DISTINCT(Esql.source_ip_azure_signin_case), - Esql.source_ip_network_alert_case_count_distinct = COUNT_DISTINCT(Esql.source_ip_network_alert_case), - Esql.event_dataset_count_distinct = COUNT_DISTINCT(event.dataset), - Esql.event_dataset_values = VALUES(event.dataset), - Esql.kibana_alert_rule_name_values = VALUES(kibana.alert.rule.name), - Esql.event_category_values = VALUES(event.category) - by Esql.source_ip = TO_IP(source.ip) + Esql.source_ip_mail_access_case_count_distinct = count_distinct(Esql.source_ip_mail_access_case), + Esql.source_ip_azure_signin_case_count_distinct = count_distinct(Esql.source_ip_azure_signin_case), + Esql.source_ip_network_alert_case_count_distinct = count_distinct(Esql.source_ip_network_alert_case), + Esql.event_dataset_count_distinct = count_distinct(event.dataset), + Esql.event_dataset_values = values(event.dataset), + Esql.kibana_alert_rule_name_values = values(kibana.alert.rule.name), + Esql.event_category_values = values(event.category) + by Esql.source_ip = to_ip(source.ip) // correlation condition | where @@ -119,7 +119,6 @@ FROM logs-*, .alerts-security.* and Esql.event_dataset_count_distinct >= 2 and (Esql.source_ip_mail_access_case_count_distinct > 0 or Esql.source_ip_azure_signin_case_count_distinct > 0) and Esql.event_count <= 100 - ''' diff --git a/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml b/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml index ca0d0c84328..cdf6c4d7929 100644 --- a/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml +++ b/rules/integrations/aws/discovery_ec2_multi_region_describe_instances.toml @@ -86,7 +86,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* +from logs-aws.cloudtrail-* // filter for DescribeInstances API calls | where event.dataset == "aws.cloudtrail" @@ -94,15 +94,15 @@ FROM logs-aws.cloudtrail-* and event.action == "DescribeInstances" // truncate the timestamp to a 30-second window -| eval Esql.time_window_date_trunc = DATE_TRUNC(30 seconds, @timestamp) +| eval Esql.time_window_date_trunc = date_trunc(30 seconds, @timestamp) // keep only the relevant raw fields | keep Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn, cloud.region // count the number of unique regions and total API calls within the 30-second window | stats - Esql.cloud_region_count_distinct = COUNT_DISTINCT(cloud.region), - Esql.event_count = COUNT(*) + Esql.cloud_region_count_distinct = count_distinct(cloud.region), + Esql.event_count = count(*) by Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn // filter for resources making DescribeInstances API calls in more than 10 regions within the 30-second window @@ -110,15 +110,14 @@ FROM logs-aws.cloudtrail-* // sort the results by time window in descending order | sort Esql.time_window_date_trunc desc - ''' [rule.investigation_fields] field_names = [ - "aws.cloudtrail.user_identity.arn", - "target_time_window", - "region_count", - "window_count" + "aws.cloudtrail.user_identity.arn", + "target_time_window", + "region_count", + "window_count" ] [[rule.threat]] diff --git a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml index 38454a75797..8aeac9ba3d7 100644 --- a/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml +++ b/rules/integrations/aws/discovery_ec2_multiple_discovery_api_calls_via_cli.toml @@ -80,10 +80,10 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail* +from logs-aws.cloudtrail* // create time window buckets of 10 seconds -| eval Esql.time_window_date_trunc = DATE_TRUNC(10 seconds, @timestamp) +| eval Esql.time_window_date_trunc = date_trunc(10 seconds, @timestamp) | where event.dataset == "aws.cloudtrail" @@ -111,22 +111,22 @@ FROM logs-aws.cloudtrail* // filter for Describe, Get, List, and Generate API calls | where true in ( - STARTS_WITH(event.action, "Describe"), - STARTS_WITH(event.action, "Get"), - STARTS_WITH(event.action, "List"), - STARTS_WITH(event.action, "Generate") + starts_with(event.action, "Describe"), + starts_with(event.action, "Get"), + starts_with(event.action, "List"), + starts_with(event.action, "Generate") ) // extract owner, identity type, and actor from the ARN | dissect aws.cloudtrail.user_identity.arn "%{}::%{Esql_priv.aws_cloudtrail_user_identity_arn_owner}:%{Esql.aws_cloudtrail_user_identity_arn_type}/%{Esql.aws_cloudtrail_user_identity_arn_roles}" -| where STARTS_WITH(Esql.aws_cloudtrail_user_identity_arn_roles, "AWSServiceRoleForConfig") != true +| where starts_with(Esql.aws_cloudtrail_user_identity_arn_roles, "AWSServiceRoleForConfig") != true // keep relevant fields (preserving ECS fields and computed time window) | keep @timestamp, Esql.time_window_date_trunc, event.action, aws.cloudtrail.user_identity.arn // count the number of unique API calls per time window and actor | stats - Esql.event_action_count_distinct = COUNT_DISTINCT(event.action) + Esql.event_action_count_distinct = count_distinct(event.action) by Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn // filter for more than 5 unique API calls per 10s window @@ -134,7 +134,6 @@ FROM logs-aws.cloudtrail* // sort the results by the number of unique API calls in descending order | sort Esql.event_action_count_distinct desc - ''' diff --git a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml index 66b31cd3bbe..afb9e3f6d35 100644 --- a/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml +++ b/rules/integrations/aws/discovery_servicequotas_multi_region_service_quota_requests.toml @@ -15,6 +15,40 @@ from = "now-9m" language = "esql" license = "Elastic License v2" name = "AWS Service Quotas Multi-Region `GetServiceQuota` Requests" +note = """## Triage and analysis + +> **Disclaimer**: +> This investigation guide was created using generative AI technology and has been reviewed to improve its accuracy and relevance. While every effort has been made to ensure its quality, we recommend validating the content and adapting it to suit your specific environment and operational needs. + +### Investigating AWS Service Quotas Multi-Region `GetServiceQuota` Requests + +AWS Service Quotas manage resource limits across AWS services, crucial for maintaining operational boundaries. Adversaries may exploit `GetServiceQuota` API calls to probe AWS infrastructure, seeking vulnerabilities for deploying threats like cryptocurrency miners. The detection rule identifies unusual multi-region queries for EC2 quotas, signaling potential credential compromise or unauthorized access attempts. + +### Possible investigation steps + +- Review the AWS CloudTrail logs to identify the specific user or role associated with the `aws.cloudtrail.user_identity.arn` field that triggered the alert. Determine if this user or role should have access to multiple regions. +- Examine the `cloud.region` field to identify which regions were accessed and verify if these regions are typically used by your organization. Investigate any unfamiliar regions for unauthorized activity. +- Check the AWS IAM policies and permissions associated with the identified user or role to ensure they align with the principle of least privilege. Look for any recent changes or anomalies in permissions. +- Investigate the source IP addresses and locations from which the `GetServiceQuota` API calls were made to determine if they match expected patterns for your organization. Look for any unusual or suspicious IP addresses. +- Review recent activity logs for the identified user or role to detect any other unusual or unauthorized actions, such as attempts to launch EC2 instances or access other AWS services. +- If a compromise is suspected, consider rotating the credentials for the affected user or role and implementing additional security measures, such as multi-factor authentication (MFA) and enhanced monitoring. + +### False positive analysis + +- Legitimate multi-region operations: Organizations with a global presence may have legitimate reasons for querying EC2 service quotas across multiple regions. To handle this, users can create exceptions for known accounts or roles that regularly perform such operations. +- Automated infrastructure management tools: Some tools or scripts designed for infrastructure management might perform multi-region `GetServiceQuota` requests as part of their normal operation. Users should identify these tools and exclude their activity from triggering alerts by whitelisting their associated user identities or ARNs. +- Testing and development activities: Developers or testers might intentionally perform multi-region queries during testing phases. Users can mitigate false positives by setting up temporary exceptions for specific time frames or user identities involved in testing. +- Cloud service providers or partners: Third-party services or partners managing AWS resources on behalf of an organization might generate similar patterns. Users should establish trust relationships and exclude these entities from detection by verifying their activities and adding them to an exception list. + +### Response and remediation + +- Immediately isolate the AWS account or IAM user identified in the alert to prevent further unauthorized access. This can be done by disabling the access keys or suspending the account temporarily. +- Conduct a thorough review of the AWS CloudTrail logs for the identified user or resource to determine the extent of the unauthorized activity and identify any other potentially compromised resources. +- Rotate all access keys and passwords associated with the compromised account or IAM user to prevent further unauthorized access. +- Implement additional security measures such as enabling multi-factor authentication (MFA) for all IAM users and roles to enhance account security. +- Notify the security operations team and relevant stakeholders about the potential compromise and the steps being taken to remediate the issue. +- If evidence of compromise is confirmed, consider engaging AWS Support or a third-party incident response team for further investigation and assistance. +- Review and update IAM policies and permissions to ensure the principle of least privilege is enforced, reducing the risk of future unauthorized access attempts.""" references = [ "https://www.sentinelone.com/labs/exploring-fbot-python-based-malware-targeting-cloud-and-payment-services/", "https://docs.aws.amazon.com/servicequotas/2019-06-24/apireference/API_GetServiceQuota.html", @@ -35,7 +69,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* +from logs-aws.cloudtrail-* // filter for GetServiceQuota API calls | where @@ -44,7 +78,7 @@ FROM logs-aws.cloudtrail-* and event.action == "GetServiceQuota" // truncate the timestamp to a 30-second window -| eval Esql.time_window_date_trunc = DATE_TRUNC(30 seconds, @timestamp) +| eval Esql.time_window_date_trunc = date_trunc(30 seconds, @timestamp) // dissect request parameters to extract service and quota code | dissect aws.cloudtrail.request_parameters "{%{?Esql.aws_cloudtrail_request_parameters_service_code_key}=%{Esql.aws_cloudtrail_request_parameters_service_code}, %{?quota_code_key}=%{Esql.aws_cloudtrail_request_parameters_quota_code}}" @@ -62,8 +96,8 @@ FROM logs-aws.cloudtrail-* // count the number of unique regions and total API calls within the time window | stats - Esql.cloud_region_count_distinct = COUNT_DISTINCT(cloud.region), - Esql.event_count = COUNT(*) + Esql.cloud_region_count_distinct = count_distinct(cloud.region), + Esql.event_count = count(*) by Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn // filter for API calls in more than 10 regions within the 30-second window @@ -73,42 +107,8 @@ FROM logs-aws.cloudtrail-* // sort by time window descending | sort Esql.time_window_date_trunc desc - ''' -note = """## Triage and analysis - -> **Disclaimer**: -> This investigation guide was created using generative AI technology and has been reviewed to improve its accuracy and relevance. While every effort has been made to ensure its quality, we recommend validating the content and adapting it to suit your specific environment and operational needs. - -### Investigating AWS Service Quotas Multi-Region `GetServiceQuota` Requests - -AWS Service Quotas manage resource limits across AWS services, crucial for maintaining operational boundaries. Adversaries may exploit `GetServiceQuota` API calls to probe AWS infrastructure, seeking vulnerabilities for deploying threats like cryptocurrency miners. The detection rule identifies unusual multi-region queries for EC2 quotas, signaling potential credential compromise or unauthorized access attempts. - -### Possible investigation steps - -- Review the AWS CloudTrail logs to identify the specific user or role associated with the `aws.cloudtrail.user_identity.arn` field that triggered the alert. Determine if this user or role should have access to multiple regions. -- Examine the `cloud.region` field to identify which regions were accessed and verify if these regions are typically used by your organization. Investigate any unfamiliar regions for unauthorized activity. -- Check the AWS IAM policies and permissions associated with the identified user or role to ensure they align with the principle of least privilege. Look for any recent changes or anomalies in permissions. -- Investigate the source IP addresses and locations from which the `GetServiceQuota` API calls were made to determine if they match expected patterns for your organization. Look for any unusual or suspicious IP addresses. -- Review recent activity logs for the identified user or role to detect any other unusual or unauthorized actions, such as attempts to launch EC2 instances or access other AWS services. -- If a compromise is suspected, consider rotating the credentials for the affected user or role and implementing additional security measures, such as multi-factor authentication (MFA) and enhanced monitoring. - -### False positive analysis -- Legitimate multi-region operations: Organizations with a global presence may have legitimate reasons for querying EC2 service quotas across multiple regions. To handle this, users can create exceptions for known accounts or roles that regularly perform such operations. -- Automated infrastructure management tools: Some tools or scripts designed for infrastructure management might perform multi-region `GetServiceQuota` requests as part of their normal operation. Users should identify these tools and exclude their activity from triggering alerts by whitelisting their associated user identities or ARNs. -- Testing and development activities: Developers or testers might intentionally perform multi-region queries during testing phases. Users can mitigate false positives by setting up temporary exceptions for specific time frames or user identities involved in testing. -- Cloud service providers or partners: Third-party services or partners managing AWS resources on behalf of an organization might generate similar patterns. Users should establish trust relationships and exclude these entities from detection by verifying their activities and adding them to an exception list. - -### Response and remediation - -- Immediately isolate the AWS account or IAM user identified in the alert to prevent further unauthorized access. This can be done by disabling the access keys or suspending the account temporarily. -- Conduct a thorough review of the AWS CloudTrail logs for the identified user or resource to determine the extent of the unauthorized activity and identify any other potentially compromised resources. -- Rotate all access keys and passwords associated with the compromised account or IAM user to prevent further unauthorized access. -- Implement additional security measures such as enabling multi-factor authentication (MFA) for all IAM users and roles to enhance account security. -- Notify the security operations team and relevant stakeholders about the potential compromise and the steps being taken to remediate the issue. -- If evidence of compromise is confirmed, consider engaging AWS Support or a third-party incident response team for further investigation and assistance. -- Review and update IAM policies and permissions to ensure the principle of least privilege is enforced, reducing the risk of future unauthorized access attempts.""" [[rule.threat]] framework = "MITRE ATT&CK" diff --git a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml index 8d1b8b4cbf0..5a55508c3b8 100644 --- a/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml +++ b/rules/integrations/aws/exfiltration_ec2_ebs_snapshot_shared_with_another_account.toml @@ -21,8 +21,7 @@ interval = "5m" language = "esql" license = "Elastic License v2" name = "AWS EC2 EBS Snapshot Shared or Made Public" -note = """ -## Triage and analysis +note = """## Triage and analysis ### Investigating AWS EC2 EBS Snapshot Shared or Made Public @@ -80,7 +79,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* METADATA _id, _version, _index +from logs-aws.cloudtrail-* metadata _id, _version, _index | where event.provider == "ec2.amazonaws.com" and event.action == "ModifySnapshotAttribute" @@ -95,7 +94,7 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index Esql.aws_cloudtrail_request_parameters_operation_type == "add" and cloud.account.id != Esql_priv.aws_cloudtrail_request_parameters_user_id -// Keep ECS and derived fields +// keep ECS and derived fields | keep @timestamp, aws.cloudtrail.user_identity.arn, @@ -106,7 +105,6 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index Esql.aws_cloudtrail_request_parameters_operation_type, Esql_priv.aws_cloudtrail_request_parameters_user_id, source.ip - ''' diff --git a/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml b/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml index f8ec5dfda38..f437ea406fd 100644 --- a/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml +++ b/rules/integrations/aws/impact_aws_s3_bucket_enumeration_or_brute_force.toml @@ -85,23 +85,23 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail* +from logs-aws.cloudtrail* | where event.provider == "s3.amazonaws.com" and aws.cloudtrail.error_code == "AccessDenied" - and tls.client.server_name IS NOT NULL - and cloud.account.id IS NOT NULL + and tls.client.server_name is not null + and cloud.account.id is not null -// Keep only relevant ECS fields +// keep only relevant ECS fields | keep tls.client.server_name, source.address, cloud.account.id -// Count access denied requests per server_name, source, and account +// count access denied requests per server_name, source, and account | stats - Esql.event_count = COUNT(*) + Esql.event_count = count(*) by tls.client.server_name, source.address, @@ -109,7 +109,6 @@ FROM logs-aws.cloudtrail* // Threshold: more than 40 denied requests | where Esql.event_count > 40 - ''' diff --git a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml index 41a9e1f5535..48fad7798e8 100644 --- a/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml +++ b/rules/integrations/aws/impact_ec2_ebs_snapshot_access_removed.toml @@ -7,20 +7,21 @@ updated_date = "2025/07/16" [rule] author = ["Elastic"] description = """ -Identifies the removal of access permissions from a shared AWS EC2 EBS snapshot. EBS snapshots are essential for data retention and disaster recovery. Adversaries may revoke or modify snapshot permissions to prevent legitimate users from accessing backups, thereby obstructing recovery efforts after data loss or destructive actions. This tactic can also be used to evade detection or maintain exclusive access to critical backups, ultimately increasing the impact of an attack and complicating incident response. +Identifies the removal of access permissions from a shared AWS EC2 EBS snapshot. EBS snapshots are essential for data +retention and disaster recovery. Adversaries may revoke or modify snapshot permissions to prevent legitimate users from +accessing backups, thereby obstructing recovery efforts after data loss or destructive actions. This tactic can also be +used to evade detection or maintain exclusive access to critical backups, ultimately increasing the impact of an attack +and complicating incident response. """ false_positives = [ - """ - Access removal may be a part of normal operations and should be verified before taking action. - """, + " Access removal may be a part of normal operations and should be verified before taking action.\n ", ] from = "now-6m" interval = "5m" language = "esql" license = "Elastic License v2" name = "AWS EC2 EBS Snapshot Access Removed" -note = """ -## Triage and analysis +note = """## Triage and analysis ### Investigating AWS EC2 EBS Snapshot Access Removed @@ -75,7 +76,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* METADATA _id, _version, _index +from logs-aws.cloudtrail-* metadata _id, _version, _index // Filter for successful snapshot modifications | where @@ -83,14 +84,14 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index and event.action == "ModifySnapshotAttribute" and event.outcome == "success" -// Dissect parameters to extract key fields +// dissect parameters to extract key fields | dissect aws.cloudtrail.request_parameters "{%{?snapshotId}=%{Esql.aws_cloudtrail_request_parameters_snapshot_id},%{?attributeType}=%{Esql.aws_cloudtrail_request_parameters_attribute_type},%{?createVolumePermission}={%{Esql.aws_cloudtrail_request_parameters_operation_type}={%{?items}=[{%{?userId}=%{Esql_priv.aws_cloudtrail_request_parameters_user_id}}]}}}" // Match on snapshot permission **removal** | where Esql.aws_cloudtrail_request_parameters_operation_type == "remove" -// Keep ECS and derived fields +// keep ECS and derived fields | keep @timestamp, aws.cloudtrail.user_identity.arn, @@ -101,22 +102,21 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index Esql.aws_cloudtrail_request_parameters_operation_type, Esql_priv.aws_cloudtrail_request_parameters_user_id, source.address - ''' [[rule.threat]] framework = "MITRE ATT&CK" -[[rule.threat.technique]] -id = "T1490" -name = "Inhibit System Recovery" -reference = "https://attack.mitre.org/techniques/T1490/" - [[rule.threat.technique]] id = "T1485" name = "Data Destruction" reference = "https://attack.mitre.org/techniques/T1485/" +[[rule.threat.technique]] +id = "T1490" +name = "Inhibit System Recovery" +reference = "https://attack.mitre.org/techniques/T1490/" + [rule.threat.tactic] id = "TA0040" diff --git a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml index f54c893e25c..c93d4ab67e8 100644 --- a/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml +++ b/rules/integrations/aws/impact_s3_bucket_object_uploaded_with_ransom_extension.toml @@ -21,8 +21,7 @@ from = "now-9m" language = "esql" license = "Elastic License v2" name = "Potential AWS S3 Bucket Ransomware Note Uploaded" -note = """ -## Triage and analysis +note = """## Triage and analysis ### Investigating Potential AWS S3 Bucket Ransomware Note Uploaded @@ -81,7 +80,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* +from logs-aws.cloudtrail-* // any successful uploads via S3 API requests | where @@ -106,7 +105,7 @@ FROM logs-aws.cloudtrail-* // aggregate by server name, actor, and object key | stats - Esql.event_count = COUNT(*) + Esql.event_count = count(*) by tls.client.server_name, aws.cloudtrail.user_identity.arn, @@ -114,7 +113,6 @@ FROM logs-aws.cloudtrail-* // filter for rare single uploads (likely test/detonation) | where Esql.event_count == 1 - ''' diff --git a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml index 020169b26f3..b1eaf50275b 100644 --- a/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml +++ b/rules/integrations/aws/impact_s3_object_encryption_with_external_key.toml @@ -22,8 +22,7 @@ from = "now-9m" language = "esql" license = "Elastic License v2" name = "AWS S3 Object Encryption Using External KMS Key" -note = """ -## Triage and analysis +note = """## Triage and analysis ### Investigating AWS S3 Object Encryption Using External KMS Key @@ -83,7 +82,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* METADATA _id, _version, _index +from logs-aws.cloudtrail-* metadata _id, _version, _index // any successful S3 copy event | where @@ -109,7 +108,6 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index Esql.aws_cloudtrail_request_parameters_kms_key_account_id, Esql.aws_cloudtrail_request_parameters_kms_key_id, Esql.aws_cloudtrail_request_parameters_target_object_key - ''' diff --git a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml index c67fa905e34..86e4ddd9c1d 100644 --- a/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml +++ b/rules/integrations/aws/impact_s3_static_site_js_file_uploaded.toml @@ -8,8 +8,8 @@ updated_date = "2025/07/16" author = ["Elastic"] description = """ This rule detects when a JavaScript file is uploaded or accessed in an S3 static site directory (`static/js/`) by an IAM -user or assumed role. This can indicate suspicious modification of web content hosted on S3, such as injecting malicious scripts into a -static website frontend. +user or assumed role. This can indicate suspicious modification of web content hosted on S3, such as injecting malicious +scripts into a static website frontend. """ false_positives = [ """ @@ -70,7 +70,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail* METADATA _id, _version, _index +from logs-aws.cloudtrail* metadata _id, _version, _index | where // S3 object read/write activity @@ -82,13 +82,13 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index and aws.cloudtrail.user_identity.type in ("IAMUser", "AssumedRole") // Requests for static site bundles - and aws.cloudtrail.request_parameters LIKE "*static/js/*.js*" + and aws.cloudtrail.request_parameters like "*static/js/*.js*" // Exclude IaC and automation tools and not ( - user_agent.original LIKE "*Terraform*" - or user_agent.original LIKE "*Ansible*" - or user_agent.original LIKE "*Pulumni*" + user_agent.original like "*Terraform*" + or user_agent.original like "*Ansible*" + or user_agent.original like "*Pulumni*" ) // Extract fields from request parameters @@ -99,7 +99,7 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index | dissect Esql.aws_cloudtrail_request_parameters_object_location "%{}static/js/%{Esql.aws_cloudtrail_request_parameters_object_key}" // Match on JavaScript files -| where ENDS_WITH(Esql.aws_cloudtrail_request_parameters_object_key, ".js") +| where ends_with(Esql.aws_cloudtrail_request_parameters_object_key, ".js") // Retain relevant ECS and dissected fields | keep @@ -113,7 +113,6 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index source.ip, event.action, @timestamp - ''' diff --git a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml index 1421b791706..995fd06a2e5 100644 --- a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml +++ b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml @@ -80,67 +80,67 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail* METADATA _id, _version, _index -| WHERE @timestamp > NOW() - 30 minutes - AND event.dataset == "aws.cloudtrail" - AND aws.cloudtrail.user_identity.arn IS NOT NULL - AND aws.cloudtrail.user_identity.type == "IAMUser" - AND source.ip IS NOT NULL - AND NOT ( - user_agent.original LIKE "%Terraform%" OR - user_agent.original LIKE "%Ansible%" OR - user_agent.original LIKE "%Pulumni%" +from logs-aws.cloudtrail* metadata _id, _version, _index +| where @timestamp > now() - 30 minutes + and event.dataset == "aws.cloudtrail" + and aws.cloudtrail.user_identity.arn is not null + and aws.cloudtrail.user_identity.type == "IAMUser" + and source.ip is not null + and not ( + user_agent.original like "%Terraform%" or + user_agent.original like "%Ansible%" or + user_agent.original like "%Pulumni%" ) - AND `source.as.organization.name` != "AMAZON-AES" - AND event.provider NOT IN ( + and `source.as.organization.name` != "AMAZON-AES" + and event.provider not in ( "health.amazonaws.com", "monitoring.amazonaws.com", "notifications.amazonaws.com", "ce.amazonaws.com", "cost-optimization-hub.amazonaws.com", "servicecatalog-appregistry.amazonaws.com", "securityhub.amazonaws.com" ) -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(30 minutes, @timestamp), +| eval + Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp), Esql.aws_cloudtrail_user_identity_arn = aws.cloudtrail.user_identity.arn, Esql.aws_cloudtrail_user_identity_access_key_id = aws.cloudtrail.user_identity.access_key_id, Esql.source_ip = source.ip, Esql.user_agent_original = user_agent.original, - Esql.source_ip_string = TO_STRING(source.ip), - Esql.source_ip_user_agent_pair = CONCAT(Esql.source_ip_string, " - ", user_agent.original), - Esql.source_ip_city_pair = CONCAT(Esql.source_ip_string, " - ", source.geo.city_name), + Esql.source_ip_string = to_string(source.ip), + Esql.source_ip_user_agent_pair = concat(Esql.source_ip_string, " - ", user_agent.original), + Esql.source_ip_city_pair = concat(Esql.source_ip_string, " - ", source.geo.city_name), Esql.source_geo_city_name = source.geo.city_name, Esql.event_timestamp = @timestamp, Esql.source_network_org_name = `source.as.organization.name` -| STATS - Esql.event_action_values = VALUES(event.action), - Esql.event_provider_values = VALUES(event.provider), - Esql.aws_cloudtrail_user_identity_access_key_id_values = VALUES(Esql.aws_cloudtrail_user_identity_access_key_id), - Esql.aws_cloudtrail_user_identity_arn_values = VALUES(Esql.aws_cloudtrail_user_identity_arn), - Esql.source_ip_values = VALUES(Esql.source_ip), - Esql.user_agent_original_values = VALUES(Esql.user_agent_original), - Esql.source_ip_user_agent_pair_values = VALUES(Esql.source_ip_user_agent_pair), - Esql.source_geo_city_name_values = VALUES(Esql.source_geo_city_name), - Esql.source_ip_city_pair_values = VALUES(Esql.source_ip_city_pair), - Esql.source_network_org_name_values = VALUES(Esql.source_network_org_name), - Esql.source_ip_count_distinct = COUNT_DISTINCT(Esql.source_ip), - Esql.user_agent_original_count_distinct = COUNT_DISTINCT(Esql.user_agent_original), - Esql.source_geo_city_name_count_distinct = COUNT_DISTINCT(Esql.source_geo_city_name), - Esql.source_network_org_name_count_distinct = COUNT_DISTINCT(Esql.source_network_org_name), - Esql.timestamp_first_seen = MIN(Esql.event_timestamp), - Esql.timestamp_last_seen = MAX(Esql.event_timestamp), - Esql.event_count = COUNT() - BY Esql.time_window_date_trunc, Esql.aws_cloudtrail_user_identity_access_key_id - -| EVAL - Esql.activity_type = CASE( - Esql.source_ip_count_distinct >= 2 AND Esql.source_network_org_name_count_distinct >= 2 AND Esql.source_geo_city_name_count_distinct >= 2 AND Esql.user_agent_original_count_distinct >= 2, "multiple_ip_network_city_user_agent", - Esql.source_ip_count_distinct >= 2 AND Esql.source_network_org_name_count_distinct >= 2 AND Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_network_city", - Esql.source_ip_count_distinct >= 2 AND Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_and_city", - Esql.source_ip_count_distinct >= 2 AND Esql.source_network_org_name_count_distinct >= 2, "multiple_ip_and_network", - Esql.source_ip_count_distinct >= 2 AND Esql.user_agent_original_count_distinct >= 2, "multiple_ip_and_user_agent", +| stats + Esql.event_action_values = values(event.action), + Esql.event_provider_values = values(event.provider), + Esql.aws_cloudtrail_user_identity_access_key_id_values = values(Esql.aws_cloudtrail_user_identity_access_key_id), + Esql.aws_cloudtrail_user_identity_arn_values = values(Esql.aws_cloudtrail_user_identity_arn), + Esql.source_ip_values = values(Esql.source_ip), + Esql.user_agent_original_values = values(Esql.user_agent_original), + Esql.source_ip_user_agent_pair_values = values(Esql.source_ip_user_agent_pair), + Esql.source_geo_city_name_values = values(Esql.source_geo_city_name), + Esql.source_ip_city_pair_values = values(Esql.source_ip_city_pair), + Esql.source_network_org_name_values = values(Esql.source_network_org_name), + Esql.source_ip_count_distinct = count_distinct(Esql.source_ip), + Esql.user_agent_original_count_distinct = count_distinct(Esql.user_agent_original), + Esql.source_geo_city_name_count_distinct = count_distinct(Esql.source_geo_city_name), + Esql.source_network_org_name_count_distinct = count_distinct(Esql.source_network_org_name), + Esql.timestamp_first_seen = min(Esql.event_timestamp), + Esql.timestamp_last_seen = max(Esql.event_timestamp), + Esql.event_count = count() + by Esql.time_window_date_trunc, Esql.aws_cloudtrail_user_identity_access_key_id + +| eval + Esql.activity_type = case( + Esql.source_ip_count_distinct >= 2 and Esql.source_network_org_name_count_distinct >= 2 and Esql.source_geo_city_name_count_distinct >= 2 and Esql.user_agent_original_count_distinct >= 2, "multiple_ip_network_city_user_agent", + Esql.source_ip_count_distinct >= 2 and Esql.source_network_org_name_count_distinct >= 2 and Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_network_city", + Esql.source_ip_count_distinct >= 2 and Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_and_city", + Esql.source_ip_count_distinct >= 2 and Esql.source_network_org_name_count_distinct >= 2, "multiple_ip_and_network", + Esql.source_ip_count_distinct >= 2 and Esql.user_agent_original_count_distinct >= 2, "multiple_ip_and_user_agent", "normal_activity" ), - Esql.activity_fidelity_score = CASE( + Esql.activity_fidelity_score = case( Esql.activity_type == "multiple_ip_network_city_user_agent", "high", Esql.activity_type == "multiple_ip_network_city", "high", Esql.activity_type == "multiple_ip_and_city", "medium", @@ -148,7 +148,7 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index Esql.activity_type == "multiple_ip_and_user_agent", "low" ) -| KEEP +| keep Esql.time_window_date_trunc, Esql.activity_type, Esql.activity_fidelity_score, @@ -170,8 +170,7 @@ FROM logs-aws.cloudtrail* METADATA _id, _version, _index Esql.source_geo_city_name_count_distinct, Esql.source_network_org_name_count_distinct -| WHERE Esql.activity_type != "normal_activity" - +| where Esql.activity_type != "normal_activity" ''' diff --git a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml index 7e9dc88ea8a..ca78ccf8ca6 100644 --- a/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml +++ b/rules/integrations/aws/initial_access_signin_console_login_no_mfa.toml @@ -68,7 +68,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* METADATA _id, _version, _index +from logs-aws.cloudtrail-* metadata _id, _version, _index | where event.provider == "signin.amazonaws.com" @@ -83,7 +83,7 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Only keep events where MFA was not used | where Esql.aws_cloudtrail_additional_eventdata_auth_mfa_used == "No" -// Keep relevant ECS and dissected fields +// keep relevant ECS and dissected fields | keep @timestamp, event.action, diff --git a/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml b/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml index b4d8541ae00..e1ed51b5c6a 100644 --- a/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml +++ b/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml @@ -141,10 +141,10 @@ from logs-aws.cloudtrail* metadata _id, _version, _index and aws.cloudtrail.user_identity.type == "Root" // filter for an access key existing which sources from AssumeRoot - and aws.cloudtrail.user_identity.access_key_id IS NOT NULL + and aws.cloudtrail.user_identity.access_key_id is not null // filter on the request parameters not including UserName which assumes self-assignment - and NOT TO_LOWER(aws.cloudtrail.request_parameters) LIKE "*username*" + and not to_lower(aws.cloudtrail.request_parameters) like "*username*" | keep @timestamp, aws.cloudtrail.request_parameters, @@ -155,7 +155,6 @@ from logs-aws.cloudtrail* metadata _id, _version, _index cloud.account.id, event.action, source.address - ''' diff --git a/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml b/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml index bafdeda125d..a27c8df0b92 100644 --- a/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml +++ b/rules/integrations/aws/persistence_iam_user_created_access_keys_for_another_user.toml @@ -116,7 +116,6 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index aws.cloudtrail.response_elements, aws.cloudtrail.user_identity.arn, aws.cloudtrail.user_identity.type - ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml index c59b1912875..527258f0714 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_group.toml @@ -98,7 +98,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* METADATA _id, _version, _index +from logs-aws.cloudtrail-* metadata _id, _version, _index | where event.provider == "iam.amazonaws.com" @@ -112,7 +112,7 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Filter for attachment of AdministratorAccess policy | where Esql.aws_cloudtrail_request_parameters_policy_name == "AdministratorAccess" -// Keep ECS and derived fields +// keep ECS and derived fields | keep @timestamp, event.provider, @@ -120,7 +120,6 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.outcome, Esql.aws_cloudtrail_request_parameters_policy_name, Esql.aws_cloudtrail_request_parameters_group_name - ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml index e62dbb38dc2..930fce7f23c 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_role.toml @@ -97,7 +97,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* METADATA _id, _version, _index +from logs-aws.cloudtrail-* metadata _id, _version, _index | where event.provider == "iam.amazonaws.com" @@ -111,7 +111,7 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Filter for AdministratorAccess policy attachment | where Esql.aws_cloudtrail_request_parameters_policy_name == "AdministratorAccess" -// Keep relevant ECS and dynamic fields +// keep relevant ECS and dynamic fields | keep @timestamp, event.provider, @@ -119,7 +119,6 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index event.outcome, Esql.aws_cloudtrail_request_parameters_policy_name, Esql.aws_cloudtrail_request_parameters_role_name - ''' diff --git a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml index 6c4247610c2..79a79e8c400 100644 --- a/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml +++ b/rules/integrations/aws/privilege_escalation_iam_administratoraccess_policy_attached_to_user.toml @@ -97,7 +97,7 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws.cloudtrail-* METADATA _id, _version, _index +from logs-aws.cloudtrail-* metadata _id, _version, _index | where event.provider == "iam.amazonaws.com" @@ -111,7 +111,7 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index // Filter for AdministratorAccess policy | where Esql.aws_cloudtrail_request_parameters_policy_name == "AdministratorAccess" -// Keep ECS and parsed fields +// keep ECS and parsed fields | keep @timestamp, cloud.region, @@ -126,7 +126,6 @@ FROM logs-aws.cloudtrail-* METADATA _id, _version, _index user_agent.original, user.name, source.address - ''' diff --git a/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml b/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml index 9e0905713d1..4cb39dcbc9c 100644 --- a/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml +++ b/rules/integrations/aws/privilege_escalation_sts_role_chaining.toml @@ -7,14 +7,17 @@ updated_date = "2025/07/16" [rule] author = ["Elastic"] description = """ -Identifies role chaining activity. Role chaining is when you use one assumed role to assume a second role through the AWS CLI or API. -While this a recognized functionality in AWS, role chaining can be abused for privilege escalation if the subsequent assumed role provides additional privileges. -Role chaining can also be used as a persistence mechanism as each AssumeRole action results in a refreshed session token with a 1 hour maximum duration. -This rule looks for role chaining activity happening within a single account, to eliminate false positives produced by common cross-account behavior. +Identifies role chaining activity. Role chaining is when you use one assumed role to assume a second role through the +AWS CLI or API. While this a recognized functionality in AWS, role chaining can be abused for privilege escalation if +the subsequent assumed role provides additional privileges. Role chaining can also be used as a persistence mechanism as +each AssumeRole action results in a refreshed session token with a 1 hour maximum duration. This rule looks for role +chaining activity happening within a single account, to eliminate false positives produced by common cross-account +behavior. """ false_positives = [ """ - Role chaining can be used as an access control. Ensure that this behavior is not part of a legitimate operation before taking action. + Role chaining can be used as an access control. Ensure that this behavior is not part of a legitimate operation + before taking action. """, ] from = "now-6m" @@ -90,7 +93,6 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index // keep only the relevant fields | keep aws.cloudtrail.user_identity.arn, cloud.region, aws.cloudtrail.resources.account_id, aws.cloudtrail.recipient_account_id, aws.cloudtrail.user_identity.access_key_id - ''' @@ -118,6 +120,7 @@ name = "Application Access Token" reference = "https://attack.mitre.org/techniques/T1550/001/" + [rule.threat.tactic] id = "TA0008" name = "Lateral Movement" @@ -129,3 +132,4 @@ framework = "MITRE ATT&CK" id = "TA0003" name = "Persistence" reference = "https://attack.mitre.org/tactics/TA0003/" + diff --git a/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml b/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml index ddba5badde3..856363b151c 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_execution_without_guardrails.toml @@ -79,31 +79,30 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Create 1-minute time buckets -| eval Esql.time_window_date_trunc = DATE_TRUNC(1 minute, @timestamp) +| eval Esql.time_window_date_trunc = date_trunc(1 minute, @timestamp) // Filter for invocations without guardrails -| where gen_ai.guardrail_id IS NULL and user.id IS NOT NULL +| where gen_ai.guardrail_id is null and user.id is not null -// Keep only relevant fields +// keep only relevant fields | keep @timestamp, Esql.time_window_date_trunc, gen_ai.guardrail_id, user.id -// Count number of unsafe invocations per user +// count number of unsafe invocations per user | stats - Esql.ml_invocations_no_guardrails_count = COUNT() + Esql.ml_invocations_no_guardrails_count = count() by user.id // Filter for suspicious volume | where Esql.ml_invocations_no_guardrails_count > 5 -// Sort descending +// sort descending | sort Esql.ml_invocations_no_guardrails_count desc - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml index 25644fe7906..dde3bd8c1b1 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_by_single_user.toml @@ -80,20 +80,20 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Filter for compliance violations detected | where gen_ai.compliance.violation_detected -// Keep relevant ECS + model fields +// keep relevant ECS + model fields | keep user.id, gen_ai.request.model.id, cloud.account.id -// Count violations by user, model, and account +// count violations by user, model, and account | stats - Esql.ml_violations_count = COUNT(*) + Esql.ml_violations_count = count(*) by user.id, gen_ai.request.model.id, @@ -102,8 +102,7 @@ FROM logs-aws_bedrock.invocation-* // Filter for repeated violations | where Esql.ml_violations_count > 1 -// Sort descending by violation volume +// sort descending by violation volume | sort Esql.ml_violations_count desc - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml index 47fa9f3312d..ba297172e6f 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_guardrails_multiple_violations_in_single_request.toml @@ -80,18 +80,18 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Filter for policy-blocked requests | where gen_ai.policy.action == "BLOCKED" -// Count number of policy matches per request (multi-valued) -| eval Esql.ml_policy_violations_mv_count = MV_COUNT(gen_ai.policy.name) +// count number of policy matches per request (multi-valued) +| eval Esql.ml_policy_violations_mv_count = mv_count(gen_ai.policy.name) // Filter for requests with more than one policy match | where Esql.ml_policy_violations_mv_count > 1 -// Keep relevant fields +// keep relevant fields | keep gen_ai.policy.action, Esql.ml_policy_violations_mv_count, @@ -101,15 +101,14 @@ FROM logs-aws_bedrock.invocation-* // Aggregate requests with multiple violations | stats - Esql.ml_policy_violations_total_unique_requests_count = COUNT(*) + Esql.ml_policy_violations_total_unique_requests_count = count(*) by Esql.ml_policy_violations_mv_count, user.id, gen_ai.request.model.id, cloud.account.id -// Sort by number of unique requests +// sort by number of unique requests | sort Esql.ml_policy_violations_total_unique_requests_count desc - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml index 75f5d15b4b2..eb2d374a199 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_high_confidence_misconduct_blocks_detected.toml @@ -79,43 +79,42 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Expand multi-value fields -| MV_EXPAND gen_ai.compliance.violation_code -| MV_EXPAND gen_ai.policy.confidence -| MV_EXPAND gen_ai.policy.name +| mv_expand gen_ai.compliance.violation_code +| mv_expand gen_ai.policy.confidence +| mv_expand gen_ai.policy.name // Filter for high-confidence content policy blocks with targeted violations | where gen_ai.policy.action == "BLOCKED" and gen_ai.policy.name == "content_policy" - and gen_ai.policy.confidence LIKE "HIGH" - and gen_ai.compliance.violation_code IN ("HATE", "MISCONDUCT", "SEXUAL", "INSULTS", "PROMPT_ATTACK", "VIOLENCE") + and gen_ai.policy.confidence like "HIGH" + and gen_ai.compliance.violation_code in ("HATE", "MISCONDUCT", "SEXUAL", "INSULTS", "PROMPT_ATTACK", "VIOLENCE") -// Keep ECS + compliance fields +// keep ECS + compliance fields | keep user.id, gen_ai.compliance.violation_code -// Count blocked violations per user per violation type +// count blocked violations per user per violation type | stats - Esql.ml_policy_blocked_violation_count = COUNT() + Esql.ml_policy_blocked_violation_count = count() by user.id, gen_ai.compliance.violation_code // Aggregate all violation types per user | stats - Esql.ml_policy_blocked_violation_total_count = SUM(Esql.ml_policy_blocked_violation_count) + Esql.ml_policy_blocked_violation_total_count = sum(Esql.ml_policy_blocked_violation_count) by user.id // Filter for users with more than 5 total violations | where Esql.ml_policy_blocked_violation_total_count > 5 -// Sort by violation volume +// sort by violation volume | sort Esql.ml_policy_blocked_violation_total_count desc - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml b/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml index 39ea7491d2c..0ba98b55658 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_high_resource_consumption_detection.toml @@ -79,9 +79,9 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* -// Keep token usage data +// keep token usage data | keep user.id, gen_ai.usage.prompt_tokens, @@ -89,9 +89,9 @@ FROM logs-aws_bedrock.invocation-* // Aggregate usage metrics | stats - Esql.ml_usage_prompt_tokens_max = MAX(gen_ai.usage.prompt_tokens), - Esql.ml_invocations_total_count = COUNT(*), - Esql.ml_usage_completion_tokens_avg = AVG(gen_ai.usage.completion_tokens) + Esql.ml_usage_prompt_tokens_max = max(gen_ai.usage.prompt_tokens), + Esql.ml_invocations_total_count = count(*), + Esql.ml_usage_completion_tokens_avg = avg(gen_ai.usage.completion_tokens) by user.id @@ -110,8 +110,7 @@ FROM logs-aws_bedrock.invocation-* // Filter on risk score | where Esql.ml_risk_score > 10 -// Sort high risk users to top +// sort high risk users to top | sort Esql.ml_risk_score desc - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml index edc8f7752ce..402d3bf764f 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_attempts_to_use_denied_models_by_user.toml @@ -76,21 +76,21 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Filter for access denied errors from GenAI responses | where gen_ai.response.error_code == "AccessDeniedException" -// Keep ECS and response fields +// keep ECS and response fields | keep user.id, gen_ai.request.model.id, cloud.account.id, gen_ai.response.error_code -// Count total denials per user/model/account +// count total denials per user/model/account | stats - Esql.ml_response_access_denied_count = COUNT(*) + Esql.ml_response_access_denied_count = count(*) by user.id, gen_ai.request.model.id, @@ -99,9 +99,8 @@ FROM logs-aws_bedrock.invocation-* // Filter for users with repeated denials | where Esql.ml_response_access_denied_count > 3 -// Sort by volume of denials +// sort by volume of denials | sort Esql.ml_response_access_denied_count desc - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml index f31c8469d6e..ea99fc671fb 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_sensitive_information_policy_blocks_detected.toml @@ -78,10 +78,10 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Expand multi-valued policy name field -| MV_EXPAND gen_ai.policy.name +| mv_expand gen_ai.policy.name // Filter for blocked actions related to sensitive info policy | where @@ -89,19 +89,18 @@ FROM logs-aws_bedrock.invocation-* and gen_ai.compliance.violation_detected == "true" and gen_ai.policy.name == "sensitive_information_policy" -// Keep only relevant fields +// keep only relevant fields | keep user.id -// Count how many times each user triggered a sensitive info block +// count how many times each user triggered a sensitive info block | stats - Esql.ml_policy_blocked_sensitive_info_count = COUNT() + Esql.ml_policy_blocked_sensitive_info_count = count() by user.id // Filter for users with more than 5 violations | where Esql.ml_policy_blocked_sensitive_info_count > 5 -// Sort highest to lowest +// sort highest to lowest | sort Esql.ml_policy_blocked_sensitive_info_count desc - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml index 4db09ccc295..5bcc33cdbb9 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_topic_policy_blocks_detected.toml @@ -78,10 +78,10 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Expand multi-value policy name field -| MV_EXPAND gen_ai.policy.name +| mv_expand gen_ai.policy.name // Filter for blocked topic policy violations | where @@ -89,19 +89,18 @@ FROM logs-aws_bedrock.invocation-* and gen_ai.compliance.violation_detected == "true" and gen_ai.policy.name == "topic_policy" -// Keep only user info +// keep only user info | keep user.id -// Count how many times each user triggered a blocked topic policy +// count how many times each user triggered a blocked topic policy | stats - Esql.ml_policy_blocked_topic_count = COUNT() + Esql.ml_policy_blocked_topic_count = count() by user.id // Filter for excessive violations | where Esql.ml_policy_blocked_topic_count > 5 -// Sort highest to lowest +// sort highest to lowest | sort Esql.ml_policy_blocked_topic_count desc - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml index e46f6c1a7d9..360900d81b5 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_validation_exception_errors_by_single_user.toml @@ -82,15 +82,15 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Truncate timestamp to 1-minute window -| eval Esql.time_window_date_trunc = DATE_TRUNC(1 minutes, @timestamp) +| eval Esql.time_window_date_trunc = date_trunc(1 minutes, @timestamp) // Filter for validation exceptions in responses | where gen_ai.response.error_code == "ValidationException" -// Keep relevant ECS and derived fields +// keep relevant ECS and derived fields | keep user.id, gen_ai.request.model.id, @@ -98,9 +98,9 @@ FROM logs-aws_bedrock.invocation-* gen_ai.response.error_code, Esql.time_window_date_trunc -// Count number of denials by user/account/time window +// count number of denials by user/account/time window | stats - Esql.ml_response_validation_error_count = COUNT(*) + Esql.ml_response_validation_error_count = count(*) by Esql.time_window_date_trunc, user.id, @@ -108,8 +108,6 @@ FROM logs-aws_bedrock.invocation-* // Filter for excessive errors | where Esql.ml_response_validation_error_count > 3 - - ''' diff --git a/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml b/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml index df8c2feb062..4448076cd26 100644 --- a/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml +++ b/rules/integrations/aws_bedrock/aws_bedrock_multiple_word_policy_blocks_detected.toml @@ -78,10 +78,10 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-aws_bedrock.invocation-* +from logs-aws_bedrock.invocation-* // Expand multivalued policy names -| MV_EXPAND gen_ai.policy.name +| mv_expand gen_ai.policy.name // Filter for blocked profanity-related policy violations | where @@ -89,19 +89,18 @@ FROM logs-aws_bedrock.invocation-* and gen_ai.compliance.violation_detected == "true" and gen_ai.policy.name == "word_policy" -// Keep relevant user field +// keep relevant user field | keep user.id -// Count blocked profanity attempts per user +// count blocked profanity attempts per user | stats - Esql.ml_policy_blocked_profanity_count = COUNT() + Esql.ml_policy_blocked_profanity_count = count() by user.id // Filter for excessive policy violations | where Esql.ml_policy_blocked_profanity_count > 5 -// Sort by violation volume +// sort by violation volume | sort Esql.ml_policy_blocked_profanity_count desc - ''' diff --git a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml index 6e0f94ddcf9..09326ac93d4 100644 --- a/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml +++ b/rules/integrations/azure/credential_access_azure_entra_suspicious_signin.toml @@ -7,9 +7,10 @@ updated_date = "2025/07/16" [rule] author = ["Elastic"] description = """ -Identifies concurrent azure signin events for the same user and from multiple sources, and where one of the authentication -event has some suspicious properties often associated to DeviceCode and OAuth phishing. Adversaries may steal Refresh -Tokens (RTs) via phishing to bypass multi-factor authentication (MFA) and gain unauthorized access to Azure resources. +Identifies concurrent azure signin events for the same user and from multiple sources, and where one of the +authentication event has some suspicious properties often associated to DeviceCode and OAuth phishing. Adversaries may +steal Refresh Tokens (RTs) via phishing to bypass multi-factor authentication (MFA) and gain unauthorized access to +Azure resources. """ false_positives = [ """ @@ -45,7 +46,7 @@ references = [ "https://learn.microsoft.com/en-us/entra/identity/", "https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-sign-ins", "https://docs.microsoft.com/en-us/azure/active-directory/reports-monitoring/reference-azure-monitor-sign-ins-log-schema", - "https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/" + "https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/", ] risk_score = 73 rule_id = "e3bd85e9-7aff-46eb-b60e-20dfc9020d98" @@ -68,17 +69,17 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.signinlogs* METADATA _id, _version, _index +from logs-azure.signinlogs* metadata _id, _version, _index // Scheduled to run every hour, reviewing events from past hour | where - @timestamp > NOW() - 1 hours + @timestamp > now() - 1 hours and event.dataset == "azure.signinlogs" - and source.ip IS NOT NULL - and azure.signinlogs.identity IS NOT NULL - and TO_LOWER(event.outcome) == "success" + and source.ip is not null + and azure.signinlogs.identity is not null + and to_lower(event.outcome) == "success" -// Keep relevant raw fields +// keep relevant raw fields | keep @timestamp, azure.signinlogs.identity, @@ -89,7 +90,7 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index azure.signinlogs.properties.authentication_protocol, azure.signinlogs.properties.app_display_name -// Case classifications for identity usage +// case classifications for identity usage | eval Esql.azure_signinlogs_properties_authentication_device_code_case = case( azure.signinlogs.properties.authentication_protocol == "deviceCode" @@ -111,15 +112,15 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index // Aggregate metrics by user identity | stats - Esql.event_count = COUNT(*), - Esql.azure_signinlogs_properties_authentication_device_code_case_count_distinct = COUNT_DISTINCT(Esql.azure_signinlogs_properties_authentication_device_code_case), - Esql.azure_signinlogs_properties_auth_visual_studio_count_distinct = COUNT_DISTINCT(Esql.azure_signinlogs_auth_visual_studio_case), - Esql.azure_signinlogs_properties_auth_other_count_distinct = COUNT_DISTINCT(Esql.azure_signinlogs_auth_other_case), - Esql.azure_signinlogs_properties_source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.azure_signinlogs_properties_source_ip_values = VALUES(source.ip), - Esql.azure_signinlogs_properties_client_app_values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.azure_signinlogs_properties_auth_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement) + Esql.event_count = count(*), + Esql.azure_signinlogs_properties_authentication_device_code_case_count_distinct = count_distinct(Esql.azure_signinlogs_properties_authentication_device_code_case), + Esql.azure_signinlogs_properties_auth_visual_studio_count_distinct = count_distinct(Esql.azure_signinlogs_auth_visual_studio_case), + Esql.azure_signinlogs_properties_auth_other_count_distinct = count_distinct(Esql.azure_signinlogs_auth_other_case), + Esql.azure_signinlogs_properties_source_ip_count_distinct = count_distinct(source.ip), + Esql.azure_signinlogs_properties_source_ip_values = values(source.ip), + Esql.azure_signinlogs_properties_client_app_values = values(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_auth_requirement_values = values(azure.signinlogs.properties.authentication_requirement) by azure.signinlogs.identity // Detect multiple unique IPs for one user with signs of deviceCode or VSC OAuth usage diff --git a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml index 86f8612b80d..401ef4d84d4 100644 --- a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml +++ b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml @@ -4,7 +4,6 @@ integration = ["azure"] maturity = "production" updated_date = "2025/07/28" - [rule] author = ["Elastic"] description = """ @@ -89,7 +88,7 @@ query = ''' from logs-azure.signinlogs* metadata _id, _version, _index | where - // filter for Entra Sign-In Logs + // filter for Entra Sign-in Logs event.dataset == "azure.signinlogs" and azure.signinlogs.operation_name == "Sign-in activity" and azure.signinlogs.properties.user_type == "Member" @@ -106,29 +105,29 @@ from logs-azure.signinlogs* metadata _id, _version, _index ) | stats - Esql.event_count = COUNT(*), - Esql.azure_signinlogs_properties.session_id_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.session_id), - Esql.source_address_values = VALUES(source.address), - Esql.azure_tenant_id_valuues = VALUES(azure.tenant_id), - Esql_priv.azure_identity_values = VALUES(azure.signinlogs.identity), - Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure_signinlogs_properties_authentication_protocol_values = VALUES(azure.signinlogs.properties.authentication_protocol), - Esql.azure_signinlogs_properties_client_app_used_values = VALUES(azure.signinlogs.properties.client_app_used), - Esql.azure_signinlogs_properties_client_credential_type_values = VALUES(azure.signinlogs.properties.client_credential_type), - Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure_signinlogs_properties_correlation_id_values = VALUES(azure.signinlogs.properties.correlation_id), - Esql.azure_signinlogs_properties_is_interactive_values = VALUES(azure.signinlogs.properties.is_interactive), - Esql.azure_signinlogs_properties_mfa_detail_auth_method_values = VALUES(azure.signinlogs.properties.mfa_detail.auth_method), - Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure_signinlogs_properties_risk_detail_values = VALUES(azure.signinlogs.properties.risk_detail), - Esql.azure_signinlogs_properties_status.error_code_values = VALUES(azure.signinlogs.properties.status.error_code), - Esql.azure_signinlogs_properties_original_request_id_values = VALUES(azure.signinlogs.properties.original_request_id), - Esql.user_id_values = VALUES(user.id) + Esql.event_count = count(*), + Esql.azure_signinlogs_properties.session_id_count_distinct = count_distinct(azure.signinlogs.properties.session_id), + Esql.source_address_values = values(source.address), + Esql.azure_tenant_id_valuues = values(azure.tenant_id), + Esql_priv.azure_identity_values = values(azure.signinlogs.identity), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_authentication_protocol_values = values(azure.signinlogs.properties.authentication_protocol), + Esql.azure_signinlogs_properties_client_app_used_values = values(azure.signinlogs.properties.client_app_used), + Esql.azure_signinlogs_properties_client_credential_type_values = values(azure.signinlogs.properties.client_credential_type), + Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_correlation_id_values = values(azure.signinlogs.properties.correlation_id), + Esql.azure_signinlogs_properties_is_interactive_values = values(azure.signinlogs.properties.is_interactive), + Esql.azure_signinlogs_properties_mfa_detail_auth_method_values = values(azure.signinlogs.properties.mfa_detail.auth_method), + Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_risk_detail_values = values(azure.signinlogs.properties.risk_detail), + Esql.azure_signinlogs_properties_status.error_code_values = values(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_original_request_id_values = values(azure.signinlogs.properties.original_request_id), + Esql.user_id_values = values(user.id) by user.id | where Esql.event_count >= 20 and Esql.azure_signinlogs_properties.session_id_count_distinct >= 10 diff --git a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml index cec78a72025..f80f96bc003 100644 --- a/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml +++ b/rules/integrations/azure/credential_access_azure_key_vault_excessive_retrieval.toml @@ -86,11 +86,11 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.platformlogs-* METADATA _id, _index +from logs-azure.platformlogs-* metadata _id, _index // Filter for Azure Key Vault read operations -| WHERE event.dataset == "azure.platformlogs" - AND event.action IN ( +| where event.dataset == "azure.platformlogs" + and event.action in ( "VaultGet", "KeyGet", "KeyList", @@ -114,38 +114,38 @@ FROM logs-azure.platformlogs-* METADATA _id, _index ) // Truncate timestamps into 1-minute windows -| EVAL Esql.time_window_date_trunc = DATE_TRUNC(1 minute, @timestamp) +| eval Esql.time_window_date_trunc = date_trunc(1 minute, @timestamp) // Aggregate identity, geo, resource, and activity info -| STATS - Esql_priv.azure_platformlogs_identity_claim_upn_values = VALUES(azure.platformlogs.identity.claim.upn), - Esql.azure_platformlogs_identity_claim_upn_count_distinct = COUNT_DISTINCT(azure.platformlogs.identity.claim.upn), - Esql.azure_platformlogs_identity_claim_appid_values = VALUES(azure.platformlogs.identity.claim.appid), - Esql.azure_platformlogs_identity_claim_objectid_values = VALUES(azure.platformlogs.identity.claim.objectid), - - Esql.source_ip_values = VALUES(source.ip), - Esql.geo_city_values = VALUES(geo.city_name), - Esql.geo_region_values = VALUES(geo.region_name), - Esql.geo_country_values = VALUES(geo.country_name), - Esql.source_as_organization_name_values = VALUES(source.as.organization.name), - - Esql.event_action_values = VALUES(event.action), - Esql.event_count = COUNT(*), - Esql.event_action_count_distinct = COUNT_DISTINCT(event.action), - Esql.azure_resource_name_count_distinct = COUNT_DISTINCT(azure.resource.name), - Esql.azure_resource_name_values = VALUES(azure.resource.name), - Esql.azure_platformlogs_result_type_values = VALUES(azure.platformlogs.result_type), - Esql.cloud_region_values = VALUES(cloud.region), - - Esql.agent_name_values = VALUES(agent.name), - Esql.azure_subscription_id_values = VALUES(azure.subscription_id), - Esql.azure_resource_group_values = VALUES(azure.resource.group), - Esql.azure_resource_id_values = VALUES(azure.resource.id) - -BY Esql.time_window_date_trunc, azure.platformlogs.identity.claim.upn - -// Keep relevant fields -| KEEP +| stats + Esql_priv.azure_platformlogs_identity_claim_upn_values = values(azure.platformlogs.identity.claim.upn), + Esql.azure_platformlogs_identity_claim_upn_count_distinct = count_distinct(azure.platformlogs.identity.claim.upn), + Esql.azure_platformlogs_identity_claim_appid_values = values(azure.platformlogs.identity.claim.appid), + Esql.azure_platformlogs_identity_claim_objectid_values = values(azure.platformlogs.identity.claim.objectid), + + Esql.source_ip_values = values(source.ip), + Esql.geo_city_values = values(geo.city_name), + Esql.geo_region_values = values(geo.region_name), + Esql.geo_country_values = values(geo.country_name), + Esql.source_as_organization_name_values = values(source.as.organization.name), + + Esql.event_action_values = values(event.action), + Esql.event_count = count(*), + Esql.event_action_count_distinct = count_distinct(event.action), + Esql.azure_resource_name_count_distinct = count_distinct(azure.resource.name), + Esql.azure_resource_name_values = values(azure.resource.name), + Esql.azure_platformlogs_result_type_values = values(azure.platformlogs.result_type), + Esql.cloud_region_values = values(cloud.region), + + Esql.agent_name_values = values(agent.name), + Esql.azure_subscription_id_values = values(azure.subscription_id), + Esql.azure_resource_group_values = values(azure.resource.group), + Esql.azure_resource_id_values = values(azure.resource.id) + +by Esql.time_window_date_trunc, azure.platformlogs.identity.claim.upn + +// keep relevant fields +| keep Esql.time_window_date_trunc, Esql_priv.azure_platformlogs_identity_claim_upn_values, Esql.azure_platformlogs_identity_claim_upn_count_distinct, @@ -169,10 +169,9 @@ BY Esql.time_window_date_trunc, azure.platformlogs.identity.claim.upn Esql.azure_resource_id_values // Filter for suspiciously high volume of distinct Key Vault reads by a single actor -| WHERE Esql.azure_platformlogs_identity_claim_upn_count_distinct == 1 AND Esql.event_count >= 10 AND Esql.event_action_count_distinct >= 2 - -| SORT Esql.time_window_date_trunc DESC +| where Esql.azure_platformlogs_identity_claim_upn_count_distinct == 1 and Esql.event_count >= 10 and Esql.event_action_count_distinct >= 2 +| sort Esql.time_window_date_trunc desc ''' diff --git a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml index 25cbab78969..5f664ef65ed 100644 --- a/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml +++ b/rules/integrations/azure/credential_access_entra_id_brute_force_activity.toml @@ -90,18 +90,18 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.signinlogs* +from logs-azure.signinlogs* // Define a time window for grouping and maintain the original event timestamp -| EVAL Esql.time_window_date_trunc = DATE_TRUNC(15 minutes, @timestamp) +| eval Esql.time_window_date_trunc = date_trunc(15 minutes, @timestamp) // Filter relevant failed authentication events with specific error codes -| WHERE event.dataset == "azure.signinlogs" - AND event.category == "authentication" - AND azure.signinlogs.category IN ("NonInteractiveUserSignInLogs", "SignInLogs") - AND event.outcome == "failure" - AND azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication" - AND azure.signinlogs.properties.status.error_code IN ( +| where event.dataset == "azure.signinlogs" + and event.category == "authentication" + and azure.signinlogs.category in ("NonInteractiveUserSignInLogs", "SignInLogs") + and event.outcome == "failure" + and azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication" + and azure.signinlogs.properties.status.error_code in ( 50034, // UserAccountNotFound 50126, // InvalidUsernameOrPassword 50055, // PasswordExpired @@ -123,68 +123,68 @@ FROM logs-azure.signinlogs* 120002, // PasswordChangeInvalidNewPasswordWeak 120020 // PasswordChangeFailure ) - AND azure.signinlogs.properties.user_principal_name IS NOT NULL AND azure.signinlogs.properties.user_principal_name != "" - AND user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" - AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" + and azure.signinlogs.properties.user_principal_name is not null and azure.signinlogs.properties.user_principal_name != "" + and user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" + and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK" -| STATS - Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure_signinlogs_properties_device_detail_browser_values = VALUES(azure.signinlogs.properties.device_detail.browser), - Esql.azure_signinlogs_properties_device_detail_device_id_values = VALUES(azure.signinlogs.properties.device_detail.device_id), - Esql.azure_signinlogs_properties_device_detail_operating_system_values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - Esql.azure_signinlogs_properties_incoming_token_type_values = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure_signinlogs_properties_session_id_values = VALUES(azure.signinlogs.properties.session_id), - Esql.azure_signinlogs_properties_user_id_values = VALUES(azure.signinlogs.properties.user_id), - Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure_signinlogs_result_description_values = VALUES(azure.signinlogs.result_description), - Esql.azure_signinlogs_result_signature_values = VALUES(azure.signinlogs.result_signature), - Esql.azure_signinlogs_result_type_values = VALUES(azure.signinlogs.result_type), +| stats + Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser), + Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id), + Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature), + Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type), - Esql.azure_signinlogs_properties_user_id_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.user_id), - Esql.azure_signinlogs_properties_user_id_list = VALUES(azure.signinlogs.properties.user_id), - Esql.azure_signinlogs_result_description_values_all = VALUES(azure.signinlogs.result_description), - Esql.azure_signinlogs_result_description_count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - Esql.azure_signinlogs_properties_status_error_code_values = VALUES(azure.signinlogs.properties.status.error_code), - Esql.azure_signinlogs_properties_status_error_code_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - Esql.azure_signinlogs_properties_incoming_token_type_values_all = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure_signinlogs_properties_app_display_name_values_all = VALUES(azure.signinlogs.properties.app_display_name), - Esql.source_ip_values = VALUES(source.ip), - Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), - Esql.source_geo_country_name_values = VALUES(source.geo.country_name), - Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.timestamp_first_seen = MIN(@timestamp), - Esql.timestamp_last_seen = MAX(@timestamp), - Esql.event_count = COUNT() -BY Esql.time_window_date_trunc + Esql.azure_signinlogs_properties_user_id_count_distinct = count_distinct(azure.signinlogs.properties.user_id), + Esql.azure_signinlogs_properties_user_id_list = values(azure.signinlogs.properties.user_id), + Esql.azure_signinlogs_result_description_values_all = values(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description), + Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_incoming_token_type_values_all = values(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_app_display_name_values_all = values(azure.signinlogs.properties.app_display_name), + Esql.source_ip_values = values(source.ip), + Esql.source_ip_count_distinct = count_distinct(source.ip), + Esql.source_as_organization_name_values = values(source.`as`.organization.name), + Esql.source_geo_country_name_values = values(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name), + Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name), + Esql.timestamp_first_seen = min(@timestamp), + Esql.timestamp_last_seen = max(@timestamp), + Esql.event_count = count() +by Esql.time_window_date_trunc -| EVAL - Esql.duration_seconds = DATE_DIFF("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen), - Esql.brute_force_type = CASE( - Esql.azure_signinlogs_properties_user_id_count_distinct >= 10 AND Esql.event_count >= 30 AND Esql.azure_signinlogs_result_description_count_distinct <= 3 - AND Esql.source_ip_count_distinct >= 5 - AND Esql.duration_seconds <= 600 - AND Esql.azure_signinlogs_properties_user_id_count_distinct > Esql.source_ip_count_distinct, +| eval + Esql.duration_seconds = date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen), + Esql.brute_force_type = case( + Esql.azure_signinlogs_properties_user_id_count_distinct >= 10 and Esql.event_count >= 30 and Esql.azure_signinlogs_result_description_count_distinct <= 3 + and Esql.source_ip_count_distinct >= 5 + and Esql.duration_seconds <= 600 + and Esql.azure_signinlogs_properties_user_id_count_distinct > Esql.source_ip_count_distinct, "credential_stuffing", - Esql.azure_signinlogs_properties_user_id_count_distinct >= 15 AND Esql.azure_signinlogs_result_description_count_distinct == 1 AND Esql.event_count >= 15 AND Esql.duration_seconds <= 1800, + Esql.azure_signinlogs_properties_user_id_count_distinct >= 15 and Esql.azure_signinlogs_result_description_count_distinct == 1 and Esql.event_count >= 15 and Esql.duration_seconds <= 1800, "password_spraying", - (Esql.azure_signinlogs_properties_user_id_count_distinct == 1 AND Esql.azure_signinlogs_result_description_count_distinct == 1 AND Esql.event_count >= 30 AND Esql.duration_seconds <= 300) - OR (Esql.azure_signinlogs_properties_user_id_count_distinct <= 3 AND Esql.source_ip_count_distinct > 30 AND Esql.event_count >= 100), + (Esql.azure_signinlogs_properties_user_id_count_distinct == 1 and Esql.azure_signinlogs_result_description_count_distinct == 1 and Esql.event_count >= 30 and Esql.duration_seconds <= 300) + or (Esql.azure_signinlogs_properties_user_id_count_distinct <= 3 and Esql.source_ip_count_distinct > 30 and Esql.event_count >= 100), "password_guessing", "other" ) -| KEEP +| keep Esql.time_window_date_trunc, Esql.brute_force_type, Esql.duration_seconds, @@ -223,8 +223,7 @@ BY Esql.time_window_date_trunc Esql.azure_signinlogs_result_signature_values, Esql.azure_signinlogs_result_type_values -| WHERE Esql.brute_force_type != "other" - +| where Esql.brute_force_type != "other" ''' diff --git a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml index 94c29ccd982..8981a92e250 100644 --- a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml +++ b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml @@ -84,64 +84,64 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.signinlogs* - -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(30 minutes, @timestamp), - Esql_priv.azure_signinlogs_properties_user_principal_name_lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), - Esql.azure_signinlogs_properties_incoming_token_type_lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), - Esql.azure_signinlogs_properties_app_display_name_lower = TO_LOWER(azure.signinlogs.properties.app_display_name) - -| WHERE event.dataset == "azure.signinlogs" - AND event.category == "authentication" - AND azure.signinlogs.category IN ("NonInteractiveUserSignInLogs", "SignInLogs") - AND event.outcome == "failure" - AND azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication" - AND azure.signinlogs.properties.status.error_code == 50053 - AND azure.signinlogs.properties.user_principal_name IS NOT NULL - AND azure.signinlogs.properties.user_principal_name != "" - AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" - -| STATS - Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure_signinlogs_properties_device_detail_browser_values = VALUES(azure.signinlogs.properties.device_detail.browser), - Esql.azure_signinlogs_properties_device_detail_device_id_values = VALUES(azure.signinlogs.properties.device_detail.device_id), - Esql.azure_signinlogs_properties_device_detail_operating_system_values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - Esql.azure_signinlogs_properties_incoming_token_type_values = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure_signinlogs_properties_session_id_values = VALUES(azure.signinlogs.properties.session_id), - Esql.azure_signinlogs_properties_user_id_values = VALUES(azure.signinlogs.properties.user_id), - Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure_signinlogs_result_description_values = VALUES(azure.signinlogs.result_description), - Esql.azure_signinlogs_result_signature_values = VALUES(azure.signinlogs.result_signature), - Esql.azure_signinlogs_result_type_values = VALUES(azure.signinlogs.result_type), - - Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = COUNT_DISTINCT(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), - Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = VALUES(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), - Esql.azure_signinlogs_result_description_count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - Esql.azure_signinlogs_properties_status_error_code_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - Esql.azure_signinlogs_properties_status_error_code_values = VALUES(azure.signinlogs.properties.status.error_code), - Esql.azure_signinlogs_properties_incoming_token_type_lower_values = VALUES(Esql.azure_signinlogs_properties_incoming_token_type_lower), - Esql.azure_signinlogs_properties_app_display_name_lower_values = VALUES(Esql.azure_signinlogs_properties_app_display_name_lower), - Esql.source_ip_values = VALUES(source.ip), - Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), - Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.source_geo_country_name_values = VALUES(source.geo.country_name), - Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.@timestamp.min = MIN(@timestamp), - Esql.@timestamp.max = MAX(@timestamp), - Esql.event_count = COUNT() -BY Esql.time_window_date_trunc - -| WHERE Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 15 AND Esql.event_count >= 20 - -| KEEP +from logs-azure.signinlogs* + +| eval + Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp), + Esql_priv.azure_signinlogs_properties_user_principal_name_lower = to_lower(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_properties_incoming_token_type_lower = to_lower(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_app_display_name_lower = to_lower(azure.signinlogs.properties.app_display_name) + +| where event.dataset == "azure.signinlogs" + and event.category == "authentication" + and azure.signinlogs.category in ("NonInteractiveUserSignInLogs", "SignInLogs") + and event.outcome == "failure" + and azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication" + and azure.signinlogs.properties.status.error_code == 50053 + and azure.signinlogs.properties.user_principal_name is not null + and azure.signinlogs.properties.user_principal_name != "" + and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK" + +| stats + Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser), + Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id), + Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature), + Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type), + + Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = count_distinct(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), + Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = values(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), + Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description), + Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_incoming_token_type_lower_values = values(Esql.azure_signinlogs_properties_incoming_token_type_lower), + Esql.azure_signinlogs_properties_app_display_name_lower_values = values(Esql.azure_signinlogs_properties_app_display_name_lower), + Esql.source_ip_values = values(source.ip), + Esql.source_ip_count_distinct = count_distinct(source.ip), + Esql.source_as_organization_name_values = values(source.`as`.organization.name), + Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name), + Esql.source_geo_country_name_values = values(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name), + Esql.@timestamp.min = min(@timestamp), + Esql.@timestamp.max = max(@timestamp), + Esql.event_count = count() +by Esql.time_window_date_trunc + +| where Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 15 and Esql.event_count >= 20 + +| keep Esql.time_window_date_trunc, Esql.event_count, Esql.@timestamp.min, @@ -177,7 +177,6 @@ BY Esql.time_window_date_trunc Esql.azure_signinlogs_result_description_values, Esql.azure_signinlogs_result_signature_values, Esql.azure_signinlogs_result_type_values - ''' diff --git a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml index cbd602ec4f9..1185de758c1 100644 --- a/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml +++ b/rules/integrations/azure/credential_access_entra_signin_brute_force_microsoft_365.toml @@ -88,22 +88,22 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.signinlogs* +from logs-azure.signinlogs* -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(15 minutes, @timestamp), - Esql_priv.azure_signinlogs_properties_user_principal_name_lower = TO_LOWER(azure.signinlogs.properties.user_principal_name), - Esql.azure_signinlogs_properties_incoming_token_type_lower = TO_LOWER(azure.signinlogs.properties.incoming_token_type), - Esql.azure_signinlogs_properties_app_display_name_lower = TO_LOWER(azure.signinlogs.properties.app_display_name), +| eval + Esql.time_window_date_trunc = date_trunc(15 minutes, @timestamp), + Esql_priv.azure_signinlogs_properties_user_principal_name_lower = to_lower(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_properties_incoming_token_type_lower = to_lower(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_app_display_name_lower = to_lower(azure.signinlogs.properties.app_display_name), Esql.user_agent_original = user_agent.original -| WHERE event.dataset == "azure.signinlogs" - AND event.category == "authentication" - AND azure.signinlogs.category IN ("NonInteractiveUserSignInLogs", "SignInLogs") - AND azure.signinlogs.properties.resource_display_name RLIKE "(.*)365|SharePoint|Exchange|Teams|Office(.*)" - AND event.outcome == "failure" - AND azure.signinlogs.properties.status.error_code != 50053 - AND azure.signinlogs.properties.status.error_code IN ( +| where event.dataset == "azure.signinlogs" + and event.category == "authentication" + and azure.signinlogs.category in ("NonInteractiveUserSignInLogs", "SignInLogs") + and azure.signinlogs.properties.resource_display_name rlike "(.*)365|SharePoint|Exchange|Teams|Office(.*)" + and event.outcome == "failure" + and azure.signinlogs.properties.status.error_code != 50053 + and azure.signinlogs.properties.status.error_code in ( 50034, // UserAccountNotFound 50126, // InvalidUsernameOrPassword 50055, // PasswordExpired @@ -125,80 +125,80 @@ FROM logs-azure.signinlogs* 120002, // PasswordChangeInvalidNewPasswordWeak 120020 // PasswordChangeFailure ) - AND azure.signinlogs.properties.user_principal_name IS NOT NULL - AND azure.signinlogs.properties.user_principal_name != "" - AND user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" + and azure.signinlogs.properties.user_principal_name is not null + and azure.signinlogs.properties.user_principal_name != "" + and user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0" -| STATS - Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), - Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure_signinlogs_properties_device_detail_browser_values = VALUES(azure.signinlogs.properties.device_detail.browser), - Esql.azure_signinlogs_properties_device_detail_device_id_values = VALUES(azure.signinlogs.properties.device_detail.device_id), - Esql.azure_signinlogs_properties_device_detail_operating_system_values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - Esql.azure_signinlogs_properties_incoming_token_type_values = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure_signinlogs_properties_session_id_values = VALUES(azure.signinlogs.properties.session_id), - Esql.azure_signinlogs_properties_user_id_values = VALUES(azure.signinlogs.properties.user_id), - Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure_signinlogs_result_description_values = VALUES(azure.signinlogs.result_description), - Esql.azure_signinlogs_result_signature_values = VALUES(azure.signinlogs.result_signature), - Esql.azure_signinlogs_result_type_values = VALUES(azure.signinlogs.result_type), +| stats + Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name), + Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser), + Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id), + Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system), + Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature), + Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type), - Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = COUNT_DISTINCT(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), - Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = VALUES(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), - Esql.azure_signinlogs_result_description_count_distinct = COUNT_DISTINCT(azure.signinlogs.result_description), - Esql.azure_signinlogs_result_description_values = VALUES(azure.signinlogs.result_description), - Esql.azure_signinlogs_properties_status_error_code_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.status.error_code), - Esql.azure_signinlogs_properties_status_error_code_values = VALUES(azure.signinlogs.properties.status.error_code), - Esql.azure_signinlogs_properties_incoming_token_type_lower_values = VALUES(Esql.azure_signinlogs_properties_incoming_token_type_lower), - Esql.azure_signinlogs_properties_app_display_name_lower_values = VALUES(Esql.azure_signinlogs_properties_app_display_name_lower), - Esql.source_ip_values = VALUES(source.ip), - Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), - Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.source_geo_country_name_values = VALUES(source.geo.country_name), - Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.@timestamp.min = MIN(@timestamp), - Esql.@timestamp.max = MAX(@timestamp), - Esql.event_count = COUNT() -BY Esql.time_window_date_trunc + Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = count_distinct(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), + Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = values(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), + Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description), + Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description), + Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_incoming_token_type_lower_values = values(Esql.azure_signinlogs_properties_incoming_token_type_lower), + Esql.azure_signinlogs_properties_app_display_name_lower_values = values(Esql.azure_signinlogs_properties_app_display_name_lower), + Esql.source_ip_values = values(source.ip), + Esql.source_ip_count_distinct = count_distinct(source.ip), + Esql.source_as_organization_name_values = values(source.`as`.organization.name), + Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name), + Esql.source_geo_country_name_values = values(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name), + Esql.@timestamp.min = min(@timestamp), + Esql.@timestamp.max = max(@timestamp), + Esql.event_count = count() +by Esql.time_window_date_trunc -| EVAL - Esql.event_duration_seconds = DATE_DIFF("seconds", Esql.@timestamp.min, Esql.@timestamp.max), - Esql.event_bf_type = CASE( +| eval + Esql.event_duration_seconds = date_diff("seconds", Esql.@timestamp.min, Esql.@timestamp.max), + Esql.event_bf_type = case( Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 10 - AND Esql.event_count >= 30 - AND Esql.azure_signinlogs_result_description_count_distinct <= 3 - AND Esql.source_ip_count_distinct >= 5 - AND Esql.event_duration_seconds <= 600 - AND Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct > Esql.source_ip_count_distinct, + and Esql.event_count >= 30 + and Esql.azure_signinlogs_result_description_count_distinct <= 3 + and Esql.source_ip_count_distinct >= 5 + and Esql.event_duration_seconds <= 600 + and Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct > Esql.source_ip_count_distinct, "credential_stuffing", Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 15 - AND Esql.azure_signinlogs_result_description_count_distinct == 1 - AND Esql.event_count >= 15 - AND Esql.event_duration_seconds <= 1800, + and Esql.azure_signinlogs_result_description_count_distinct == 1 + and Esql.event_count >= 15 + and Esql.event_duration_seconds <= 1800, "password_spraying", (Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct == 1 - AND Esql.azure_signinlogs_result_description_count_distinct == 1 - AND Esql.event_count >= 30 - AND Esql.event_duration_seconds <= 300) - OR (Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct <= 3 - AND Esql.source_ip_count_distinct > 30 - AND Esql.event_count >= 100), + and Esql.azure_signinlogs_result_description_count_distinct == 1 + and Esql.event_count >= 30 + and Esql.event_duration_seconds <= 300) + or (Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct <= 3 + and Esql.source_ip_count_distinct > 30 + and Esql.event_count >= 100), "password_guessing", "other" ) -| WHERE Esql.event_bf_type != "other" +| where Esql.event_bf_type != "other" -| KEEP +| keep Esql.time_window_date_trunc, Esql.event_bf_type, Esql.event_duration_seconds, @@ -232,7 +232,6 @@ BY Esql.time_window_date_trunc Esql.azure_signinlogs_properties_risk_state_values, Esql.azure_signinlogs_properties_session_id_values, Esql.azure_signinlogs_properties_user_id_values - ''' diff --git a/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml b/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml index a18510b3845..58bdd681f8c 100644 --- a/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml +++ b/rules/integrations/azure/initial_access_entra_graph_single_session_from_multiple_addresses.toml @@ -88,30 +88,30 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.* -| WHERE +from logs-azure.* +| where (event.dataset == "azure.signinlogs" - AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" - AND azure.signinlogs.properties.session_id IS NOT NULL) - OR + and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK" + and azure.signinlogs.properties.session_id is not null) + or (event.dataset == "azure.graphactivitylogs" - AND source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" - AND azure.graphactivitylogs.properties.c_sid IS NOT NULL) + and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK" + and azure.graphactivitylogs.properties.c_sid is not null) -| EVAL - Esql.azure_signinlogs_properties_session_id_coalesce = COALESCE(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid), - Esql.azure_signinlogs_properties_user_id_coalesce = COALESCE(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id), - Esql.azure_signinlogs_properties_app_id_coalesce = COALESCE(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id), +| eval + Esql.azure_signinlogs_properties_session_id_coalesce = coalesce(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid), + Esql.azure_signinlogs_properties_user_id_coalesce = coalesce(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id), + Esql.azure_signinlogs_properties_app_id_coalesce = coalesce(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id), Esql.source_ip = source.ip, Esql.@timestamp = @timestamp, - Esql.event_type_case = CASE( + Esql.event_type_case = case( event.dataset == "azure.signinlogs", "signin", event.dataset == "azure.graphactivitylogs", "graph", "other" ), - Esql.time_window_date_trunc = DATE_TRUNC(5 minutes, @timestamp) + Esql.time_window_date_trunc = date_trunc(5 minutes, @timestamp) -| KEEP +| keep Esql.azure_signinlogs_properties_session_id_coalesce, Esql.source_ip, Esql.@timestamp, @@ -120,34 +120,33 @@ FROM logs-azure.* Esql.azure_signinlogs_properties_user_id_coalesce, Esql.azure_signinlogs_properties_app_id_coalesce -| STATS - Esql.azure_signinlogs_properties_user_id_coalesce_values = VALUES(Esql.azure_signinlogs_properties_user_id_coalesce), - Esql.azure_signinlogs_properties_session_id_coalesce_values = VALUES(Esql.azure_signinlogs_properties_session_id_coalesce), - Esql.source_ip_values = VALUES(Esql.source_ip), - Esql.source_ip_count_distinct = COUNT_DISTINCT(Esql.source_ip), - Esql.azure_signinlogs_properties_app_id_coalesce_values = VALUES(Esql.azure_signinlogs_properties_app_id_coalesce), - Esql.azure_signinlogs_properties_app_id_coalesce_count_distinct = COUNT_DISTINCT(Esql.azure_signinlogs_properties_app_id_coalesce), - Esql.event_type_case_values = VALUES(Esql.event_type_case), - Esql.event_type_case_count_distinct = COUNT_DISTINCT(Esql.event_type_case), - Esql.@timestamp.min = MIN(Esql.@timestamp), - Esql.@timestamp.max = MAX(Esql.@timestamp), - Esql.signin_time_min = MIN(CASE(Esql.event_type_case == "signin", Esql.@timestamp, NULL)), - Esql.graph_time_min = MIN(CASE(Esql.event_type_case == "graph", Esql.@timestamp, NULL)), - Esql.event_count = COUNT() - BY Esql.azure_signinlogs_properties_session_id_coalesce, Esql.time_window_date_trunc - -| EVAL - Esql.event_duration_minutes_date_diff = DATE_DIFF("minutes", Esql.@timestamp.min, Esql.@timestamp.max), - Esql.event_signin_to_graph_delay_minutes_date_diff = DATE_DIFF("minutes", Esql.signin_time_min, Esql.graph_time_min) - -| WHERE - Esql.event_type_case_count_distinct > 1 AND - Esql.source_ip_count_distinct > 1 AND - Esql.event_duration_minutes_date_diff <= 5 AND - Esql.signin_time_min IS NOT NULL AND - Esql.graph_time_min IS NOT NULL AND +| stats + Esql.azure_signinlogs_properties_user_id_coalesce_values = values(Esql.azure_signinlogs_properties_user_id_coalesce), + Esql.azure_signinlogs_properties_session_id_coalesce_values = values(Esql.azure_signinlogs_properties_session_id_coalesce), + Esql.source_ip_values = values(Esql.source_ip), + Esql.source_ip_count_distinct = count_distinct(Esql.source_ip), + Esql.azure_signinlogs_properties_app_id_coalesce_values = values(Esql.azure_signinlogs_properties_app_id_coalesce), + Esql.azure_signinlogs_properties_app_id_coalesce_count_distinct = count_distinct(Esql.azure_signinlogs_properties_app_id_coalesce), + Esql.event_type_case_values = values(Esql.event_type_case), + Esql.event_type_case_count_distinct = count_distinct(Esql.event_type_case), + Esql.@timestamp.min = min(Esql.@timestamp), + Esql.@timestamp.max = max(Esql.@timestamp), + Esql.signin_time_min = min(case(Esql.event_type_case == "signin", Esql.@timestamp, null)), + Esql.graph_time_min = min(case(Esql.event_type_case == "graph", Esql.@timestamp, null)), + Esql.event_count = count() + by Esql.azure_signinlogs_properties_session_id_coalesce, Esql.time_window_date_trunc + +| eval + Esql.event_duration_minutes_date_diff = date_diff("minutes", Esql.@timestamp.min, Esql.@timestamp.max), + Esql.event_signin_to_graph_delay_minutes_date_diff = date_diff("minutes", Esql.signin_time_min, Esql.graph_time_min) + +| where + Esql.event_type_case_count_distinct > 1 and + Esql.source_ip_count_distinct > 1 and + Esql.event_duration_minutes_date_diff <= 5 and + Esql.signin_time_min is not null and + Esql.graph_time_min is not null and Esql.event_signin_to_graph_delay_minutes_date_diff >= 0 - ''' diff --git a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml index d5f5ad68e06..0cd35b7bd96 100644 --- a/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml +++ b/rules/integrations/azure/initial_access_entra_id_suspicious_oauth_flow_via_auth_broker_to_drs.toml @@ -89,68 +89,68 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.signinlogs* METADATA _id, _version, _index -| WHERE - event.dataset == "azure.signinlogs" AND - event.outcome == "success" AND - azure.signinlogs.properties.user_type == "Member" AND - azure.signinlogs.identity IS NOT NULL AND - azure.signinlogs.properties.user_principal_name IS NOT NULL AND - source.address IS NOT NULL AND - azure.signinlogs.properties.app_id == "29d9ed98-a469-4536-ade2-f981bc1d605e" AND // MAB +from logs-azure.signinlogs* metadata _id, _version, _index +| where + event.dataset == "azure.signinlogs" and + event.outcome == "success" and + azure.signinlogs.properties.user_type == "Member" and + azure.signinlogs.identity is not null and + azure.signinlogs.properties.user_principal_name is not null and + source.address is not null and + azure.signinlogs.properties.app_id == "29d9ed98-a469-4536-ade2-f981bc1d605e" and // MAB azure.signinlogs.properties.resource_id == "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9" // DRS -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(30 minutes, @timestamp), +| eval + Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp), Esql.azure_signinlogs_properties_session_id = azure.signinlogs.properties.session_id, - Esql.is_browser_case = CASE( - TO_LOWER(azure.signinlogs.properties.device_detail.browser) RLIKE "(chrome|firefox|edge|safari).*", 1, 0 + Esql.is_browser_case = case( + to_lower(azure.signinlogs.properties.device_detail.browser) rlike "(chrome|firefox|edge|safari).*", 1, 0 ) -| STATS - Esql_priv.azure_signinlogs_properties_user_display_name_values = VALUES(azure.signinlogs.properties.user_display_name), - Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES(azure.signinlogs.properties.user_principal_name), - Esql.azure_signinlogs_properties_session_id_values = VALUES(azure.signinlogs.properties.session_id), - Esql.azure_signinlogs_properties_unique_token_identifier_values = VALUES(azure.signinlogs.properties.unique_token_identifier), - - Esql.source_geo_city_name_values = VALUES(source.geo.city_name), - Esql.source_geo_country_name_values = VALUES(source.geo.country_name), - Esql.source_geo_region_name_values = VALUES(source.geo.region_name), - Esql.source_address_values = VALUES(source.address), - Esql.source_address_count_distinct = COUNT_DISTINCT(source.address), - Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), - - Esql.azure_signinlogs_properties_authentication_protocol_values = VALUES(azure.signinlogs.properties.authentication_protocol), - Esql.azure_signinlogs_properties_authentication_requirement_values = VALUES(azure.signinlogs.properties.authentication_requirement), - Esql.azure_signinlogs_properties_is_interactive_values = VALUES(azure.signinlogs.properties.is_interactive), - - Esql.azure_signinlogs_properties_incoming_token_type_values = VALUES(azure.signinlogs.properties.incoming_token_type), - Esql.azure_signinlogs_properties_token_protection_status_details_sign_in_session_status_values = VALUES(azure.signinlogs.properties.token_protection_status_details.sign_in_session_status), - Esql.azure_signinlogs_properties_session_id_count_distinct = COUNT_DISTINCT(azure.signinlogs.properties.session_id), - Esql.azure_signinlogs_properties_app_display_name_values = VALUES(azure.signinlogs.properties.app_display_name), - Esql.azure_signinlogs_properties_app_id_values = VALUES(azure.signinlogs.properties.app_id), - Esql.azure_signinlogs_properties_resource_id_values = VALUES(azure.signinlogs.properties.resource_id), - Esql.azure_signinlogs_properties_resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name), - - Esql.azure_signinlogs_properties_app_owner_tenant_id_values = VALUES(azure.signinlogs.properties.app_owner_tenant_id), - Esql.azure_signinlogs_properties_resource_owner_tenant_id_values = VALUES(azure.signinlogs.properties.resource_owner_tenant_id), - - Esql.azure_signinlogs_properties_conditional_access_status_values = VALUES(azure.signinlogs.properties.conditional_access_status), - Esql.azure_signinlogs_properties_risk_state_values = VALUES(azure.signinlogs.properties.risk_state), - Esql.azure_signinlogs_properties_risk_level_aggregated_values = VALUES(azure.signinlogs.properties.risk_level_aggregated), - - Esql.azure_signinlogs_properties_device_detail_browser_values = VALUES(azure.signinlogs.properties.device_detail.browser), - Esql.azure_signinlogs_properties_device_detail_operating_system_values = VALUES(azure.signinlogs.properties.device_detail.operating_system), - Esql.user_agent_original_values = VALUES(user_agent.original), - Esql.is_browser_case_max = MAX(Esql.is_browser_case), - - Esql.event_count = COUNT(*) - BY +| stats + Esql_priv.azure_signinlogs_properties_user_display_name_values = values(azure.signinlogs.properties.user_display_name), + Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name), + Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_unique_token_identifier_values = values(azure.signinlogs.properties.unique_token_identifier), + + Esql.source_geo_city_name_values = values(source.geo.city_name), + Esql.source_geo_country_name_values = values(source.geo.country_name), + Esql.source_geo_region_name_values = values(source.geo.region_name), + Esql.source_address_values = values(source.address), + Esql.source_address_count_distinct = count_distinct(source.address), + Esql.source_as_organization_name_values = values(source.`as`.organization.name), + + Esql.azure_signinlogs_properties_authentication_protocol_values = values(azure.signinlogs.properties.authentication_protocol), + Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement), + Esql.azure_signinlogs_properties_is_interactive_values = values(azure.signinlogs.properties.is_interactive), + + Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type), + Esql.azure_signinlogs_properties_token_protection_status_details_sign_in_session_status_values = values(azure.signinlogs.properties.token_protection_status_details.sign_in_session_status), + Esql.azure_signinlogs_properties_session_id_count_distinct = count_distinct(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name), + Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id), + Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id), + Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name), + + Esql.azure_signinlogs_properties_app_owner_tenant_id_values = values(azure.signinlogs.properties.app_owner_tenant_id), + Esql.azure_signinlogs_properties_resource_owner_tenant_id_values = values(azure.signinlogs.properties.resource_owner_tenant_id), + + Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status), + Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state), + Esql.azure_signinlogs_properties_risk_level_aggregated_values = values(azure.signinlogs.properties.risk_level_aggregated), + + Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser), + Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system), + Esql.user_agent_original_values = values(user_agent.original), + Esql.is_browser_case_max = max(Esql.is_browser_case), + + Esql.event_count = count(*) + by Esql.time_window_date_trunc, azure.signinlogs.properties.user_principal_name, azure.signinlogs.properties.session_id -| KEEP +| keep Esql.time_window_date_trunc, Esql_priv.azure_signinlogs_properties_user_display_name_values, Esql_priv.azure_signinlogs_properties_user_principal_name_values, @@ -183,12 +183,11 @@ FROM logs-azure.signinlogs* METADATA _id, _version, _index Esql.is_browser_case_max, Esql.event_count -| WHERE - Esql.source_address_count_distinct >= 2 AND - Esql.azure_signinlogs_properties_session_id_count_distinct == 1 AND - Esql.is_browser_case_max >= 1 AND +| where + Esql.source_address_count_distinct >= 2 and + Esql.azure_signinlogs_properties_session_id_count_distinct == 1 and + Esql.is_browser_case_max >= 1 and Esql.event_count >= 2 - ''' diff --git a/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml b/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml index 3b28f7881c8..3094d7236cc 100644 --- a/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml +++ b/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml @@ -57,15 +57,15 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.auditlogs-* metadata _id, _version, _index -| WHERE event.action == "Authentication Methods Policy Update" -| EVAL Esql.azure_auditlogs_properties_target_resources_modified_properties_new_value_replace = REPLACE(`azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`, "\\\\", "") -| EVAL Esql.azure_auditlogs_properties_target_resources_modified_properties_old_value_replace = REPLACE(`azure.auditlogs.properties.target_resources.0.modified_properties.0.old_value`, "\\\\", "") -| DISSECT Esql.azure_auditlogs_properties_target_resources_modified_properties_new_value_replace "%{}discoveryUrl\":\"%{Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new}\"}%{}" -| DISSECT Esql.azure_auditlogs_properties_target_resources_modified_properties_old_value_replace "%{}discoveryUrl\":\"%{Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old}\"}%{}" -| WHERE Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new IS NOT NULL and Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old IS NOT NULL -| WHERE Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new != Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old -| KEEP +from logs-azure.auditlogs-* metadata _id, _version, _index +| where event.action == "Authentication Methods Policy Update" +| eval Esql.azure_auditlogs_properties_target_resources_modified_properties_new_value_replace = replace(`azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`, "\\\\", "") +| eval Esql.azure_auditlogs_properties_target_resources_modified_properties_old_value_replace = replace(`azure.auditlogs.properties.target_resources.0.modified_properties.0.old_value`, "\\\\", "") +| dissect Esql.azure_auditlogs_properties_target_resources_modified_properties_new_value_replace "%{}discoveryUrl\":\"%{Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new}\"}%{}" +| dissect Esql.azure_auditlogs_properties_target_resources_modified_properties_old_value_replace "%{}discoveryUrl\":\"%{Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old}\"}%{}" +| where Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new is not null and Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old is not null +| where Esql.azure_auditlogs_properties_auth_oidc_discovery_url_new != Esql.azure_auditlogs_properties_auth_oidc_discovery_url_old +| keep @timestamp, event.action, event.outcome, diff --git a/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml b/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml index bbfea7a52f1..26e2b388fa3 100644 --- a/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_denial_of_ml_service_detection.toml @@ -76,25 +76,24 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure_openai.logs-* -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(1 minutes, @timestamp) -| WHERE azure.open_ai.operation_name == "ChatCompletions_Create" -| KEEP +from logs-azure_openai.logs-* +| eval + Esql.time_window_date_trunc = date_trunc(1 minutes, @timestamp) +| where azure.open_ai.operation_name == "ChatCompletions_Create" +| keep azure.open_ai.properties.request_length, azure.resource.name, cloud.account.id, Esql.time_window_date_trunc -| STATS - Esql.event_count = COUNT(*), - Esql.azure_open_ai_properties_request_length_avg = AVG(azure.open_ai.properties.request_length) - BY +| stats + Esql.event_count = count(*), + Esql.azure_open_ai_properties_request_length_avg = avg(azure.open_ai.properties.request_length) + by Esql.time_window_date_trunc, azure.resource.name -| WHERE - Esql.event_count >= 10 AND +| where + Esql.event_count >= 10 and Esql.azure_open_ai_properties_request_length_avg >= 5000 -| SORT Esql.event_count DESC - +| sort Esql.event_count desc ''' diff --git a/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml b/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml index c570a581497..4979da3f494 100644 --- a/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_insecure_output_handling_detection.toml @@ -71,24 +71,23 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure_openai.logs-* -| WHERE - azure.open_ai.properties.response_length == 0 AND - azure.open_ai.result_signature == "200" AND +from logs-azure_openai.logs-* +| where + azure.open_ai.properties.response_length == 0 and + azure.open_ai.result_signature == "200" and azure.open_ai.operation_name == "ChatCompletions_Create" -| KEEP +| keep azure.open_ai.properties.request_length, azure.open_ai.result_signature, cloud.account.id, azure.resource.name -| STATS - Esql.event_count = COUNT(*) - BY +| stats + Esql.event_count = count(*) + by azure.resource.name -| WHERE +| where Esql.event_count >= 10 -| SORT - Esql.event_count DESC - +| sort + Esql.event_count desc ''' diff --git a/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml b/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml index 9caa72ff224..1e584a3119b 100644 --- a/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml +++ b/rules/integrations/azure_openai/azure_openai_model_theft_detection.toml @@ -73,28 +73,27 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure_openai.logs-* -| WHERE - azure.open_ai.operation_name == "ListKey" AND +from logs-azure_openai.logs-* +| where + azure.open_ai.operation_name == "ListKey" and azure.open_ai.category == "Audit" -| KEEP +| keep @timestamp, azure.open_ai.operation_name, azure.open_ai.category, azure.resource.group, azure.resource.name, azure.open_ai.properties.response_length -| STATS - Esql.event_count = COUNT(*), - Esql.azure_open_ai_properties_response_length_max = MAX(azure.open_ai.properties.response_length) - BY +| stats + Esql.event_count = count(*), + Esql.azure_open_ai_properties_response_length_max = max(azure.open_ai.properties.response_length) + by azure.resource.group, azure.resource.name -| WHERE - Esql.event_count >= 100 OR +| where + Esql.event_count >= 100 or Esql.azure_open_ai_properties_response_length_max >= 1000000 -| SORT - Esql.event_count DESC - +| sort + Esql.event_count desc ''' diff --git a/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml b/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml index f798080c63a..37d9710d3a5 100644 --- a/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml +++ b/rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml @@ -80,31 +80,30 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-o365.audit-* -| WHERE - @timestamp > NOW() - 14d AND - event.dataset == "o365.audit" AND - event.provider == "OneDrive" AND - event.action == "FileDownloaded" AND - o365.audit.AuthenticationType == "OAuth" AND +from logs-o365.audit-* +| where + @timestamp > now() - 14d and + event.dataset == "o365.audit" and + event.provider == "OneDrive" and + event.action == "FileDownloaded" and + o365.audit.AuthenticationType == "OAuth" and event.outcome == "success" -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(1 minutes, @timestamp) -| KEEP +| eval + Esql.time_window_date_trunc = date_trunc(1 minutes, @timestamp) +| keep Esql.time_window_date_trunc, o365.audit.UserId, file.name, source.ip -| STATS - Esql.file_name_count_distinct = COUNT_DISTINCT(file.name), - Esql.event_count = COUNT(*) - BY +| stats + Esql.file_name_count_distinct = count_distinct(file.name), + Esql.event_count = count(*) + by Esql.time_window_date_trunc, o365.audit.UserId, source.ip -| WHERE +| where Esql.file_name_count_distinct >= 25 - ''' diff --git a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml index ba111d73adf..448eb30ea34 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_excessive_account_lockouts.toml @@ -72,37 +72,37 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-o365.audit-* -| MV_EXPAND event.category -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(5 minutes, @timestamp) -| WHERE - event.dataset == "o365.audit" AND - event.category == "authentication" AND - event.provider IN ("AzureActiveDirectory", "Exchange") AND - event.action IN ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") AND - TO_LOWER(o365.audit.ExtendedProperties.RequestType) RLIKE "(oauth.*||.*login.*)" AND - o365.audit.LogonError == "IdsLocked" AND - TO_LOWER(o365.audit.UserId) != "not available" AND - o365.audit.Target.Type IN ("0", "2", "6", "10") AND - source.`as`.organization.name != "MICROSOFT-CORP-MSN-AS-BLOCK" -| STATS - Esql_priv.o365_audit_UserId_count_distinct = COUNT_DISTINCT(TO_LOWER(o365.audit.UserId)), - Esql_priv.o365_audit_UserId_values = VALUES(TO_LOWER(o365.audit.UserId)), - Esql.source_ip_values = VALUES(source.ip), - Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), - Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.source_geo_country_name_values = VALUES(source.geo.country_name), - Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.o365_audit_ExtendedProperties_RequestType_values = VALUES(TO_LOWER(o365.audit.ExtendedProperties.RequestType)), - Esql.timestamp_first_seen = MIN(@timestamp), - Esql.timestamp_last_seen = MAX(@timestamp), - Esql.event_count = COUNT(*) - BY Esql.time_window_date_trunc -| EVAL - Esql.event_duration_seconds = DATE_DIFF("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen) -| KEEP +from logs-o365.audit-* +| mv_expand event.category +| eval + Esql.time_window_date_trunc = date_trunc(5 minutes, @timestamp) +| where + event.dataset == "o365.audit" and + event.category == "authentication" and + event.provider in ("AzureActiveDirectory", "Exchange") and + event.action in ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") and + to_lower(o365.audit.ExtendedProperties.RequestType) rlike "(oauth.*||.*login.*)" and + o365.audit.LogonError == "IdsLocked" and + to_lower(o365.audit.UserId) != "not available" and + o365.audit.Target.Type in ("0", "2", "6", "10") and + source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK" +| stats + Esql_priv.o365_audit_UserId_count_distinct = count_distinct(to_lower(o365.audit.UserId)), + Esql_priv.o365_audit_UserId_values = values(to_lower(o365.audit.UserId)), + Esql.source_ip_values = values(source.ip), + Esql.source_ip_count_distinct = count_distinct(source.ip), + Esql.source_as_organization_name_values = values(source.`as`.organization.name), + Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name), + Esql.source_geo_country_name_values = values(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name), + Esql.o365_audit_ExtendedProperties_RequestType_values = values(to_lower(o365.audit.ExtendedProperties.RequestType)), + Esql.timestamp_first_seen = min(@timestamp), + Esql.timestamp_last_seen = max(@timestamp), + Esql.event_count = count(*) + by Esql.time_window_date_trunc +| eval + Esql.event_duration_seconds = date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen) +| keep Esql.time_window_date_trunc, Esql_priv.o365_audit_UserId_count_distinct, Esql_priv.o365_audit_UserId_values, @@ -117,11 +117,10 @@ FROM logs-o365.audit-* Esql.timestamp_last_seen, Esql.event_count, Esql.event_duration_seconds -| WHERE - Esql_priv.o365_audit_UserId_count_distinct >= 10 AND - Esql.event_count >= 10 AND +| where + Esql_priv.o365_audit_UserId_count_distinct >= 10 and + Esql.event_count >= 10 and Esql.event_duration_seconds <= 300 - ''' diff --git a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml index 065434ea3a7..20413cd6ea4 100644 --- a/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml +++ b/rules/integrations/o365/credential_access_microsoft_365_potential_user_account_brute_force.toml @@ -78,21 +78,21 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-o365.audit-* -| MV_EXPAND event.category -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(5 minutes, @timestamp), - Esql_priv.o365_audit_UserId_lower = TO_LOWER(o365.audit.UserId), +from logs-o365.audit-* +| mv_expand event.category +| eval + Esql.time_window_date_trunc = date_trunc(5 minutes, @timestamp), + Esql_priv.o365_audit_UserId_lower = to_lower(o365.audit.UserId), Esql.o365_audit_LogonError = o365.audit.LogonError, - Esql.o365_audit_ExtendedProperties_RequestType_lower = TO_LOWER(o365.audit.ExtendedProperties.RequestType) -| WHERE - event.dataset == "o365.audit" AND - event.category == "authentication" AND - event.provider IN ("AzureActiveDirectory", "Exchange") AND - event.action IN ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") AND - Esql.o365_audit_ExtendedProperties_RequestType_lower RLIKE "(oauth.*||.*login.*)" AND - Esql.o365_audit_LogonError != "IdsLocked" AND - Esql.o365_audit_LogonError NOT IN ( + Esql.o365_audit_ExtendedProperties_RequestType_lower = to_lower(o365.audit.ExtendedProperties.RequestType) +| where + event.dataset == "o365.audit" and + event.category == "authentication" and + event.provider in ("AzureActiveDirectory", "Exchange") and + event.action in ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") and + Esql.o365_audit_ExtendedProperties_RequestType_lower rlike "(oauth.*||.*login.*)" and + Esql.o365_audit_LogonError != "IdsLocked" and + Esql.o365_audit_LogonError not in ( "EntitlementGrantsNotFound", "UserStrongAuthEnrollmentRequired", "UserStrongAuthClientAuthNRequired", @@ -102,34 +102,34 @@ FROM logs-o365.audit-* "SsoUserAccountNotFoundInResourceTenant", "UserStrongAuthExpired", "CmsiInterrupt" - ) AND - Esql_priv.o365_audit_UserId_lower != "not available" AND - o365.audit.Target.Type IN ("0", "2", "6", "10") -| STATS - Esql.o365_audit_UserId_lower_count_distinct = COUNT_DISTINCT(Esql_priv.o365_audit_UserId_lower), - Esql_priv.o365_audit_UserId_lower_values = VALUES(Esql_priv.o365_audit_UserId_lower), - Esql.o365_audit_LogonError_values = VALUES(Esql.o365_audit_LogonError), - Esql.o365_audit_LogonError_count_distinct = COUNT_DISTINCT(Esql.o365_audit_LogonError), - Esql.o365_audit_ExtendedProperties_RequestType_values = VALUES(Esql.o365_audit_ExtendedProperties_RequestType_lower), - Esql.source_ip_values = VALUES(source.ip), - Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), - Esql.source_geo_country_name_values = VALUES(source.geo.country_name), - Esql.source_geo_country_name_count_distinct = COUNT_DISTINCT(source.geo.country_name), - Esql.source_as_organization_name_count_distinct = COUNT_DISTINCT(source.`as`.organization.name), - Esql.timestamp_first_seen = MIN(@timestamp), - Esql.timestamp_last_seen = MAX(@timestamp), - Esql.event_count = COUNT(*) - BY Esql.time_window_date_trunc -| EVAL - Esql.event_duration_seconds = DATE_DIFF("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen), - Esql.brute_force_type = CASE( - Esql.o365_audit_UserId_lower_count_distinct >= 15 AND Esql.o365_audit_LogonError_count_distinct == 1 AND Esql.event_count >= 10 AND Esql.event_duration_seconds <= 1800, "password_spraying", - Esql.o365_audit_UserId_lower_count_distinct >= 8 AND Esql.event_count >= 15 AND Esql.o365_audit_LogonError_count_distinct <= 3 AND Esql.source_ip_count_distinct <= 5 AND Esql.event_duration_seconds <= 600, "credential_stuffing", - Esql.o365_audit_UserId_lower_count_distinct == 1 AND Esql.o365_audit_LogonError_count_distinct == 1 AND Esql.event_count >= 20 AND Esql.event_duration_seconds <= 300, "password_guessing", + ) and + Esql_priv.o365_audit_UserId_lower != "not available" and + o365.audit.Target.Type in ("0", "2", "6", "10") +| stats + Esql.o365_audit_UserId_lower_count_distinct = count_distinct(Esql_priv.o365_audit_UserId_lower), + Esql_priv.o365_audit_UserId_lower_values = values(Esql_priv.o365_audit_UserId_lower), + Esql.o365_audit_LogonError_values = values(Esql.o365_audit_LogonError), + Esql.o365_audit_LogonError_count_distinct = count_distinct(Esql.o365_audit_LogonError), + Esql.o365_audit_ExtendedProperties_RequestType_values = values(Esql.o365_audit_ExtendedProperties_RequestType_lower), + Esql.source_ip_values = values(source.ip), + Esql.source_ip_count_distinct = count_distinct(source.ip), + Esql.source_as_organization_name_values = values(source.`as`.organization.name), + Esql.source_geo_country_name_values = values(source.geo.country_name), + Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name), + Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name), + Esql.timestamp_first_seen = min(@timestamp), + Esql.timestamp_last_seen = max(@timestamp), + Esql.event_count = count(*) + by Esql.time_window_date_trunc +| eval + Esql.event_duration_seconds = date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen), + Esql.brute_force_type = case( + Esql.o365_audit_UserId_lower_count_distinct >= 15 and Esql.o365_audit_LogonError_count_distinct == 1 and Esql.event_count >= 10 and Esql.event_duration_seconds <= 1800, "password_spraying", + Esql.o365_audit_UserId_lower_count_distinct >= 8 and Esql.event_count >= 15 and Esql.o365_audit_LogonError_count_distinct <= 3 and Esql.source_ip_count_distinct <= 5 and Esql.event_duration_seconds <= 600, "credential_stuffing", + Esql.o365_audit_UserId_lower_count_distinct == 1 and Esql.o365_audit_LogonError_count_distinct == 1 and Esql.event_count >= 20 and Esql.event_duration_seconds <= 300, "password_guessing", "other" ) -| KEEP +| keep Esql.time_window_date_trunc, Esql.o365_audit_UserId_lower_count_distinct, Esql_priv.o365_audit_UserId_lower_values, @@ -147,8 +147,7 @@ FROM logs-o365.audit-* Esql.event_duration_seconds, Esql.event_count, Esql.brute_force_type -| WHERE Esql.brute_force_type != "other" - +| where Esql.brute_force_type != "other" ''' diff --git a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml index 6e4d8e4b8f0..9c247608297 100644 --- a/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml +++ b/rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml @@ -68,41 +68,41 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-o365.audit-* -| WHERE - event.dataset == "o365.audit" AND - event.action == "UserLoggedIn" AND - source.ip IS NOT NULL AND - o365.audit.UserId IS NOT NULL AND - o365.audit.ApplicationId IS NOT NULL AND - o365.audit.UserType IN ("0", "2", "3", "10") AND - o365.audit.ApplicationId IN ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") AND - o365.audit.ObjectId IN ("00000003-0000-0000-c000-000000000000") -| EVAL - Esql.time_window_date_trunc = DATE_TRUNC(30 minutes, @timestamp), - Esql.oauth_authorize_user_id_case = CASE( - o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" AND o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", +from logs-o365.audit-* +| where + event.dataset == "o365.audit" and + event.action == "UserLoggedIn" and + source.ip is not null and + o365.audit.UserId is not null and + o365.audit.ApplicationId is not null and + o365.audit.UserType in ("0", "2", "3", "10") and + o365.audit.ApplicationId in ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") and + o365.audit.ObjectId in ("00000003-0000-0000-c000-000000000000") +| eval + Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp), + Esql.oauth_authorize_user_id_case = case( + o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" and o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", o365.audit.UserId, null ), - Esql.oauth_token_user_id_case = CASE( + Esql.oauth_token_user_id_case = case( o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", o365.audit.UserId, null ) -| STATS - Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip), - Esql.source_ip_values = VALUES(source.ip), - Esql.o365_audit_ApplicationId_values = VALUES(o365.audit.ApplicationId), - Esql.source_as_organization_name_values = VALUES(source.`as`.organization.name), - Esql.oauth_token_count_distinct = COUNT_DISTINCT(Esql.oauth_token_user_id_case), - Esql.oauth_authorize_count_distinct = COUNT_DISTINCT(Esql.oauth_authorize_user_id_case) - BY +| stats + Esql.source_ip_count_distinct = count_distinct(source.ip), + Esql.source_ip_values = values(source.ip), + Esql.o365_audit_ApplicationId_values = values(o365.audit.ApplicationId), + Esql.source_as_organization_name_values = values(source.`as`.organization.name), + Esql.oauth_token_count_distinct = count_distinct(Esql.oauth_token_user_id_case), + Esql.oauth_authorize_count_distinct = count_distinct(Esql.oauth_authorize_user_id_case) + by o365.audit.UserId, Esql.time_window_date_trunc, o365.audit.ApplicationId, o365.audit.ObjectId -| KEEP +| keep Esql.time_window_date_trunc, Esql.source_ip_values, Esql.source_ip_count_distinct, @@ -110,11 +110,10 @@ FROM logs-o365.audit-* Esql.source_as_organization_name_values, Esql.oauth_token_count_distinct, Esql.oauth_authorize_count_distinct -| WHERE - Esql.source_ip_count_distinct >= 2 AND - Esql.oauth_token_count_distinct > 0 AND +| where + Esql.source_ip_count_distinct >= 2 and + Esql.oauth_token_count_distinct > 0 and Esql.oauth_authorize_count_distinct > 0 - ''' diff --git a/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml b/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml index 709170122e1..080f35e67dd 100644 --- a/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml +++ b/rules/integrations/okta/credential_access_multiple_device_token_hashes_for_single_okta_session.toml @@ -77,32 +77,31 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-okta* -| WHERE - event.dataset == "okta.system" AND - NOT event.action IN ( +from logs-okta* +| where + event.dataset == "okta.system" and + not event.action in ( "policy.evaluate_sign_on", "user.session.start", "user.authentication.sso" - ) AND - okta.actor.alternate_id != "system@okta.com" AND - okta.actor.alternate_id RLIKE "[^@\s]+\@[^@\s]+" AND + ) and + okta.actor.alternate_id != "system@okta.com" and + okta.actor.alternate_id rlike "[^@\s]+\@[^@\s]+" and okta.authentication_context.external_session_id != "unknown" -| KEEP +| keep event.action, okta.actor.alternate_id, okta.authentication_context.external_session_id, okta.debug_context.debug_data.dt_hash -| STATS - Esql.okta_debug_context_debug_data_dt_hash_count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) - BY +| stats + Esql.okta_debug_context_debug_data_dt_hash_count_distinct = count_distinct(okta.debug_context.debug_data.dt_hash) + by okta.actor.alternate_id, okta.authentication_context.external_session_id -| WHERE +| where Esql.okta_debug_context_debug_data_dt_hash_count_distinct >= 2 -| SORT - Esql.okta_debug_context_debug_data_dt_hash_count_distinct DESC - +| sort + Esql.okta_debug_context_debug_data_dt_hash_count_distinct desc ''' diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml index 0b97ad8ec72..3dd856caa1b 100644 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml +++ b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_from_single_source.toml @@ -90,27 +90,26 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-okta* -| WHERE - event.dataset == "okta.system" AND - (event.action == "user.session.start" OR event.action RLIKE "user\.authentication(.*)") AND +from logs-okta* +| where + event.dataset == "okta.system" and + (event.action == "user.session.start" or event.action rlike "user\.authentication(.*)") and okta.outcome.reason == "INVALID_CREDENTIALS" -| KEEP +| keep okta.client.ip, okta.actor.alternate_id, okta.actor.id, event.action, okta.outcome.reason -| STATS - Esql.okta_actor_id_count_distinct = COUNT_DISTINCT(okta.actor.id) - BY +| stats + Esql.okta_actor_id_count_distinct = count_distinct(okta.actor.id) + by okta.client.ip, okta.actor.alternate_id -| WHERE +| where Esql.okta_actor_id_count_distinct > 5 -| SORT - Esql.okta_actor_id_count_distinct DESC - +| sort + Esql.okta_actor_id_count_distinct desc ''' diff --git a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml index 6ad517f0ae8..1bc4dfd5c00 100644 --- a/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml +++ b/rules/integrations/okta/credential_access_okta_authentication_for_multiple_users_with_the_same_device_token_hash.toml @@ -87,28 +87,27 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-okta* -| WHERE - event.dataset == "okta.system" AND - (event.action RLIKE "user\.authentication(.*)" OR event.action == "user.session.start") AND - okta.debug_context.debug_data.dt_hash != "-" AND +from logs-okta* +| where + event.dataset == "okta.system" and + (event.action rlike "user\.authentication(.*)" or event.action == "user.session.start") and + okta.debug_context.debug_data.dt_hash != "-" and okta.outcome.reason == "INVALID_CREDENTIALS" -| KEEP +| keep event.action, okta.debug_context.debug_data.dt_hash, okta.actor.id, okta.actor.alternate_id, okta.outcome.reason -| STATS - Esql.okta_actor_id_count_distinct = COUNT_DISTINCT(okta.actor.id) - BY +| stats + Esql.okta_actor_id_count_distinct = count_distinct(okta.actor.id) + by okta.debug_context.debug_data.dt_hash, okta.actor.alternate_id -| WHERE +| where Esql.okta_actor_id_count_distinct > 20 -| SORT - Esql.okta_actor_id_count_distinct DESC - +| sort + Esql.okta_actor_id_count_distinct desc ''' diff --git a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml index 0e2748307f7..3d1a722f10f 100644 --- a/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml +++ b/rules/integrations/okta/credential_access_okta_multiple_device_token_hashes_for_single_user.toml @@ -91,29 +91,28 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-okta* -| WHERE - event.dataset == "okta.system" AND - (event.action RLIKE "user\.authentication(.*)" OR event.action == "user.session.start") AND - okta.debug_context.debug_data.request_uri == "/api/v1/authn" AND +from logs-okta* +| where + event.dataset == "okta.system" and + (event.action rlike "user\.authentication(.*)" or event.action == "user.session.start") and + okta.debug_context.debug_data.request_uri == "/api/v1/authn" and okta.outcome.reason == "INVALID_CREDENTIALS" -| KEEP +| keep event.action, okta.debug_context.debug_data.dt_hash, okta.client.ip, okta.actor.alternate_id, okta.debug_context.debug_data.request_uri, okta.outcome.reason -| STATS - Esql.okta_debug_context_debug_data_dt_hash_count_distinct = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash) - BY +| stats + Esql.okta_debug_context_debug_data_dt_hash_count_distinct = count_distinct(okta.debug_context.debug_data.dt_hash) + by okta.client.ip, okta.actor.alternate_id -| WHERE +| where Esql.okta_debug_context_debug_data_dt_hash_count_distinct >= 30 -| SORT - Esql.okta_debug_context_debug_data_dt_hash_count_distinct DESC - +| sort + Esql.okta_debug_context_debug_data_dt_hash_count_distinct desc ''' diff --git a/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml b/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml index 85d346ce886..c871fd86c0d 100644 --- a/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml +++ b/rules/integrations/okta/initial_access_okta_user_sessions_started_from_different_geolocations.toml @@ -16,8 +16,7 @@ interval = "15m" language = "esql" license = "Elastic License v2" name = "Okta User Sessions Started from Different Geolocations" -note = """ -## Triage and analysis +note = """## Triage and analysis ### Investigating Okta User Sessions Started from Different Geolocations @@ -78,28 +77,27 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-okta* -| WHERE - event.dataset == "okta.system" AND - (event.action RLIKE "user\.authentication(.*)" OR event.action == "user.session.start") AND - okta.security_context.is_proxy != true AND - okta.actor.id != "unknown" AND +from logs-okta* +| where + event.dataset == "okta.system" and + (event.action rlike "user\.authentication(.*)" or event.action == "user.session.start") and + okta.security_context.is_proxy != true and + okta.actor.id != "unknown" and event.outcome == "success" -| KEEP +| keep event.action, okta.security_context.is_proxy, okta.actor.id, okta.actor.alternate_id, event.outcome, client.geo.country_name -| STATS - Esql.client_geo_country_name_count_distinct = COUNT_DISTINCT(client.geo.country_name) - BY okta.actor.id, okta.actor.alternate_id -| WHERE +| stats + Esql.client_geo_country_name_count_distinct = count_distinct(client.geo.country_name) + by okta.actor.id, okta.actor.alternate_id +| where Esql.client_geo_country_name_count_distinct >= 2 -| SORT - Esql.client_geo_country_name_count_distinct DESC - +| sort + Esql.client_geo_country_name_count_distinct desc ''' diff --git a/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml b/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml index d4e5f878a01..18e98e3d18f 100644 --- a/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml +++ b/rules/linux/command_and_control_frequent_egress_netcon_from_sus_executable.toml @@ -93,32 +93,32 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.network-* -| WHERE - @timestamp > NOW() - 1h AND - host.os.type == "linux" AND - event.type == "start" AND - event.action == "connection_attempted" AND +from logs-endpoint.events.network-* +| where + @timestamp > now() - 1h and + host.os.type == "linux" and + event.type == "start" and + event.action == "connection_attempted" and ( - process.executable LIKE "/tmp/*" OR - process.executable LIKE "/var/tmp/*" OR - process.executable LIKE "/dev/shm/*" OR - process.name RLIKE ".*" - ) AND NOT ( - CIDR_MATCH(destination.ip, + process.executable like "/tmp/*" or + process.executable like "/var/tmp/*" or + process.executable like "/dev/shm/*" or + process.name rlike ".*" + ) and not ( + cidr_match(destination.ip, "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.0.29/32", "192.0.0.8/32", "192.0.0.9/32", "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1", "FE80::/10", "FF00::/8" - ) OR - process.executable LIKE "/nix/store/*" OR - process.executable LIKE "/tmp/newroot/*" OR - process.executable LIKE "/tmp/.mount*" OR - process.executable LIKE "/tmp/go-build*" + ) or + process.executable like "/nix/store/*" or + process.executable like "/tmp/newroot/*" or + process.executable like "/tmp/.mount*" or + process.executable like "/tmp/go-build*" ) -| KEEP +| keep @timestamp, host.os.type, event.type, @@ -128,18 +128,17 @@ FROM logs-endpoint.events.network-* destination.ip, agent.id, host.name -| STATS - Esql.event_count = COUNT(), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.executable -| WHERE - Esql.agent_id_count_distinct == 1 AND +| stats + Esql.event_count = count(), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.executable +| where + Esql.agent_id_count_distinct == 1 and Esql.event_count > 15 -| SORT Esql.event_count ASC -| LIMIT 100 - +| sort Esql.event_count asc +| limit 100 ''' diff --git a/rules/linux/defense_evasion_base64_decoding_activity.toml b/rules/linux/defense_evasion_base64_decoding_activity.toml index 94ff7b72a13..ffd98181da9 100644 --- a/rules/linux/defense_evasion_base64_decoding_activity.toml +++ b/rules/linux/defense_evasion_base64_decoding_activity.toml @@ -94,45 +94,45 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.process-* -| WHERE - @timestamp > NOW() - 1h AND - host.os.type == "linux" AND - event.type == "start" AND - event.action == "exec" AND ( +from logs-endpoint.events.process-* +| where + @timestamp > now() - 1h and + host.os.type == "linux" and + event.type == "start" and + event.action == "exec" and ( ( - process.name IN ("base64", "base64plain", "base64url", "base64mime", "base64pem", "base32", "base16") AND - process.command_line LIKE "*-*d*" - ) OR + process.name in ("base64", "base64plain", "base64url", "base64mime", "base64pem", "base32", "base16") and + process.command_line like "*-*d*" + ) or ( - process.name == "openssl" AND - process.args == "enc" AND - process.args IN ("-d", "-base64", "-a") - ) OR + process.name == "openssl" and + process.args == "enc" and + process.args in ("-d", "-base64", "-a") + ) or ( - process.name LIKE "python*" AND ( + process.name like "python*" and ( ( - process.args == "base64" AND - process.args IN ("-d", "-u", "-t") - ) OR + process.args == "base64" and + process.args in ("-d", "-u", "-t") + ) or ( - process.args == "-c" AND - process.command_line LIKE "*base64*" AND - process.command_line LIKE "*b64decode*" + process.args == "-c" and + process.command_line like "*base64*" and + process.command_line like "*b64decode*" ) ) - ) OR + ) or ( - process.name LIKE "perl*" AND - process.command_line LIKE "*decode_base64*" - ) OR + process.name like "perl*" and + process.command_line like "*decode_base64*" + ) or ( - process.name LIKE "ruby*" AND - process.args == "-e" AND - process.command_line LIKE "*Base64.decode64*" + process.name like "ruby*" and + process.args == "-e" and + process.command_line like "*Base64.decode64*" ) ) -| KEEP +| keep @timestamp, host.os.type, event.type, @@ -142,18 +142,17 @@ FROM logs-endpoint.events.process-* process.command_line, agent.id, host.name -| STATS - Esql.event_count = COUNT(), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.name, process.command_line -| WHERE - Esql.agent_id_count_distinct == 1 AND +| stats + Esql.event_count = count(), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.name, process.command_line +| where + Esql.agent_id_count_distinct == 1 and Esql.event_count < 15 -| SORT Esql.event_count ASC -| LIMIT 100 - +| sort Esql.event_count asc +| limit 100 ''' diff --git a/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml index 790a6b5fb14..e9867a5acb4 100644 --- a/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_port_scanning_activity_from_compromised_host.toml @@ -95,13 +95,13 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.network-* -| WHERE - @timestamp > NOW() - 1h AND - host.os.type == "linux" AND - event.type == "start" AND +from logs-endpoint.events.network-* +| where + @timestamp > now() - 1h and + host.os.type == "linux" and + event.type == "start" and event.action == "connection_attempted" -| KEEP +| keep @timestamp, host.os.type, event.type, @@ -111,19 +111,18 @@ FROM logs-endpoint.events.network-* destination.ip, agent.id, host.name -| STATS - Esql.event_count = COUNT(), - Esql.destination_port_count_distinct = COUNT_DISTINCT(destination.port), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.executable, destination.ip -| WHERE - Esql.agent_id_count_distinct == 1 AND +| stats + Esql.event_count = count(), + Esql.destination_port_count_distinct = count_distinct(destination.port), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.executable, destination.ip +| where + Esql.agent_id_count_distinct == 1 and Esql.destination_port_count_distinct > 100 -| SORT Esql.event_count ASC -| LIMIT 100 - +| sort Esql.event_count asc +| limit 100 ''' diff --git a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml index 0e8141f6b8f..ae377495dcc 100644 --- a/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml +++ b/rules/linux/discovery_subnet_scanning_activity_from_compromised_host.toml @@ -94,26 +94,25 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.network-* -| KEEP @timestamp, host.os.type, event.type, event.action, process.executable, destination.ip, agent.id, host.name -| WHERE - @timestamp > NOW() - 1 HOURS AND - host.os.type == "linux" AND - event.type == "start" AND +from logs-endpoint.events.network-* +| keep @timestamp, host.os.type, event.type, event.action, process.executable, destination.ip, agent.id, host.name +| where + @timestamp > now() - 1 hours and + host.os.type == "linux" and + event.type == "start" and event.action == "connection_attempted" -| STATS - Esql.event_count = COUNT(), - Esql.destination_ip_count_distinct = COUNT_DISTINCT(destination.ip), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.executable -| WHERE - Esql.agent_id_count_distinct == 1 AND +| stats + Esql.event_count = count(), + Esql.destination_ip_count_distinct = count_distinct(destination.ip), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.executable +| where + Esql.agent_id_count_distinct == 1 and Esql.destination_ip_count_distinct > 250 -| SORT Esql.event_count ASC -| LIMIT 100 - +| sort Esql.event_count asc +| limit 100 ''' diff --git a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml index 8670f17af01..4fdf884f8a2 100644 --- a/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml +++ b/rules/linux/exfiltration_unusual_file_transfer_utility_launched.toml @@ -93,26 +93,25 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.process-* -| KEEP @timestamp, host.os.type, event.type, event.action, process.name, process.executable, process.parent.executable, process.command_line, agent.id, host.name -| WHERE - @timestamp > NOW() - 1 HOURS AND - host.os.type == "linux" AND - event.type == "start" AND - event.action == "exec" AND - process.name IN ("scp", "ftp", "sftp", "vsftpd", "sftp-server", "rsync") -| STATS - Esql.event_count = COUNT(), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.executable, process.parent.executable, process.command_line -| WHERE - Esql.agent_id_count_distinct == 1 AND +from logs-endpoint.events.process-* +| keep @timestamp, host.os.type, event.type, event.action, process.name, process.executable, process.parent.executable, process.command_line, agent.id, host.name +| where + @timestamp > now() - 1 hours and + host.os.type == "linux" and + event.type == "start" and + event.action == "exec" and + process.name in ("scp", "ftp", "sftp", "vsftpd", "sftp-server", "rsync") +| stats + Esql.event_count = count(), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.executable, process.parent.executable, process.command_line +| where + Esql.agent_id_count_distinct == 1 and Esql.event_count < 5 -| SORT Esql.event_count ASC -| LIMIT 100 - +| sort Esql.event_count asc +| limit 100 ''' diff --git a/rules/linux/impact_potential_bruteforce_malware_infection.toml b/rules/linux/impact_potential_bruteforce_malware_infection.toml index 8dbd627f076..71b42747d35 100644 --- a/rules/linux/impact_potential_bruteforce_malware_infection.toml +++ b/rules/linux/impact_potential_bruteforce_malware_infection.toml @@ -97,15 +97,15 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.network-* -| KEEP @timestamp, host.os.type, event.type, event.action, destination.port, process.executable, destination.ip, agent.id, host.name -| WHERE - @timestamp > NOW() - 1 HOURS AND - host.os.type == "linux" AND - event.type == "start" AND - event.action == "connection_attempted" AND - destination.port IN (22, 222, 2222, 10022, 2022, 2200, 62612, 8022) AND - NOT CIDR_MATCH( +from logs-endpoint.events.network-* +| keep @timestamp, host.os.type, event.type, event.action, destination.port, process.executable, destination.ip, agent.id, host.name +| where + @timestamp > now() - 1 hours and + host.os.type == "linux" and + event.type == "start" and + event.action == "connection_attempted" and + destination.port in (22, 222, 2222, 10022, 2022, 2200, 62612, 8022) and + not cidr_match( destination.ip, "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32", @@ -114,18 +114,17 @@ FROM logs-endpoint.events.network-* "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1", "FE80::/10", "FF00::/8" ) -| STATS - Esql.event_count = COUNT(), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.executable, destination.port -| WHERE - Esql.agent_id_count_distinct == 1 AND +| stats + Esql.event_count = count(), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.executable, destination.port +| where + Esql.agent_id_count_distinct == 1 and Esql.event_count > 15 -| SORT Esql.event_count ASC -| LIMIT 100 - +| sort Esql.event_count asc +| limit 100 ''' diff --git a/rules/linux/persistence_web_server_sus_child_spawned.toml b/rules/linux/persistence_web_server_sus_child_spawned.toml index 60570584f44..bf3b75fb4ab 100644 --- a/rules/linux/persistence_web_server_sus_child_spawned.toml +++ b/rules/linux/persistence_web_server_sus_child_spawned.toml @@ -96,8 +96,8 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.process-* -| KEEP +from logs-endpoint.events.process-* +| keep @timestamp, host.os.type, event.type, @@ -112,42 +112,41 @@ FROM logs-endpoint.events.process-* process.parent.executable, agent.id, host.name -| WHERE - @timestamp > NOW() - 1 HOURS AND - host.os.type == "linux" AND - event.type == "start" AND - event.action == "exec" AND ( - process.parent.name IN ( +| where + @timestamp > now() - 1 hours and + host.os.type == "linux" and + event.type == "start" and + event.action == "exec" and ( + process.parent.name in ( "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" - ) OR - process.parent.name LIKE "php-*" OR - process.parent.name LIKE "python*" OR - process.parent.name LIKE "ruby*" OR - process.parent.name LIKE "perl*" OR - user.name IN ( + ) or + process.parent.name like "php-*" or + process.parent.name like "python*" or + process.parent.name like "ruby*" or + process.parent.name like "perl*" or + user.name in ( "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" - ) OR - user.id IN ("99", "33", "498", "48") OR - process.working_directory LIKE "/var/www/*" - ) AND NOT ( - process.working_directory LIKE "/home/*" OR - process.working_directory == "/" OR - process.parent.executable LIKE "/vscode/vscode-server/*" + ) or + user.id in ("99", "33", "498", "48") or + process.working_directory like "/var/www/*" + ) and not ( + process.working_directory like "/home/*" or + process.working_directory == "/" or + process.parent.executable like "/vscode/vscode-server/*" ) -| STATS - Esql.event_count = COUNT(), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.executable, process.working_directory, process.parent.executable -| WHERE - Esql.agent_id_count_distinct == 1 AND +| stats + Esql.event_count = count(), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.executable, process.working_directory, process.parent.executable +| where + Esql.agent_id_count_distinct == 1 and Esql.event_count < 5 -| SORT Esql.event_count ASC -| LIMIT 100 - +| sort Esql.event_count asc +| limit 100 ''' diff --git a/rules/linux/persistence_web_server_sus_command_execution.toml b/rules/linux/persistence_web_server_sus_command_execution.toml index 2c9f4692689..e75c582d1d6 100644 --- a/rules/linux/persistence_web_server_sus_command_execution.toml +++ b/rules/linux/persistence_web_server_sus_command_execution.toml @@ -103,8 +103,8 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.process-* -| KEEP +from logs-endpoint.events.process-* +| keep @timestamp, host.os.type, event.type, @@ -118,46 +118,45 @@ FROM logs-endpoint.events.process-* process.parent.executable, agent.id, host.name -| WHERE - @timestamp > NOW() - 1 HOURS AND - host.os.type == "linux" AND - event.type == "start" AND - event.action == "exec" AND ( - process.parent.name IN ( +| where + @timestamp > now() - 1 hours and + host.os.type == "linux" and + event.type == "start" and + event.action == "exec" and ( + process.parent.name in ( "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" - ) OR - process.parent.name LIKE "php-*" OR - process.parent.name LIKE "python*" OR - process.parent.name LIKE "ruby*" OR - process.parent.name LIKE "perl*" OR - user.name IN ( + ) or + process.parent.name like "php-*" or + process.parent.name like "python*" or + process.parent.name like "ruby*" or + process.parent.name like "perl*" or + user.name in ( "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" - ) OR - user.id IN ("99", "33", "498", "48") OR - process.working_directory LIKE "/var/www/*" - ) AND - process.name IN ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") AND - process.command_line LIKE "* -c *" AND NOT ( - process.working_directory LIKE "/home/*" OR - process.working_directory == "/" OR - process.working_directory LIKE "/vscode/vscode-server/*" OR - process.parent.executable LIKE "/vscode/vscode-server/*" OR + ) or + user.id in ("99", "33", "498", "48") or + process.working_directory like "/var/www/*" + ) and + process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and + process.command_line like "* -c *" and not ( + process.working_directory like "/home/*" or + process.working_directory == "/" or + process.working_directory like "/vscode/vscode-server/*" or + process.parent.executable like "/vscode/vscode-server/*" or process.parent.executable == "/usr/bin/xfce4-terminal" ) -| STATS - Esql.event_count = COUNT(), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.command_line, process.working_directory, process.parent.executable -| WHERE - Esql.agent_id_count_distinct == 1 AND +| stats + Esql.event_count = count(), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.command_line, process.working_directory, process.parent.executable +| where + Esql.agent_id_count_distinct == 1 and Esql.event_count < 5 -| SORT Esql.event_count ASC -| LIMIT 100 - +| sort Esql.event_count asc +| limit 100 ''' diff --git a/rules/windows/credential_access_rare_webdav_destination.toml b/rules/windows/credential_access_rare_webdav_destination.toml index 5966773bc8c..072af969828 100644 --- a/rules/windows/credential_access_rare_webdav_destination.toml +++ b/rules/windows/credential_access_rare_webdav_destination.toml @@ -54,31 +54,31 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-* -| WHERE - @timestamp > NOW() - 8 HOURS AND - event.category == "process" AND - event.type == "start" AND - process.name == "rundll32.exe" AND - process.command_line LIKE "*DavSetCookie*" -| KEEP host.id, process.command_line, user.name -| GROK +from logs-* +| where + @timestamp > now() - 8 hours and + event.category == "process" and + event.type == "start" and + process.name == "rundll32.exe" and + process.command_line like "*DavSetCookie*" +| keep host.id, process.command_line, user.name +| grok process.command_line """(?DavSetCookie .* http)""" -| EVAL - Esql.server_webdav_cookie_replace = REPLACE(Esql.server_webdav_cookie, "(DavSetCookie | http)", "") -| WHERE - Esql.server_webdav_cookie_replace IS NOT NULL AND - Esql.server_webdav_cookie_replace RLIKE """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" AND - NOT Esql.server_webdav_cookie_replace IN ("www.google.com@SSL", "www.elastic.co@SSL") AND - NOT Esql.server_webdav_cookie_replace RLIKE """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" -| STATS - Esql.event_count = COUNT(*), - Esql.host_id_count_distinct = COUNT_DISTINCT(host.id), - Esql.host_id_values = VALUES(host.id), - Esql.user_name_values = VALUES(user.name) - BY Esql.server_webdav_cookie_replace -| WHERE - Esql.host_id_count_distinct == 1 AND +| eval + Esql.server_webdav_cookie_replace = replace(Esql.server_webdav_cookie, "(DavSetCookie | http)", "") +| where + Esql.server_webdav_cookie_replace is not null and + Esql.server_webdav_cookie_replace rlike """(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,3}(@SSL.*)*|(\d{1,3}\.){3}\d{1,3})""" and + not Esql.server_webdav_cookie_replace in ("www.google.com@SSL", "www.elastic.co@SSL") and + not Esql.server_webdav_cookie_replace rlike """(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.(\d{1,3}\.)\d{1,3}|192\.168\.(\d{1,3}\.)\d{1,3})""" +| stats + Esql.event_count = count(*), + Esql.host_id_count_distinct = count_distinct(host.id), + Esql.host_id_values = values(host.id), + Esql.user_name_values = values(user.name) + by Esql.server_webdav_cookie_replace +| where + Esql.host_id_count_distinct == 1 and Esql.event_count <= 3 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml index 83e61322569..d60c4655cec 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml @@ -90,20 +90,20 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index -| WHERE - event.code == "4104" AND - powershell.file.script_block_text LIKE "*`*" +from logs-windows.powershell_operational* metadata _id, _version, _index +| where + event.code == "4104" and + powershell.file.script_block_text like "*`*" -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql.script_block_text_replace = REPLACE(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") +| eval Esql.script_block_text_replace = replace(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.script_block_text_character_count = LENGTH(Esql.script_block_text_replace) - LENGTH(REPLACE(Esql.script_block_text_replace, "🔥", "")) +// count how many patterns were detected by calculating the number of 🔥 characters inserted +| eval Esql.script_block_text_character_count = length(Esql.script_block_text_replace) - length(replace(Esql.script_block_text_replace, "🔥", "")) -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id +| keep Esql.script_block_text_character_count, Esql.script_block_text_replace, powershell.file.script_block_text, @@ -118,15 +118,16 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id, user.id -| WHERE Esql.script_block_text_character_count >= 10 +| where Esql.script_block_text_character_count >= 10 -// Filter FPs, and due to the behavior of the LIKE operator, allow null values -| WHERE (file.name NOT LIKE "TSS_*.psm1" OR file.name IS NULL) +// Filter FPs, and due to the behavior of the like operator, allow null values +| where (file.name not like "TSS_*.psm1" or file.name is null) // VSCode Shell integration -| WHERE NOT powershell.file.script_block_text LIKE "*$([char]0x1b)]633*" +| where not powershell.file.script_block_text like "*$([char]0x1b)]633*" ''' + [[rule.threat]] framework = "MITRE ATT&CK" [[rule.threat.technique]] diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index 0a1a489ab31..7bec9a91bd6 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -84,22 +84,22 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index -| WHERE event.code == "4104" +from logs-windows.powershell_operational* metadata _id, _version, _index +| where event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL Esql.script_block_text_length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.script_block_text_length > 500 +| eval Esql.script_block_text_length = length(powershell.file.script_block_text) +| where Esql.script_block_text_length > 500 -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") +| eval Esql_priv.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count how many patterns were detected by calculating the number of 🔥 characters inserted +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id +| keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_replace_length, Esql_priv.powershell_file_script_block_text_replace, @@ -115,7 +115,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id, user.id -| WHERE Esql.powershell_file_script_block_text_character_count >= 1 +| where Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml index 0020f79389e..9436a733ee2 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml @@ -84,27 +84,27 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index +from logs-windows.powershell_operational* metadata _id, _version, _index // Filter for Event ID 4104 indicating script block logging -| WHERE event.code == "4104" +| where event.code == "4104" // Filter for scripts that contain the "char" keyword using MATCH, boosts the query performance -| WHERE powershell.file.script_block_text : "char" +| where powershell.file.script_block_text : "char" -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +| eval Esql_priv.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """(char\[\]\]\(\d+,\d+[^)]+|(\s?\(\[char\]\d+\s?\)\+){2,})""", "🔥" ) -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count how many patterns were detected by calculating the number of 🔥 characters inserted +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id +| keep Esql.powershell_file_script_block_text_character_count, Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, @@ -119,7 +119,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Final filter on match count -| WHERE Esql.powershell_file_script_block_text_character_count >= 1 +| where Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml index 2ca93ddaf08..6471389c50e 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml @@ -83,22 +83,22 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index -| WHERE event.code == "4104" AND powershell.file.script_block_text LIKE "*+*" +from logs-windows.powershell_operational* metadata _id, _version, _index +| where event.code == "4104" and powershell.file.script_block_text like "*+*" -// Replace suspicious method string constructions with 🔥 for entropy-style detection +// replace suspicious method string constructions with 🔥 for entropy-style detection // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +| eval Esql_priv.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """[.&]\(\s*(['"][A-Za-z0-9.-]+['"]\s*\+\s*)+['"][A-Za-z0-9.-]+['"]\s*\)""", "🔥" ) -// Count how many patterns were detected based on 🔥 characters -| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count how many patterns were detected based on 🔥 characters +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep relevant context fields for triage -| KEEP +// keep relevant context fields for triage +| keep Esql.powershell_file_script_block_text_character_count, Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, @@ -113,7 +113,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Alert if suspicious pattern is present -| WHERE Esql.powershell_file_script_block_text_character_count >= 1 +| where Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml index 3b748eb9f43..69b0d7117c6 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml @@ -83,22 +83,22 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index -| WHERE event.code == "4104" +from logs-windows.powershell_operational* metadata _id, _version, _index +| where event.code == "4104" // Measure script length -| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell_file_script_block_text_length > 1000 +| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) +| where Esql.powershell_file_script_block_text_length > 1000 -// Replace digits with 🔥 for numeric pattern density analysis -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE(powershell.file.script_block_text, """[0-9]""", "🔥") +// replace digits with 🔥 for numeric pattern density analysis +| eval Esql_priv.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """[0-9]""", "🔥") -// Count numeric characters and calculate proportion of total -| EVAL Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -| EVAL Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double +// count numeric characters and calculate proportion of total +| eval Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double // Retain relevant fields for investigation -| KEEP +| keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_character_ratio, Esql.powershell_file_script_block_text_length, @@ -115,10 +115,10 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for suspiciously high numeric content -| WHERE Esql.powershell_file_script_block_text_character_ratio > 0.30 +| where Esql.powershell_file_script_block_text_character_ratio > 0.30 // Exclude noisy patterns such as 64-character hashes -| WHERE NOT powershell.file.script_block_text RLIKE """.*\"[a-fA-F0-9]{64}\"\,.*""" +| where not powershell.file.script_block_text rlike """.*\"[a-fA-F0-9]{64}\"\,.*""" ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml index f1f5f6f38d6..b3fce96aad8 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml @@ -83,25 +83,25 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index -| WHERE event.code == "4104" +from logs-windows.powershell_operational* metadata _id, _version, _index +| where event.code == "4104" // Evaluate the length of the script block -| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell_file_script_block_text_length > 500 +| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) +| where Esql.powershell_file_script_block_text_length > 500 -// Replace obfuscated dynamic string construction with 🔥 to count obfuscation attempts -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +// replace obfuscated dynamic string construction with 🔥 to count obfuscation attempts +| eval Esql_priv.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """(?i)(\$(?:\w+|\w+\:\w+)\[\d++\]\+\$(?:\w+|\w+\:\w+)\[\d++\]\+['"]x['"]|\$(?:\w+\:\w+)\[\d++,\d++,\d++\]|\.name\[\d++,\d++,\d++\])""", "🔥" ) -// Count how many 🔥 were inserted -| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count how many 🔥 were inserted +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep relevant context fields -| KEEP +// keep relevant context fields +| keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, Esql_priv.powershell_file_script_block_text_replace, @@ -117,7 +117,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for meaningful pattern matches -| WHERE Esql.powershell_file_script_block_text_character_count >= 1 +| where Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml index 5d9ef7a2c7b..6c513a9bd59 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml @@ -84,25 +84,25 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index -| WHERE event.code == "4104" +from logs-windows.powershell_operational* metadata _id, _version, _index +| where event.code == "4104" // Check script length > 500 -| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell_file_script_block_text_length > 500 +| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) +| where Esql.powershell_file_script_block_text_length > 500 -// Replace method access patterns with 🔥 for detection -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +// replace method access patterns with 🔥 for detection +| eval Esql_priv.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, - """(?i)['"]['"].(Insert|Normalize|Chars|SubString|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", + """(?i)['"]['"].(Insert|Normalize|Chars|substring|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", "🔥" ) -// Count 🔥 instances to determine presence of suspicious method usage -| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count 🔥 instances to determine presence of suspicious method usage +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep relevant fields for context and triage -| KEEP +// keep relevant fields for context and triage +| keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, Esql_priv.powershell_file_script_block_text_replace, @@ -118,7 +118,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Only return if at least one pattern is found -| WHERE Esql.powershell_file_script_block_text_character_count >= 1 +| where Esql.powershell_file_script_block_text_character_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml index 997f142d3a8..689930cd9cc 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml @@ -85,28 +85,28 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index +from logs-windows.powershell_operational* metadata _id, _version, _index // Filter for PowerShell Event ID 4104 -| WHERE event.code == "4104" +| where event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell_file_script_block_text_length > 500 +| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) +| where Esql.powershell_file_script_block_text_length > 500 -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +| eval Esql_priv.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """\$\w+\[\-\s?1\.\.""", "🔥" ) -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count how many patterns were detected by calculating the number of 🔥 characters inserted +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id +| keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, Esql_priv.powershell_file_script_block_text_replace, @@ -122,11 +122,10 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for patterns found at least once -| WHERE Esql.powershell_file_script_block_text_character_count >= 1 +| where Esql.powershell_file_script_block_text_character_count >= 1 // FP Patterns -| WHERE NOT powershell.file.script_block_text LIKE "*GENESIS-5654*" - +| where not powershell.file.script_block_text like "*GENESIS-5654*" ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml index eb4d19d5532..31f796b2e5d 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml @@ -82,28 +82,28 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index +from logs-windows.powershell_operational* metadata _id, _version, _index // Filter for PowerShell Event ID 4104 -| WHERE event.code == "4104" +| where event.code == "4104" // Filter for scripts that contains these keywords using MATCH, boosts the query performance, // match will ignore the | and look for the individual words -| WHERE powershell.file.script_block_text : "rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman|vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod" +| where powershell.file.script_block_text : "rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman|vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod" -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +| eval Esql_priv.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """(?i)(rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman\.|:vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod)""", "🔥" ) -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count how many patterns were detected by calculating the number of 🔥 characters inserted +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id +| keep Esql.powershell_file_script_block_text_character_count, Esql_priv.powershell_file_script_block_text_replace, powershell.file.script_block_text, @@ -116,7 +116,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index agent.id // Filter for scripts with at least 2 suspicious keyword matches -| WHERE Esql.powershell_file_script_block_text_character_count >= 2 +| where Esql.powershell_file_script_block_text_character_count >= 2 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml index 7400d0d97c5..756449ff73a 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml @@ -83,28 +83,28 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index +from logs-windows.powershell_operational* metadata _id, _version, _index // Filter for PowerShell Event ID 4104 -| WHERE event.code == "4104" +| where event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell_file_script_block_text_length > 500 +| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) +| where Esql.powershell_file_script_block_text_length > 500 -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +| eval Esql_priv.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """['"][A-Za-z0-9.]+['"](\s?\+\s?['"][A-Za-z0-9.,\-\s]+['"]){2,}""", "🔥" ) -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell_file_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count how many patterns were detected by calculating the number of 🔥 characters inserted +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id +| keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, Esql_priv.powershell_file_script_block_text_replace, @@ -120,7 +120,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with at least 2 suspicious string concatenations -| WHERE Esql.powershell_file_script_block_text_character_count >= 2 +| where Esql.powershell_file_script_block_text_character_count >= 2 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index c7c78d48317..5b6515d4836 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -82,29 +82,29 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index +from logs-windows.powershell_operational* metadata _id, _version, _index // Filter for PowerShell Event ID 4104 -| WHERE event.code == "4104" +| where event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) -| WHERE Esql.powershell_file_script_block_text_length > 500 -| WHERE powershell.file.script_block_text LIKE "*{0}*" +| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) +| where Esql.powershell_file_script_block_text_length > 500 +| where powershell.file.script_block_text like "*{0}*" -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +| eval Esql_priv.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """((\{\d+\}){2,}["']\s?-f|::Format[^\{]+(\{\d+\}){2,})""", "🔥" ) -// Count how many patterns were detected by calculating the number of 🔥 characters inserted -| EVAL Esql.powershell_script_block_text_character_count = LENGTH(Esql_priv.powershell_file_script_block_text_replace) - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +// count how many patterns were detected by calculating the number of 🔥 characters inserted +| eval Esql.powershell_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id +| keep Esql.powershell_script_block_text_character_count, Esql.powershell_file_script_block_text_length, Esql_priv.powershell_file_script_block_text_replace, @@ -121,21 +121,21 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with more than 3 suspicious format patterns -| WHERE Esql.powershell_script_block_text_character_count > 3 +| where Esql.powershell_script_block_text_character_count > 3 // Exclude Noisy Patterns // Icinga Framework -| WHERE (file.name NOT LIKE "framework_cache.psm1" OR file.name IS NULL) +| where (file.name not like "framework_cache.psm1" or file.name is null) -| WHERE NOT +| where not // https://wtfbins.wtf/17 ( - (powershell.file.script_block_text LIKE "*sentinelbreakpoints*" OR - powershell.file.script_block_text LIKE "*:::::\\\\windows\\\\sentinel*") - AND - (powershell.file.script_block_text LIKE "*$local:Bypassed*" OR - powershell.file.script_block_text LIKE "*origPSExecutionPolicyPreference*") + (powershell.file.script_block_text like "*sentinelbreakpoints*" or + powershell.file.script_block_text like "*:::::\\\\windows\\\\sentinel*") + and + (powershell.file.script_block_text like "*$local:Bypassed*" or + powershell.file.script_block_text like "*origPSExecutionPolicyPreference*") ) ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml index a56f4989802..86593013b5b 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml @@ -83,31 +83,31 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index +from logs-windows.powershell_operational* metadata _id, _version, _index // Filter for PowerShell Event ID 4104 -| WHERE event.code == "4104" +| where event.code == "4104" -// Replace repeated spaces used for formatting after a new line with a single space to reduce FPs -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE(powershell.file.script_block_text, """\n\s+""", "\n ") +// replace repeated spaces used for formatting after a new line with a single space to reduce FPs +| eval Esql_priv.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """\n\s+""", "\n ") // Look for scripts with more than 1000 chars -| EVAL Esql.powershell_file_script_block_text_replace_length = LENGTH(Esql_priv.powershell_file_script_block_text_replace) -| WHERE Esql.powershell_file_script_block_text_replace_length > 1000 +| eval Esql.powershell_file_script_block_text_replace_length = length(Esql_priv.powershell_file_script_block_text_replace) +| where Esql.powershell_file_script_block_text_replace_length > 1000 -// Replace format characters with 🔥 to count suspicious formatting density -| EVAL Esql_priv.powershell_file_script_block_text_replace = REPLACE( +// replace format characters with 🔥 to count suspicious formatting density +| eval Esql_priv.powershell_file_script_block_text_replace = replace( Esql_priv.powershell_file_script_block_text_replace, """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\.]""", "🔥" ) -// Count 🔥 and calculate its proportion of the script -| EVAL Esql.powershell_file_script_block_text_count = Esql.powershell_file_script_block_text_length - LENGTH(REPLACE(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) -| EVAL Esql.powershell_file_script_block_text_ratio = Esql.powershell_file_script_block_text_count::double / Esql.powershell_file_script_block_text_length::double +// count 🔥 and calculate its proportion of the script +| eval Esql.powershell_file_script_block_text_count = Esql.powershell_file_script_block_text_length - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_ratio = Esql.powershell_file_script_block_text_count::double / Esql.powershell_file_script_block_text_length::double // Retain fields for context or alert generation -| KEEP +| keep Esql.powershell_file_script_block_text_count, Esql.powershell_file_script_block_text_length, Esql.powershell_file_script_block_text_ratio, @@ -125,7 +125,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for high-ratio suspicious formatting scripts -| WHERE Esql.powershell_file_script_block_text_ratio > 0.75 +| where Esql.powershell_file_script_block_text_ratio > 0.75 ''' diff --git a/rules/windows/execution_posh_malicious_script_agg.toml b/rules/windows/execution_posh_malicious_script_agg.toml index f2fd5270e48..dba1df48aad 100644 --- a/rules/windows/execution_posh_malicious_script_agg.toml +++ b/rules/windows/execution_posh_malicious_script_agg.toml @@ -28,11 +28,10 @@ services.path FROM services JOIN authenticode ON services.path = authenticode.pa authenticode.path JOIN hash ON services.path = hash.path WHERE authenticode.result != 'trusted' """ + [rule] author = ["Elastic"] -description = """ -Identifies PowerShell script blocks associated with multiple distinct detections, indicating likely malicious behavior. -""" +description = "Identifies PowerShell script blocks associated with multiple distinct detections, indicating likely malicious behavior.\n" from = "now-9m" language = "esql" license = "Elastic License v2" @@ -101,33 +100,33 @@ tags = [ "Use Case: Threat Detection", "Tactic: Execution", "Rule Type: Higher-Order Rule", - "Resources: Investigation Guide" + "Resources: Investigation Guide", ] timestamp_override = "event.ingested" type = "esql" query = ''' -FROM .alerts-security.* METADATA _id +from .alerts-security.* metadata _id // Filter for PowerShell related alerts -| WHERE kibana.alert.rule.name LIKE "*PowerShell*" +| where kibana.alert.rule.name like "*PowerShell*" -// As alerts don't have non-ECS fields, parse the script block ID using GROK -| GROK message "ScriptBlock ID: (?.+)" -| WHERE Esql.message_powershell_file_script_block_id IS NOT NULL +// as alerts don't have non-ECS fields, parse the script block ID using grok +| grok message "ScriptBlock ID: (?.+)" +| where Esql.message_powershell_file_script_block_id is not null -// Keep relevant fields for further processing -| KEEP kibana.alert.rule.name, Esql.message_powershell_file_script_block_id, _id +// keep relevant fields for further processing +| keep kibana.alert.rule.name, Esql.message_powershell_file_script_block_id, _id -// Count distinct alerts and filter for matches above the threshold -| STATS - Esql.kibana_alert_rule_name_count_distinct = COUNT_DISTINCT(kibana.alert.rule.name), - Esql.kibana_alert_rule_name_values = VALUES(kibana.alert.rule.name), - Esql._id_values = VALUES(_id) - BY Esql.message_powershell_file_script_block_id +// count distinct alerts and filter for matches above the threshold +| stats + Esql.kibana_alert_rule_name_count_distinct = count_distinct(kibana.alert.rule.name), + Esql.kibana_alert_rule_name_values = values(kibana.alert.rule.name), + Esql._id_values = values(_id) + by Esql.message_powershell_file_script_block_id // Apply detection threshold -| WHERE Esql.kibana_alert_rule_name_count_distinct >= 5 +| where Esql.kibana_alert_rule_name_count_distinct >= 5 ''' @@ -149,4 +148,3 @@ id = "TA0002" name = "Execution" reference = "https://attack.mitre.org/tactics/TA0002/" - diff --git a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml index d00c137bb61..51c6833e722 100644 --- a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml +++ b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml @@ -43,32 +43,32 @@ tags = [ "Use Case: Threat Detection", "Tactic: Defense Evasion", "Data Source: PowerShell Logs", - "Rule Type: BBR" + "Rule Type: BBR", ] timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-windows.powershell_operational* METADATA _id, _version, _index -| WHERE event.code == "4104" +from logs-windows.powershell_operational* metadata _id, _version, _index +| where event.code == "4104" // Look for scripts with more than 1000 chars that contain a related keyword -| EVAL Esql.powershell_file_script_block_text_length = LENGTH(powershell.file.script_block_text) +| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) // Filter for long scripts -| WHERE Esql.powershell_file_script_block_text_length > 1000 +| where Esql.powershell_file_script_block_text_length > 1000 -// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 // Excludes spaces, #, = and - as they are heavily used in scripts for formatting -| EVAL Esql.powershell_file_script_block_text_replace = REPLACE(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") +| eval Esql.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") -// Count the occurrence of special chars and their proportion to the total chars in the script -| EVAL Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - LENGTH(REPLACE(Esql.powershell_file_script_block_text_replace, "🔥", "")) -| EVAL Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double +// count the occurrence of special chars and their proportion to the total chars in the script +| eval Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - length(replace(Esql.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double -// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id -| KEEP +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id +| keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, Esql.powershell_file_script_block_text_character_ratio, @@ -85,7 +85,7 @@ FROM logs-windows.powershell_operational* METADATA _id, _version, _index user.id // Filter for scripts with a 25%+ proportion of special chars -| WHERE Esql.powershell_file_script_block_text_character_ratio > 0.25 +| where Esql.powershell_file_script_block_text_character_ratio > 0.25 ''' diff --git a/rules_building_block/persistence_web_server_sus_file_creation.toml b/rules_building_block/persistence_web_server_sus_file_creation.toml index 8e683304f23..231104566c4 100644 --- a/rules_building_block/persistence_web_server_sus_file_creation.toml +++ b/rules_building_block/persistence_web_server_sus_file_creation.toml @@ -61,8 +61,8 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-endpoint.events.file-* -| KEEP +from logs-endpoint.events.file-* +| keep @timestamp, host.os.type, event.type, @@ -74,37 +74,37 @@ FROM logs-endpoint.events.file-* file.path, agent.id, host.name -| WHERE - @timestamp > NOW() - 1 HOURS AND - host.os.type == "linux" AND - event.type == "change" AND - event.action IN ("rename", "creation") AND ( - user.name IN ( +| where + @timestamp > now() - 1 hours and + host.os.type == "linux" and + event.type == "change" and + event.action in ("rename", "creation") and ( + user.name in ( "apache", "www-data", "httpd", "nginx", "lighttpd", "tomcat", "tomcat8", "tomcat9", "ftp", "ftpuser", "ftpd" - ) OR - user.id IN ("99", "33", "498", "48") - ) AND ( - process.name IN ( + ) or + user.id in ("99", "33", "498", "48") + ) and ( + process.name in ( "apache", "nginx", "apache2", "httpd", "lighttpd", "caddy", "node", "mongrel_rails", "java", "gunicorn", "uwsgi", "openresty", "cherokee", "h2o", "resin", "puma", "unicorn", "traefik", "tornado", "hypercorn", "daphne", "twistd", "yaws", "webfsd", "httpd.worker", "flask", "rails", "mongrel" - ) OR - process.name LIKE "php-*" OR - process.name LIKE "python*" OR - process.name LIKE "ruby*" OR - process.name LIKE "perl*" + ) or + process.name like "php-*" or + process.name like "python*" or + process.name like "ruby*" or + process.name like "perl*" ) -| STATS - Esql.event_count = COUNT(), - Esql.agent_id_count_distinct = COUNT_DISTINCT(agent.id), - Esql.host_name_values = VALUES(host.name), - Esql.agent_id_values = VALUES(agent.id) - BY process.executable, file.path -| WHERE - Esql.agent_id_count_distinct == 1 AND +| stats + Esql.event_count = count(), + Esql.agent_id_count_distinct = count_distinct(agent.id), + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id) + by process.executable, file.path +| where + Esql.agent_id_count_distinct == 1 and Esql.event_count < 5 -| SORT Esql.event_count ASC -| LIMIT 100 +| sort Esql.event_count asc +| limit 100 ''' From 741829018b6273bc87a6014a2bb76a0dd7903f5e Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Thu, 31 Jul 2025 09:16:04 -0400 Subject: [PATCH 87/94] adjusted dates for unit tests --- ...credential_access_azure_entra_totp_brute_force_attempts.toml | 2 +- .../azure/persistence_entra_id_oidc_discovery_url_change.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml index 401ef4d84d4..510fd4c9539 100644 --- a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml +++ b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml @@ -2,7 +2,7 @@ creation_date = "2024/12/11" integration = ["azure"] maturity = "production" -updated_date = "2025/07/28" +updated_date = "2025/07/31" [rule] author = ["Elastic"] diff --git a/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml b/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml index 3094d7236cc..8612b003fad 100644 --- a/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml +++ b/rules/integrations/azure/persistence_entra_id_oidc_discovery_url_change.toml @@ -2,7 +2,7 @@ creation_date = "2025/07/14" integration = ["azure"] maturity = "production" -updated_date = "2025/07/22" +updated_date = "2025/07/31" [rule] author = ["Elastic"] From a411053865819e48b062b071808c5acc420db4f0 Mon Sep 17 00:00:00 2001 From: Jonhnathan <26856693+w0rk3r@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:44:57 -0300 Subject: [PATCH 88/94] Update Esql_priv to Esql_temp as these don't hold PII --- ...efense_evasion_posh_obfuscation_backtick_var.toml | 6 +++--- ...defense_evasion_posh_obfuscation_char_arrays.toml | 6 +++--- ...ense_evasion_posh_obfuscation_concat_dynamic.toml | 6 +++--- ...sion_posh_obfuscation_high_number_proportion.toml | 6 +++--- ...posh_obfuscation_iex_env_vars_reconstruction.toml | 6 +++--- ...n_posh_obfuscation_iex_string_reconstruction.toml | 6 +++--- ...ense_evasion_posh_obfuscation_index_reversal.toml | 6 +++--- ...nse_evasion_posh_obfuscation_reverse_keyword.toml | 6 +++--- ...fense_evasion_posh_obfuscation_string_concat.toml | 6 +++--- ...fense_evasion_posh_obfuscation_string_format.toml | 6 +++--- ...sh_obfuscation_whitespace_special_proportion.toml | 12 ++++++------ 11 files changed, 36 insertions(+), 36 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index 7bec9a91bd6..aad6df4bd8b 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -93,16 +93,16 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_priv.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") +| eval Esql_temp.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_replace_length, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml index 9436a733ee2..9dad9e8d1af 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml @@ -94,19 +94,19 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_priv.powershell_file_script_block_text_replace = replace( +| eval Esql_temp.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """(char\[\]\]\(\d+,\d+[^)]+|(\s?\(\[char\]\d+\s?\)\+){2,})""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.powershell_file_script_block_text_character_count, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml index 6471389c50e..b334470eeb2 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml @@ -88,19 +88,19 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace suspicious method string constructions with 🔥 for entropy-style detection // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_priv.powershell_file_script_block_text_replace = replace( +| eval Esql_temp.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """[.&]\(\s*(['"][A-Za-z0-9.-]+['"]\s*\+\s*)+['"][A-Za-z0-9.-]+['"]\s*\)""", "🔥" ) // count how many patterns were detected based on 🔥 characters -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep relevant context fields for triage | keep Esql.powershell_file_script_block_text_character_count, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml index 69b0d7117c6..8259942707a 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml @@ -91,10 +91,10 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where Esql.powershell_file_script_block_text_length > 1000 // replace digits with 🔥 for numeric pattern density analysis -| eval Esql_priv.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """[0-9]""", "🔥") +| eval Esql_temp.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """[0-9]""", "🔥") // count numeric characters and calculate proportion of total -| eval Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) | eval Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double // Retain relevant fields for investigation @@ -102,7 +102,7 @@ from logs-windows.powershell_operational* metadata _id, _version, _index Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_character_ratio, Esql.powershell_file_script_block_text_length, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml index b3fce96aad8..12a66ffd9f4 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml @@ -91,20 +91,20 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where Esql.powershell_file_script_block_text_length > 500 // replace obfuscated dynamic string construction with 🔥 to count obfuscation attempts -| eval Esql_priv.powershell_file_script_block_text_replace = replace( +| eval Esql_temp.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """(?i)(\$(?:\w+|\w+\:\w+)\[\d++\]\+\$(?:\w+|\w+\:\w+)\[\d++\]\+['"]x['"]|\$(?:\w+\:\w+)\[\d++,\d++,\d++\]|\.name\[\d++,\d++,\d++\])""", "🔥" ) // count how many 🔥 were inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep relevant context fields | keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml index 6c513a9bd59..eecad097a65 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml @@ -92,20 +92,20 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where Esql.powershell_file_script_block_text_length > 500 // replace method access patterns with 🔥 for detection -| eval Esql_priv.powershell_file_script_block_text_replace = replace( +| eval Esql_temp.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """(?i)['"]['"].(Insert|Normalize|Chars|substring|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", "🔥" ) // count 🔥 instances to determine presence of suspicious method usage -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep relevant fields for context and triage | keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml index 689930cd9cc..f041e0f22cb 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml @@ -96,20 +96,20 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_priv.powershell_file_script_block_text_replace = replace( +| eval Esql_temp.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """\$\w+\[\-\s?1\.\.""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml index 31f796b2e5d..e2975282341 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml @@ -93,19 +93,19 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_priv.powershell_file_script_block_text_replace = replace( +| eval Esql_temp.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """(?i)(rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman\.|:vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod)""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.powershell_file_script_block_text_character_count, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml index 756449ff73a..bed7c772169 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml @@ -94,20 +94,20 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_priv.powershell_file_script_block_text_replace = replace( +| eval Esql_temp.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """['"][A-Za-z0-9.]+['"](\s?\+\s?['"][A-Za-z0-9.,\-\s]+['"]){2,}""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.powershell_file_script_block_text_character_count, Esql.powershell_file_script_block_text_length, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index 5b6515d4836..8ff92f04581 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -94,20 +94,20 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_priv.powershell_file_script_block_text_replace = replace( +| eval Esql_temp.powershell_file_script_block_text_replace = replace( powershell.file.script_block_text, """((\{\d+\}){2,}["']\s?-f|::Format[^\{]+(\{\d+\}){2,})""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_script_block_text_character_count = length(Esql_priv.powershell_file_script_block_text_replace) - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.powershell_script_block_text_character_count, Esql.powershell_file_script_block_text_length, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, powershell.file.script_block_text, powershell.file.script_block_id, file.path, diff --git a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml index 86593013b5b..6c5868087d0 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml @@ -89,21 +89,21 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // replace repeated spaces used for formatting after a new line with a single space to reduce FPs -| eval Esql_priv.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """\n\s+""", "\n ") +| eval Esql_temp.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """\n\s+""", "\n ") // Look for scripts with more than 1000 chars -| eval Esql.powershell_file_script_block_text_replace_length = length(Esql_priv.powershell_file_script_block_text_replace) +| eval Esql.powershell_file_script_block_text_replace_length = length(Esql_temp.powershell_file_script_block_text_replace) | where Esql.powershell_file_script_block_text_replace_length > 1000 // replace format characters with 🔥 to count suspicious formatting density -| eval Esql_priv.powershell_file_script_block_text_replace = replace( - Esql_priv.powershell_file_script_block_text_replace, +| eval Esql_temp.powershell_file_script_block_text_replace = replace( + Esql_temp.powershell_file_script_block_text_replace, """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\.]""", "🔥" ) // count 🔥 and calculate its proportion of the script -| eval Esql.powershell_file_script_block_text_count = Esql.powershell_file_script_block_text_length - length(replace(Esql_priv.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.powershell_file_script_block_text_count = Esql.powershell_file_script_block_text_length - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) | eval Esql.powershell_file_script_block_text_ratio = Esql.powershell_file_script_block_text_count::double / Esql.powershell_file_script_block_text_length::double // Retain fields for context or alert generation @@ -111,7 +111,7 @@ from logs-windows.powershell_operational* metadata _id, _version, _index Esql.powershell_file_script_block_text_count, Esql.powershell_file_script_block_text_length, Esql.powershell_file_script_block_text_ratio, - Esql_priv.powershell_file_script_block_text_replace, + Esql_temp.powershell_file_script_block_text_replace, Esql.powershell_file_script_block_text_replace_length, powershell.file.script_block_text, powershell.file.script_block_id, From b073945193ee14f3f4f689c917416e46500ec666 Mon Sep 17 00:00:00 2001 From: Jonhnathan <26856693+w0rk3r@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:49:07 -0300 Subject: [PATCH 89/94] PowerShell adjustments --- ...nse_evasion_posh_obfuscation_backtick.toml | 11 ++++---- ...evasion_posh_obfuscation_backtick_var.toml | 17 ++++++------ ..._evasion_posh_obfuscation_char_arrays.toml | 12 ++++----- ...asion_posh_obfuscation_concat_dynamic.toml | 12 ++++----- ...sh_obfuscation_high_number_proportion.toml | 24 ++++++++--------- ...fuscation_iex_env_vars_reconstruction.toml | 18 ++++++------- ...obfuscation_iex_string_reconstruction.toml | 18 ++++++------- ...asion_posh_obfuscation_index_reversal.toml | 18 ++++++------- ...sion_posh_obfuscation_reverse_keyword.toml | 12 ++++----- ...vasion_posh_obfuscation_string_concat.toml | 18 ++++++------- ...vasion_posh_obfuscation_string_format.toml | 18 ++++++------- ...scation_whitespace_special_proportion.toml | 27 +++++++++---------- .../execution_posh_malicious_script_agg.toml | 8 +++--- ..._obfuscation_proportion_special_chars.toml | 22 +++++++-------- 14 files changed, 118 insertions(+), 117 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml index d60c4655cec..4c0ca137736 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml @@ -97,15 +97,15 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql.script_block_text_replace = replace(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") +| eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.script_block_text_character_count = length(Esql.script_block_text_replace) - length(replace(Esql.script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep - Esql.script_block_text_character_count, - Esql.script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.name, @@ -118,7 +118,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -| where Esql.script_block_text_character_count >= 10 +// Filter for scripts that match the pattern at least 10 times +| where Esql.script_block_pattern_count >= 10 // Filter FPs, and due to the behavior of the like operator, allow null values | where (file.name not like "TSS_*.psm1" or file.name is null) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index aad6df4bd8b..0c03b365b6b 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -88,21 +88,21 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| eval Esql.script_block_text_length = length(powershell.file.script_block_text) -| where Esql.script_block_text_length > 500 +| eval Esql.script_block_length = length(powershell.file.script_block_text) +| where Esql.script_block_length > 500 // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_temp.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") +| eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep - Esql.powershell_file_script_block_text_character_count, - Esql.powershell_file_script_block_text_replace_length, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_length, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -115,7 +115,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -| where Esql.powershell_file_script_block_text_character_count >= 1 +// Filter for scripts that match the pattern at least once +| where Esql.script_block_pattern_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml index 9dad9e8d1af..23df987f138 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml @@ -94,19 +94,19 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_temp.powershell_file_script_block_text_replace = replace( +| eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """(char\[\]\]\(\d+,\d+[^)]+|(\s?\(\[char\]\d+\s?\)\+){2,})""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep - Esql.powershell_file_script_block_text_character_count, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -118,8 +118,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Final filter on match count -| where Esql.powershell_file_script_block_text_character_count >= 1 +// Filter for scripts that match the pattern at least once +| where Esql.script_block_pattern_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml index b334470eeb2..9e77eca2ea5 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml @@ -88,19 +88,19 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace suspicious method string constructions with 🔥 for entropy-style detection // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_temp.powershell_file_script_block_text_replace = replace( +| eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """[.&]\(\s*(['"][A-Za-z0-9.-]+['"]\s*\+\s*)+['"][A-Za-z0-9.-]+['"]\s*\)""", "🔥" ) // count how many patterns were detected based on 🔥 characters -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep relevant context fields for triage | keep - Esql.powershell_file_script_block_text_character_count, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -112,8 +112,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Alert if suspicious pattern is present -| where Esql.powershell_file_script_block_text_character_count >= 1 +// Filter for scripts that match the pattern at least once +| where Esql.script_block_pattern_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml index 8259942707a..4b0babddd81 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml @@ -87,22 +87,22 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // Measure script length -| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) -| where Esql.powershell_file_script_block_text_length > 1000 +| eval Esql.script_block_length = length(powershell.file.script_block_text) +| where Esql.script_block_length > 1000 // replace digits with 🔥 for numeric pattern density analysis -| eval Esql_temp.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """[0-9]""", "🔥") +| eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """[0-9]""", "🔥") // count numeric characters and calculate proportion of total -| eval Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) -| eval Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double +| eval Esql.script_block_pattern_count = Esql.script_block_length - length(replace(Esql.script_block_tmp, "🔥", "")) +| eval Esql.script_block_ratio = Esql.script_block_pattern_count::double / Esql.script_block_length::double // Retain relevant fields for investigation | keep - Esql.powershell_file_script_block_text_character_count, - Esql.powershell_file_script_block_text_character_ratio, - Esql.powershell_file_script_block_text_length, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_ratio, + Esql.script_block_length, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -114,10 +114,10 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Filter for suspiciously high numeric content -| where Esql.powershell_file_script_block_text_character_ratio > 0.30 +// Filter for scripts with high numeric character ratio +| where Esql.script_block_ratio > 0.30 -// Exclude noisy patterns such as 64-character hashes +// Exclude noisy patterns such as 64-character hash lists | where not powershell.file.script_block_text rlike """.*\"[a-fA-F0-9]{64}\"\,.*""" ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml index 12a66ffd9f4..9611584176a 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml @@ -87,24 +87,24 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // Evaluate the length of the script block -| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) -| where Esql.powershell_file_script_block_text_length > 500 +| eval Esql.script_block_length = length(powershell.file.script_block_text) +| where Esql.script_block_length > 500 // replace obfuscated dynamic string construction with 🔥 to count obfuscation attempts -| eval Esql_temp.powershell_file_script_block_text_replace = replace( +| eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """(?i)(\$(?:\w+|\w+\:\w+)\[\d++\]\+\$(?:\w+|\w+\:\w+)\[\d++\]\+['"]x['"]|\$(?:\w+\:\w+)\[\d++,\d++,\d++\]|\.name\[\d++,\d++,\d++\])""", "🔥" ) // count how many 🔥 were inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep relevant context fields | keep - Esql.powershell_file_script_block_text_character_count, - Esql.powershell_file_script_block_text_length, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_length, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -116,8 +116,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Filter for meaningful pattern matches -| where Esql.powershell_file_script_block_text_character_count >= 1 +// Filter for scripts that match the pattern at least once +| where Esql.script_block_pattern_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml index eecad097a65..55b55746a69 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml @@ -88,24 +88,24 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // Check script length > 500 -| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) -| where Esql.powershell_file_script_block_text_length > 500 +| eval Esql.script_block_length = length(powershell.file.script_block_text) +| where Esql.script_block_length > 500 // replace method access patterns with 🔥 for detection -| eval Esql_temp.powershell_file_script_block_text_replace = replace( +| eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """(?i)['"]['"].(Insert|Normalize|Chars|substring|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", "🔥" ) // count 🔥 instances to determine presence of suspicious method usage -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep relevant fields for context and triage | keep - Esql.powershell_file_script_block_text_character_count, - Esql.powershell_file_script_block_text_length, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_length, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -117,8 +117,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Only return if at least one pattern is found -| where Esql.powershell_file_script_block_text_character_count >= 1 +// Filter for scripts that match the pattern at least once +| where Esql.script_block_pattern_count >= 1 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml index f041e0f22cb..aabc1499f86 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml @@ -91,25 +91,25 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) -| where Esql.powershell_file_script_block_text_length > 500 +| eval Esql.script_block_length = length(powershell.file.script_block_text) +| where Esql.script_block_length > 500 // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_temp.powershell_file_script_block_text_replace = replace( +| eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """\$\w+\[\-\s?1\.\.""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep - Esql.powershell_file_script_block_text_character_count, - Esql.powershell_file_script_block_text_length, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_length, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -121,8 +121,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Filter for patterns found at least once -| where Esql.powershell_file_script_block_text_character_count >= 1 +// Filter for scripts that match the pattern at least once +| where Esql.script_block_pattern_count >= 1 // FP Patterns | where not powershell.file.script_block_text like "*GENESIS-5654*" diff --git a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml index e2975282341..35146c142c5 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml @@ -93,19 +93,19 @@ from logs-windows.powershell_operational* metadata _id, _version, _index // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_temp.powershell_file_script_block_text_replace = replace( +| eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """(?i)(rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman\.|:vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod)""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep - Esql.powershell_file_script_block_text_character_count, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -115,8 +115,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index _index, agent.id -// Filter for scripts with at least 2 suspicious keyword matches -| where Esql.powershell_file_script_block_text_character_count >= 2 +// Filter for scripts that match the pattern at least twice +| where Esql.script_block_pattern_count >= 2 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml index bed7c772169..1796d9394db 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml @@ -89,25 +89,25 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) -| where Esql.powershell_file_script_block_text_length > 500 +| eval Esql.script_block_length = length(powershell.file.script_block_text) +| where Esql.script_block_length > 500 // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_temp.powershell_file_script_block_text_replace = replace( +| eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """['"][A-Za-z0-9.]+['"](\s?\+\s?['"][A-Za-z0-9.,\-\s]+['"]){2,}""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_file_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep - Esql.powershell_file_script_block_text_character_count, - Esql.powershell_file_script_block_text_length, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_length, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -119,8 +119,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Filter for scripts with at least 2 suspicious string concatenations -| where Esql.powershell_file_script_block_text_character_count >= 2 +// Filter for scripts that match the pattern at least twice +| where Esql.script_block_pattern_count >= 2 ''' diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index 8ff92f04581..5eb3f097786 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -88,26 +88,26 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // Look for scripts with more than 500 chars that contain a related keyword -| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) -| where Esql.powershell_file_script_block_text_length > 500 +| eval Esql.script_block_length = length(powershell.file.script_block_text) +| where Esql.script_block_length > 500 | where powershell.file.script_block_text like "*{0}*" // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 -| eval Esql_temp.powershell_file_script_block_text_replace = replace( +| eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """((\{\d+\}){2,}["']\s?-f|::Format[^\{]+(\{\d+\}){2,})""", "🔥" ) // count how many patterns were detected by calculating the number of 🔥 characters inserted -| eval Esql.powershell_script_block_text_character_count = length(Esql_temp.powershell_file_script_block_text_replace) - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) +| eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep - Esql.powershell_script_block_text_character_count, - Esql.powershell_file_script_block_text_length, - Esql_temp.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_length, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -120,8 +120,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Filter for scripts with more than 3 suspicious format patterns -| where Esql.powershell_script_block_text_character_count > 3 +// Filter for scripts that match the pattern at least four times +| where Esql.script_block_pattern_count >= 4 // Exclude Noisy Patterns diff --git a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml index 6c5868087d0..fdd5b48a710 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml @@ -89,30 +89,29 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // replace repeated spaces used for formatting after a new line with a single space to reduce FPs -| eval Esql_temp.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """\n\s+""", "\n ") +| eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """\n\s+""", "\n ") // Look for scripts with more than 1000 chars -| eval Esql.powershell_file_script_block_text_replace_length = length(Esql_temp.powershell_file_script_block_text_replace) -| where Esql.powershell_file_script_block_text_replace_length > 1000 +| eval Esql.script_block_length = length(Esql.script_block_tmp) +| where Esql.script_block_length > 1000 // replace format characters with 🔥 to count suspicious formatting density -| eval Esql_temp.powershell_file_script_block_text_replace = replace( - Esql_temp.powershell_file_script_block_text_replace, +| eval Esql.script_block_tmp = replace( + Esql.script_block_tmp, """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\.]""", "🔥" ) // count 🔥 and calculate its proportion of the script -| eval Esql.powershell_file_script_block_text_count = Esql.powershell_file_script_block_text_length - length(replace(Esql_temp.powershell_file_script_block_text_replace, "🔥", "")) -| eval Esql.powershell_file_script_block_text_ratio = Esql.powershell_file_script_block_text_count::double / Esql.powershell_file_script_block_text_length::double +| eval Esql.script_block_count = Esql.script_block_length - length(replace(Esql.script_block_tmp, "🔥", "")) +| eval Esql.script_block_ratio = Esql.script_block_count::double / Esql.script_block_length::double // Retain fields for context or alert generation | keep - Esql.powershell_file_script_block_text_count, - Esql.powershell_file_script_block_text_length, - Esql.powershell_file_script_block_text_ratio, - Esql_temp.powershell_file_script_block_text_replace, - Esql.powershell_file_script_block_text_replace_length, + Esql.script_block_count, + Esql.script_block_length, + Esql.script_block_ratio, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -124,8 +123,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Filter for high-ratio suspicious formatting scripts -| where Esql.powershell_file_script_block_text_ratio > 0.75 +// Filter for scripts with high whitespace and special character ratio +| where Esql.script_block_ratio > 0.75 ''' diff --git a/rules/windows/execution_posh_malicious_script_agg.toml b/rules/windows/execution_posh_malicious_script_agg.toml index dba1df48aad..ee48f94d869 100644 --- a/rules/windows/execution_posh_malicious_script_agg.toml +++ b/rules/windows/execution_posh_malicious_script_agg.toml @@ -112,18 +112,18 @@ from .alerts-security.* metadata _id | where kibana.alert.rule.name like "*PowerShell*" // as alerts don't have non-ECS fields, parse the script block ID using grok -| grok message "ScriptBlock ID: (?.+)" -| where Esql.message_powershell_file_script_block_id is not null +| grok message "ScriptBlock ID: (?.+)" +| where Esql.script_block_id is not null // keep relevant fields for further processing -| keep kibana.alert.rule.name, Esql.message_powershell_file_script_block_id, _id +| keep kibana.alert.rule.name, Esql.script_block_id, _id // count distinct alerts and filter for matches above the threshold | stats Esql.kibana_alert_rule_name_count_distinct = count_distinct(kibana.alert.rule.name), Esql.kibana_alert_rule_name_values = values(kibana.alert.rule.name), Esql._id_values = values(_id) - by Esql.message_powershell_file_script_block_id + by Esql.script_block_id // Apply detection threshold | where Esql.kibana_alert_rule_name_count_distinct >= 5 diff --git a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml index 51c6833e722..01a76d175f5 100644 --- a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml +++ b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml @@ -53,26 +53,26 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" // Look for scripts with more than 1000 chars that contain a related keyword -| eval Esql.powershell_file_script_block_text_length = length(powershell.file.script_block_text) +| eval Esql.script_block_length = length(powershell.file.script_block_text) // Filter for long scripts -| where Esql.powershell_file_script_block_text_length > 1000 +| where Esql.script_block_length > 1000 // replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 // Excludes spaces, #, = and - as they are heavily used in scripts for formatting -| eval Esql.powershell_file_script_block_text_replace = replace(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") +| eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") // count the occurrence of special chars and their proportion to the total chars in the script -| eval Esql.powershell_file_script_block_text_character_count = Esql.powershell_file_script_block_text_length - length(replace(Esql.powershell_file_script_block_text_replace, "🔥", "")) -| eval Esql.powershell_file_script_block_text_character_ratio = Esql.powershell_file_script_block_text_character_count::double / Esql.powershell_file_script_block_text_length::double +| eval Esql.script_block_pattern_count = Esql.script_block_length - length(replace(Esql.script_block_tmp, "🔥", "")) +| eval Esql.script_block_ratio = Esql.script_block_pattern_count::double / Esql.script_block_length::double // keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep - Esql.powershell_file_script_block_text_character_count, - Esql.powershell_file_script_block_text_length, - Esql.powershell_file_script_block_text_character_ratio, - Esql.powershell_file_script_block_text_replace, + Esql.script_block_pattern_count, + Esql.script_block_length, + Esql.script_block_ratio, + Esql.script_block_tmp, powershell.file.script_block_text, powershell.file.script_block_id, file.path, @@ -84,8 +84,8 @@ from logs-windows.powershell_operational* metadata _id, _version, _index agent.id, user.id -// Filter for scripts with a 25%+ proportion of special chars -| where Esql.powershell_file_script_block_text_character_ratio > 0.25 +// Filter for scripts with high special character ratio +| where Esql.script_block_ratio > 0.25 ''' From 71587d85d7ce61f97ae938a9ab77346dc6ee5936 Mon Sep 17 00:00:00 2001 From: Jonhnathan <26856693+w0rk3r@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:09:56 -0300 Subject: [PATCH 90/94] Make query comments consistent --- .../defense_evasion_posh_obfuscation_backtick.toml | 6 ++---- ...defense_evasion_posh_obfuscation_backtick_var.toml | 4 ++-- .../defense_evasion_posh_obfuscation_char_arrays.toml | 4 +--- ...fense_evasion_posh_obfuscation_concat_dynamic.toml | 6 +++--- ...asion_posh_obfuscation_high_number_proportion.toml | 11 +++++++---- ..._posh_obfuscation_iex_env_vars_reconstruction.toml | 9 +++++---- ...on_posh_obfuscation_iex_string_reconstruction.toml | 9 +++++---- ...fense_evasion_posh_obfuscation_index_reversal.toml | 6 ++---- ...ense_evasion_posh_obfuscation_reverse_keyword.toml | 4 +--- ...efense_evasion_posh_obfuscation_string_concat.toml | 6 ++---- ...efense_evasion_posh_obfuscation_string_format.toml | 9 +++------ ...osh_obfuscation_whitespace_special_proportion.toml | 11 ++++++----- ...ion_posh_obfuscation_proportion_special_chars.toml | 10 +++++----- 13 files changed, 44 insertions(+), 51 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml index 4c0ca137736..8a4a8498d40 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick.toml @@ -91,11 +91,9 @@ type = "esql" query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index -| where - event.code == "4104" and - powershell.file.script_block_text like "*`*" +| where event.code == "4104" and powershell.file.script_block_text like "*`*" -// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """[A-Za-z0-9_-]`(?![rntb]|\r|\n|\d)[A-Za-z0-9_-]""", "🔥") diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index 0c03b365b6b..af85ff059a3 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -87,11 +87,11 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Look for scripts with more than 500 chars that contain a related keyword +// Filter out small scripts that are unlikely to contain the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 -// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥") diff --git a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml index 23df987f138..81bee0a4af1 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_char_arrays.toml @@ -85,14 +85,12 @@ type = "esql" query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index - -// Filter for Event ID 4104 indicating script block logging | where event.code == "4104" // Filter for scripts that contain the "char" keyword using MATCH, boosts the query performance | where powershell.file.script_block_text : "char" -// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( powershell.file.script_block_text, diff --git a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml index 9e77eca2ea5..3f7653156b8 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic.toml @@ -86,7 +86,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" and powershell.file.script_block_text like "*+*" -// replace suspicious method string constructions with 🔥 for entropy-style detection +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( powershell.file.script_block_text, @@ -94,10 +94,10 @@ from logs-windows.powershell_operational* metadata _id, _version, _index "🔥" ) -// count how many patterns were detected based on 🔥 characters +// count how many patterns were detected by calculating the number of 🔥 characters inserted | eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) -// keep relevant context fields for triage +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.script_block_pattern_count, Esql.script_block_tmp, diff --git a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml index 4b0babddd81..1507f4418ea 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml @@ -86,18 +86,21 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Measure script length +// Filter out small scripts that are unlikely to contain the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 1000 -// replace digits with 🔥 for numeric pattern density analysis +// replace the patterns we are looking for with the 🔥 emoji to enable counting them +// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """[0-9]""", "🔥") -// count numeric characters and calculate proportion of total +// count how many patterns were detected by calculating the number of 🔥 characters inserted | eval Esql.script_block_pattern_count = Esql.script_block_length - length(replace(Esql.script_block_tmp, "🔥", "")) + +// Calculate the ratio of special characters to total length | eval Esql.script_block_ratio = Esql.script_block_pattern_count::double / Esql.script_block_length::double -// Retain relevant fields for investigation +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.script_block_pattern_count, Esql.script_block_ratio, diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml index 9611584176a..07810205e3a 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml @@ -86,21 +86,22 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Evaluate the length of the script block +// Filter out small scripts that are unlikely to contain the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 -// replace obfuscated dynamic string construction with 🔥 to count obfuscation attempts +// replace the patterns we are looking for with the 🔥 emoji to enable counting them +// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """(?i)(\$(?:\w+|\w+\:\w+)\[\d++\]\+\$(?:\w+|\w+\:\w+)\[\d++\]\+['"]x['"]|\$(?:\w+\:\w+)\[\d++,\d++,\d++\]|\.name\[\d++,\d++,\d++\])""", "🔥" ) -// count how many 🔥 were inserted +// count how many patterns were detected by calculating the number of 🔥 characters inserted | eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) -// keep relevant context fields +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.script_block_pattern_count, Esql.script_block_length, diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml index 55b55746a69..8428d0ae8c2 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml @@ -87,21 +87,22 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Check script length > 500 +// Filter out small scripts that are unlikely to contain the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 -// replace method access patterns with 🔥 for detection +// replace the patterns we are looking for with the 🔥 emoji to enable counting them +// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( powershell.file.script_block_text, """(?i)['"]['"].(Insert|Normalize|Chars|substring|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", "🔥" ) -// count 🔥 instances to determine presence of suspicious method usage +// count how many patterns were detected by calculating the number of 🔥 characters inserted | eval Esql.script_block_pattern_count = length(Esql.script_block_tmp) - length(replace(Esql.script_block_tmp, "🔥", "")) -// keep relevant fields for context and triage +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.script_block_pattern_count, Esql.script_block_length, diff --git a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml index aabc1499f86..a0a4f96d24d 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml @@ -86,15 +86,13 @@ type = "esql" query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index - -// Filter for PowerShell Event ID 4104 | where event.code == "4104" -// Look for scripts with more than 500 chars that contain a related keyword +// Filter out small scripts that are unlikely to contain the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 -// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( powershell.file.script_block_text, diff --git a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml index 35146c142c5..eb83ae996f0 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_reverse_keyword.toml @@ -83,15 +83,13 @@ type = "esql" query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index - -// Filter for PowerShell Event ID 4104 | where event.code == "4104" // Filter for scripts that contains these keywords using MATCH, boosts the query performance, // match will ignore the | and look for the individual words | where powershell.file.script_block_text : "rahc|metsys|stekcos|tcejboimw|ecalper|ecnerferpe|noitcennoc|nioj|eman|vne|gnirts|tcejbo-wen|_23niw|noisserpxe|ekovni|daolnwod" -// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( powershell.file.script_block_text, diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml index 1796d9394db..bdd6be02a18 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml @@ -84,15 +84,13 @@ type = "esql" query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index - -// Filter for PowerShell Event ID 4104 | where event.code == "4104" -// Look for scripts with more than 500 chars that contain a related keyword +// Filter out small scripts that are unlikely to contain the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 -// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( powershell.file.script_block_text, diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index 5eb3f097786..1309d497fd7 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -83,16 +83,13 @@ type = "esql" query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index +| where event.code == "4104" and powershell.file.script_block_text like "*{0}*" -// Filter for PowerShell Event ID 4104 -| where event.code == "4104" - -// Look for scripts with more than 500 chars that contain a related keyword +// Filter out small scripts that are unlikely to contain the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 -| where powershell.file.script_block_text like "*{0}*" -// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( powershell.file.script_block_text, diff --git a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml index fdd5b48a710..1598e42b6ba 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_whitespace_special_proportion.toml @@ -84,8 +84,6 @@ type = "esql" query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index - -// Filter for PowerShell Event ID 4104 | where event.code == "4104" // replace repeated spaces used for formatting after a new line with a single space to reduce FPs @@ -95,18 +93,21 @@ from logs-windows.powershell_operational* metadata _id, _version, _index | eval Esql.script_block_length = length(Esql.script_block_tmp) | where Esql.script_block_length > 1000 -// replace format characters with 🔥 to count suspicious formatting density +// replace the patterns we are looking for with the 🔥 emoji to enable counting them +// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 | eval Esql.script_block_tmp = replace( Esql.script_block_tmp, """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\.]""", "🔥" ) -// count 🔥 and calculate its proportion of the script +// count how many patterns were detected by calculating the number of 🔥 characters inserted | eval Esql.script_block_count = Esql.script_block_length - length(replace(Esql.script_block_tmp, "🔥", "")) + +// Calculate the ratio of special characters to total length | eval Esql.script_block_ratio = Esql.script_block_count::double / Esql.script_block_length::double -// Retain fields for context or alert generation +// keep the fields relevant to the query, although this is not needed as the alert is populated using _id | keep Esql.script_block_count, Esql.script_block_length, diff --git a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml index 01a76d175f5..860beb9b16c 100644 --- a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml +++ b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml @@ -52,19 +52,19 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Look for scripts with more than 1000 chars that contain a related keyword +// Filter out small scripts that are unlikely to contain the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) - -// Filter for long scripts | where Esql.script_block_length > 1000 -// replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for +// replace the patterns we are looking for with the 🔥 emoji to enable counting them // The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1 // Excludes spaces, #, = and - as they are heavily used in scripts for formatting | eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """[^0-9A-Za-z\s#=-]""", "🔥") -// count the occurrence of special chars and their proportion to the total chars in the script +// count how many patterns were detected by calculating the number of 🔥 characters inserted | eval Esql.script_block_pattern_count = Esql.script_block_length - length(replace(Esql.script_block_tmp, "🔥", "")) + +// Calculate the ratio of special characters to total length | eval Esql.script_block_ratio = Esql.script_block_pattern_count::double / Esql.script_block_length::double // keep the fields relevant to the query, although this is not needed as the alert is populated using _id From 37951fcbc90e5af8ffd77b167839516695475ed5 Mon Sep 17 00:00:00 2001 From: Jonhnathan <26856693+w0rk3r@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:16:11 -0300 Subject: [PATCH 91/94] update comment --- .../windows/defense_evasion_posh_obfuscation_backtick_var.toml | 2 +- ...defense_evasion_posh_obfuscation_high_number_proportion.toml | 2 +- ...se_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml | 2 +- ...ense_evasion_posh_obfuscation_iex_string_reconstruction.toml | 2 +- .../defense_evasion_posh_obfuscation_index_reversal.toml | 2 +- .../windows/defense_evasion_posh_obfuscation_string_concat.toml | 2 +- .../windows/defense_evasion_posh_obfuscation_string_format.toml | 2 +- ...fense_evasion_posh_obfuscation_proportion_special_chars.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml index af85ff059a3..63ba68fd0e1 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_backtick_var.toml @@ -87,7 +87,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Filter out small scripts that are unlikely to contain the patterns we are looking for +// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 diff --git a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml index 1507f4418ea..c6f0ec60d18 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_high_number_proportion.toml @@ -86,7 +86,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Filter out small scripts that are unlikely to contain the patterns we are looking for +// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 1000 diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml index 07810205e3a..dcf423bacba 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_env_vars_reconstruction.toml @@ -86,7 +86,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Filter out small scripts that are unlikely to contain the patterns we are looking for +// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 diff --git a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml index 8428d0ae8c2..5d5aaeae975 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_iex_string_reconstruction.toml @@ -87,7 +87,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Filter out small scripts that are unlikely to contain the patterns we are looking for +// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 diff --git a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml index a0a4f96d24d..666e6990313 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml @@ -88,7 +88,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Filter out small scripts that are unlikely to contain the patterns we are looking for +// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml index bdd6be02a18..390b3bff774 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_concat.toml @@ -86,7 +86,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Filter out small scripts that are unlikely to contain the patterns we are looking for +// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 diff --git a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml index 1309d497fd7..61edfb1081f 100644 --- a/rules/windows/defense_evasion_posh_obfuscation_string_format.toml +++ b/rules/windows/defense_evasion_posh_obfuscation_string_format.toml @@ -85,7 +85,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" and powershell.file.script_block_text like "*{0}*" -// Filter out small scripts that are unlikely to contain the patterns we are looking for +// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 500 diff --git a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml index 860beb9b16c..930c6d16cfd 100644 --- a/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml +++ b/rules_building_block/defense_evasion_posh_obfuscation_proportion_special_chars.toml @@ -52,7 +52,7 @@ query = ''' from logs-windows.powershell_operational* metadata _id, _version, _index | where event.code == "4104" -// Filter out small scripts that are unlikely to contain the patterns we are looking for +// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for | eval Esql.script_block_length = length(powershell.file.script_block_text) | where Esql.script_block_length > 1000 From 7a30a787df5a75cbafde1e9c7dcbaccc9114fd7d Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Tue, 5 Aug 2025 18:54:19 -0400 Subject: [PATCH 92/94] reverted 2856446a-34e6-435b-9fb5-f8f040bfa7ed --- rules/windows/discovery_command_system_account.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/windows/discovery_command_system_account.toml b/rules/windows/discovery_command_system_account.toml index b2a19004a01..d19f10c389d 100644 --- a/rules/windows/discovery_command_system_account.toml +++ b/rules/windows/discovery_command_system_account.toml @@ -2,7 +2,7 @@ creation_date = "2020/03/18" integration = ["endpoint", "windows"] maturity = "production" -updated_date = "2025/07/24" +updated_date = "2025/05/20" [rule] author = ["Elastic"] @@ -84,7 +84,7 @@ not process.parent.executable : ("C:\\Program Files\\Microsoft Monitoring Agent\\Agent\\MonitoringHost.exe", "C:\\Program Files\\Dell\\SupportAssistAgent\\SRE\\SRE.exe", "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", - "C:\\Windows\\Temp\\WinGet\\defaultState\\PostgrEsql.PostgreSQL*\\postgresql-*-windows-x64.exe", + "C:\\Windows\\Temp\\WinGet\\defaultState\\PostgreSQL.PostgreSQL*\\postgresql-*-windows-x64.exe", "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", "C:\\Program Files (x86)\\SolarWinds\\Agent\\Plugins\\JobEngine\\SWJobEngineWorker2.exe") and not (process.parent.executable : "C:\\Windows\\Sys?????\\WindowsPowerShell\\v1.0\\powershell.exe" and From 888ca0809d98c818d13e38d3ffcf5c79de2cd085 Mon Sep 17 00:00:00 2001 From: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:57:12 -0400 Subject: [PATCH 93/94] Update rules/windows/discovery_command_system_account.toml --- .../discovery_command_system_account.toml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rules/windows/discovery_command_system_account.toml b/rules/windows/discovery_command_system_account.toml index d19f10c389d..03d5ecdf531 100644 --- a/rules/windows/discovery_command_system_account.toml +++ b/rules/windows/discovery_command_system_account.toml @@ -77,19 +77,19 @@ process where host.os.type == "windows" and event.type == "start" and ( process.name : "net1.exe" and not process.parent.name : "net.exe" and not process.args : ("start", "stop", "/active:*") ) - ) and -process.parent.executable != null and -not (process.name : "net1.exe" and process.working_directory : "C:\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\Downloads\\") and -not process.parent.executable : - ("C:\\Program Files\\Microsoft Monitoring Agent\\Agent\\MonitoringHost.exe", - "C:\\Program Files\\Dell\\SupportAssistAgent\\SRE\\SRE.exe", - "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", - "C:\\Windows\\Temp\\WinGet\\defaultState\\PostgreSQL.PostgreSQL*\\postgresql-*-windows-x64.exe", - "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", - "C:\\Program Files (x86)\\SolarWinds\\Agent\\Plugins\\JobEngine\\SWJobEngineWorker2.exe") and -not (process.parent.executable : "C:\\Windows\\Sys?????\\WindowsPowerShell\\v1.0\\powershell.exe" and - process.parent.args : ("C:\\Program Files (x86)\\Microsoft Intune Management Extension\\*.ps1", - "Agent\\Modules\\AdHealthConfiguration\\AdHealthConfiguration.psd1'")) and + ) and +process.parent.executable != null and +not (process.name : "net1.exe" and process.working_directory : "C:\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\Downloads\\") and +not process.parent.executable : + ("C:\\Program Files\\Microsoft Monitoring Agent\\Agent\\MonitoringHost.exe", + "C:\\Program Files\\Dell\\SupportAssistAgent\\SRE\\SRE.exe", + "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", + "C:\\Windows\\Temp\\WinGet\\defaultState\\PostgreSQL.PostgreSQL*\\postgresql-*-windows-x64.exe", + "C:\\Program Files\\Obkio Agent\\main.dist\\ObkioAgentSoftware.exe", + "C:\\Program Files (x86)\\SolarWinds\\Agent\\Plugins\\JobEngine\\SWJobEngineWorker2.exe") and +not (process.parent.executable : "C:\\Windows\\Sys?????\\WindowsPowerShell\\v1.0\\powershell.exe" and + process.parent.args : ("C:\\Program Files (x86)\\Microsoft Intune Management Extension\\*.ps1", + "Agent\\Modules\\AdHealthConfiguration\\AdHealthConfiguration.psd1'")) and not (process.parent.name : "cmd.exe" and process.working_directory : "C:\\Program Files\\Infraon Corp\\SecuraAgent\\") ''' From 65005ec40901d548da826aec427e93634fcd385e Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Tue, 5 Aug 2025 19:04:58 -0400 Subject: [PATCH 94/94] removed dot notation --- ...l_access_azure_entra_totp_brute_force_attempts.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml index 510fd4c9539..71102c93f69 100644 --- a/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml +++ b/rules/integrations/azure/credential_access_azure_entra_totp_brute_force_attempts.toml @@ -106,7 +106,7 @@ from logs-azure.signinlogs* metadata _id, _version, _index | stats Esql.event_count = count(*), - Esql.azure_signinlogs_properties.session_id_count_distinct = count_distinct(azure.signinlogs.properties.session_id), + Esql.azure_signinlogs_properties_session_id_count_distinct = count_distinct(azure.signinlogs.properties.session_id), Esql.source_address_values = values(source.address), Esql.azure_tenant_id_valuues = values(azure.tenant_id), Esql_priv.azure_identity_values = values(azure.signinlogs.identity), @@ -125,16 +125,16 @@ from logs-azure.signinlogs* metadata _id, _version, _index Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id), Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state), Esql.azure_signinlogs_properties_risk_detail_values = values(azure.signinlogs.properties.risk_detail), - Esql.azure_signinlogs_properties_status.error_code_values = values(azure.signinlogs.properties.status.error_code), + Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code), Esql.azure_signinlogs_properties_original_request_id_values = values(azure.signinlogs.properties.original_request_id), Esql.user_id_values = values(user.id) by user.id -| where Esql.event_count >= 20 and Esql.azure_signinlogs_properties.session_id_count_distinct >= 10 +| where Esql.event_count >= 20 and Esql.azure_signinlogs_properties_session_id_count_distinct >= 10 | keep Esql.event_count, - Esql.azure_signinlogs_properties.session_id_count_distinct, + Esql.azure_signinlogs_properties_session_id_count_distinct, Esql.source_address_values, Esql.azure_tenant_id_valuues, Esql_priv.azure_identity_values, @@ -153,7 +153,7 @@ from logs-azure.signinlogs* metadata _id, _version, _index Esql.azure_signinlogs_properties_resource_id_values, Esql.azure_signinlogs_properties_risk_state_values, Esql.azure_signinlogs_properties_risk_detail_values, - Esql.azure_signinlogs_properties_status.error_code_values, + Esql.azure_signinlogs_properties_status_error_code_values, Esql.azure_signinlogs_properties_original_request_id_values, Esql.user_id_values '''