Elastic high stable kql

Suspected Lateral Movement from Compromised Host

Detects potential lateral movement or post-compromise activity by correlating alerts where the host.ip of one alert matches the source.ip of a subsequent alert. This behavior may indicate a compromised host being used to authenticate to another system or resource, including cloud services.

View Source

Detection Logic

from .alerts-security.*

// any alerts excluding deprecated, low severity and threat_match rules
| where kibana.alert.rule.name is not null and kibana.alert.risk_score > 21 and
        kibana.alert.rule.type != "threat_match" and
        not kibana.alert.rule.name like "Deprecated - *" and
        not KQL("""kibana.alert.rule.tags : "Rule Type: Higher-Order Rule" """) and
        not kibana.alert.rule.name in ("Abnormally Large DNS Response", "Web Application Suspicious Activity: No User Agent")

// alerts with existing source.ip or host.ip
| eval alert_source_ip = CASE(source.ip is not null, source.ip, null),
       alert_host_ip = CASE(host.ip is not null and source.ip is null, host.ip, null)
| eval Esql.source_ip = COALESCE(alert_source_ip, alert_host_ip)
| where Esql.source_ip is not null and Esql.source_ip != "127.0.0.1" and Esql.source_ip != "::1"
| stats Esql.alerts_count = COUNT(*),
        Esql.event_module_distinct_count = COUNT_DISTINCT(event.module),
        Esql.host_id_distinct_count = COUNT_DISTINCT(host.id),
        Esql.rule_name_distinct_count = COUNT_DISTINCT(kibana.alert.rule.name),
        Esql.event_module_values = VALUES(event.module),
        Esql.message_values = VALUES(message),
        Esql.rule_name = VALUES(kibana.alert.rule.name),
        Esql.event_action_values = VALUES(event.action),
        Esql.event_category_values = VALUES(event.category),
        Esql.process_executable_values = VALUES(process.executable),
        Esql.process_cmdline_values = VALUES(process.command_line),
        Esql.file_path_values = VALUES(file.path),
        Esql.host_id_values = VALUES(host.id),
        Esql.host_ip_values = VALUES(host.ip),
        Esql.destination_ip_values = VALUES(destination.ip),
        Esql.user_name_values = VALUES(user.name),
        SRC_IP = VALUES(source.ip)
        by Esql.source_ip

// filter for different alerts from multiple hosts and where the host.ip of one alert matches the source.ip of the other alert
| eval concat_ip_values = MV_CONCAT(TO_STRING(Esql.host_ip_values), ",")
| eval host_ip_equal_to_source_ip =LOCATE(concat_ip_values, TO_STRING(Esql.source_ip))
| where Esql.rule_name_distinct_count >= 2 and Esql.host_id_distinct_count >= 2 and host_ip_equal_to_source_ip > 0 and SRC_IP is not null and Esql.alerts_count <= 100

// Move single values to their corresponding ECS fields for alerts exclusion
| eval source.ip = mv_min(Esql.source_ip),
       host.id = mv_min(Esql.host_id_values)
| KEEP Esql.*, source.ip, host.id

Field Validations

Loading…

Comments (0)

Loading comments...