From a3431fcc38f36e13d88abd483c50e0ecc5a65b10 Mon Sep 17 00:00:00 2001 From: Samirbous <64742097+Samirbous@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:59:21 +0100 Subject: [PATCH 1/5] [New] Remediate Windows Top Persistence Methods 3 workflows that can be from the default Elastic Defend rule or scheduled to run every 15, correlate any Elastic defend behavior alert with the presence of a persistent object: - via startup folder - via Run key - via scheduled task Elastic defend behavior protection only support kill process or process tree, which leaves some remediation steps to the analyst, those workflows should close this response gap. Example of tests: --- examples/security/response/README.md | 5 +- ...remediate-persistence-windows-run-key.yaml | 147 ++++++++++++++++ ...te-persistence-windows-scheduled-task.yaml | 142 +++++++++++++++ ...te-persistence-windows-startup-folder.yaml | 161 ++++++++++++++++++ 4 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 examples/security/response/remediate-persistence-windows-run-key.yaml create mode 100644 examples/security/response/remediate-persistence-windows-scheduled-task.yaml create mode 100644 examples/security/response/remediate-persistence-windows-startup-folder.yaml diff --git a/examples/security/response/README.md b/examples/security/response/README.md index f025449..612e70c 100644 --- a/examples/security/response/README.md +++ b/examples/security/response/README.md @@ -2,10 +2,13 @@ Incident response, case management, and remediation workflows -## Workflows (4) +## Workflows (7) | Workflow | Description | |----------|-------------| +| [Remediate Startup Folder Persistence](./remediate-persistence-windows-startup-folder.yaml) | ES\|QL correlates user/ProgramData Startup persistence with endpoint alerts in 5m windows; skips if get-file exists in history, then downloads, waits 20s, and deletes | +| [Remediate Run Key Persistence](./remediate-persistence-windows-run-key.yaml) | ES\|QL correlates Run-key registry persistence with endpoint alerts in 5m windows; skips if delete exists in history, then removes the Run value via PowerShell | +| [Remediate Scheduled Task Persistence](./remediate-persistence-windows-scheduled-task.yaml) | ES\|QL correlates scheduled task creation with endpoint alerts in 5m windows; skips if schtasks delete exists in history, then removes the task | | [📁 Case workflow - Prod](./case-workflow-prod.yaml) | The YAML workflow outlines a security operations process that triggers on alerts | | [📁 Traditional Triage](./traditional-triage.yaml) | The "Traditional Triage" workflow automates the response to security alerts, par | | [🔒 AD - Automated Triaging](./ad-automated-triaging.yaml) | The YAML workflow outlines an automated triaging process for security operations | diff --git a/examples/security/response/remediate-persistence-windows-run-key.yaml b/examples/security/response/remediate-persistence-windows-run-key.yaml new file mode 100644 index 0000000..29811dc --- /dev/null +++ b/examples/security/response/remediate-persistence-windows-run-key.yaml @@ -0,0 +1,147 @@ +# ============================================================================= +# Workflow: Remediate Run Key Persistence +# Category: security/response +# +# Finds Run-key and Policies\Explorer\Run registry persistence via ES|QL on +# logs-endpoint.*, correlating with Malicious Behavior Prevention alerts within +# 5-minute windows on the same agent/host, then for each hit: +# 0. Checks newest execute in response history (pageSize 1) for same path. +# 1. execute — PowerShell Remove-ItemProperty on registry.hive\registry.key. +# +# ES|QL columns: [0] agent.id, [1] host.id, [2] registry.hive, [3] registry.key, +# [4] registry.value, [5] registry.path, [6] registry.data.strings +# +# registry.path ends with the value name; remediation uses registry.hive + key + value. +# +# The workflow is set to run every 15m; you can also attach it as a custom +# workflow action to the "Endpoint Security (Elastic Defend)" SIEM rule. +# ============================================================================= +version: "1" +name: Remediate Run Key Persistence +description: | + Query logs-endpoint.* for Run-key registry persistence correlated with endpoint + alerts within 5-minute windows, skip values already removed per response actions + history, then delete the Run value via PowerShell Remove-ItemProperty. +enabled: false +tags: + - security + - endpoint + - response + - remediation + - persistence + - registry + +triggers: + - type: manual + inputs: + - name: esql_query + type: string + description: ES|QL returning agent.id, host.id, registry.hive, registry.key, registry.value, registry.path, registry.data.strings per row. + required: true + default: | + FROM logs-endpoint.* + | WHERE @timestamp > NOW() - 1 hour and + (message like "Malicious Behavior Prevention Alert*" OR (event.category == "registry" and (registry.path like """*\\Windows\\CurrentVersion\\Run\\*""" or registry.path like """*CurrentVersion\\Policies\\Explorer\\Run\\*""") and registry.data.strings IS NOT NULL)) + | EVAL is_endpoint_alert = CASE(message like "Malicious Behavior Prevention Alert*", agent.id, NULL), is_run_persistence = CASE(event.category == "registry" and (registry.path like """*\\Windows\\CurrentVersion\\Run\\*""" or registry.path like """*CurrentVersion\\Policies\\Explorer\\Run\\*""") and registry.data.strings IS NOT NULL, agent.id, NULL) + | STATS endpoint_alerts_count = COUNT_DISTINCT(is_endpoint_alert), persistence_count = COUNT_DISTINCT(is_run_persistence), rules = VALUES(message), registry.hive = VALUES(registry.hive), registry.key = VALUES(registry.key), registry.value = VALUES(registry.value), registry.path = VALUES(registry.path), registry.data.strings = VALUES(registry.data.strings) by agent.id, host.id, DATE_TRUNC(5 minutes, @timestamp) + | WHERE endpoint_alerts_count >= 1 and persistence_count >= 1 + | STATS BY agent.id, host.id, registry.hive, registry.key, registry.value, registry.path, registry.data.strings + - name: history_lookback + type: string + description: How far back to search response actions history (Date Math, e.g. now-1d). + default: now-1d + - type: scheduled + with: + every: 15m + +steps: + - name: run_esql + type: elasticsearch.esql.query + with: + query: "{{ inputs.esql_query }}" + + - name: process_hits + type: if + condition: steps.run_esql.output.values.length > 0 + steps: + - name: per_persistence_hit + type: foreach + foreach: "{{ steps.run_esql.output.values }}" + steps: + - name: set_hit_context + type: data.set + with: + registry_path: "{{ foreach.item[5] | downcase }}" + reg_key: >- + {% if foreach.item[2] != '' and foreach.item[2] != null and foreach.item[3] != '' and foreach.item[3] != null %}{{ foreach.item[2] }}\{{ foreach.item[3] }}{% else %}{% assign rp = foreach.item[5] %}{% assign parts = rp | split: '\' %}{% assign key_path = '' %}{% for part in parts %}{% unless forloop.last %}{% if key_path != '' %}{% assign key_path = key_path | append: '\' %}{% endif %}{% assign key_path = key_path | append: part %}{% endunless %}{% endfor %}{{ key_path }}{% endif %} + reg_value: >- + {% if foreach.item[4] != '' and foreach.item[4] != null %}{{ foreach.item[4] }}{% else %}{% assign rp = foreach.item[5] %}{% assign parts = rp | split: '\' %}{{ parts | last }}{% endif %} + + - name: log_hit + type: console + with: + message: "Checking Run-key persistence — agent {% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}, key {{ steps.set_hit_context.output.reg_key }}, value {{ steps.set_hit_context.output.reg_value }} = {{ foreach.item[6] }} (row {{ foreach.index }})." + + - name: check_execute_history + type: kibana.request + with: + method: GET + path: "/api/endpoint/action" + query: + agentIds: "{% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}" + commands: execute + startDate: "{{ inputs.history_lookback }}" + endDate: now + pageSize: 1 + headers: + kbn-xsrf: "true" + on-failure: + continue: true + + - name: capture_history + type: data.set + with: + should_skip: "{% if steps.check_execute_history.output.total == 0 %}false{% else %}{% for action in steps.check_execute_history.output.data limit:1 %}{% assign rp = steps.set_hit_context.output.registry_path %}{% assign rk = steps.set_hit_context.output.reg_key | downcase %}{% assign rv = steps.set_hit_context.output.reg_value | downcase %}{% assign hc = action.parameters.command | downcase %}{% assign cm = action.comment | downcase %}{% if hc contains rp %}true{% elsif hc contains rk and hc contains rv %}true{% elsif cm contains rp %}true{% elsif cm contains rv and cm contains 'remediate run key' %}true{% else %}false{% endif %}{% endfor %}{% endif %}" + + - name: already_remediated + type: if + condition: 'steps.capture_history.output.should_skip: "true"' + steps: + - name: log_already_in_history + type: console + with: + message: "Skipping {{ foreach.item[5] }} on agent {% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %} — delete already in response actions history ({{ inputs.history_lookback }})." + else: + - name: delete_registry_value + type: kibana.request + with: + method: POST + path: "/api/endpoint/action/execute" + headers: + kbn-xsrf: "true" + body: + endpoint_ids: + - "{% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}" + comment: "Workflow Remediate Run Key Persistence — delete {{ steps.set_hit_context.output.reg_key }} value {{ steps.set_hit_context.output.reg_value }} ({{ foreach.item[6] }})." + parameters: + command: 'powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "Remove-ItemProperty -LiteralPath ''Registry::{{ steps.set_hit_context.output.reg_key }}'' -Name ''{{ steps.set_hit_context.output.reg_value }}'' -Force -ErrorAction Stop"' + timeout: 120 + on-failure: + continue: true + + - name: log_remediation_result + type: console + with: + message: "{% if steps.delete_registry_value.error %}Delete warning for {{ steps.set_hit_context.output.reg_key }}\\{{ steps.set_hit_context.output.reg_value }}: {{ steps.delete_registry_value.error }}{% else %}Remove-ItemProperty submitted for {{ steps.set_hit_context.output.reg_key }} value {{ steps.set_hit_context.output.reg_value }} (data: {{ foreach.item[6] }}).{% endif %}" + + else: + - name: log_no_hits + type: console + with: + message: "No Run-key persistence correlated with endpoint alerts in the configured ES|QL window." + + - name: log_summary + type: console + with: + message: | + Processed {{ steps.run_esql.output.values | size }} unique Run-key persistence target(s). Check Security → Endpoints → Response actions history. diff --git a/examples/security/response/remediate-persistence-windows-scheduled-task.yaml b/examples/security/response/remediate-persistence-windows-scheduled-task.yaml new file mode 100644 index 0000000..e7244fb --- /dev/null +++ b/examples/security/response/remediate-persistence-windows-scheduled-task.yaml @@ -0,0 +1,142 @@ +# ============================================================================= +# Workflow: Remediate Scheduled Task Persistence +# Category: security/response +# +# Finds scheduled task file creation under C:\Windows\System32\Tasks via ES|QL on +# logs-endpoint.*, correlating with Malicious Behavior Prevention alerts within +# 5-minute windows on the same agent/host, then for each hit: +# 0. Checks newest execute in response history (pageSize 1) for same task. +# 1. execute — schtasks /Delete using file.name as the task name. +# +# ES|QL columns: [0] agent.id, [1] host.id, [2] file.path, [3] file.name +# +# The workflow is set to run every 15m; you can also attach it as a custom +# workflow action to the "Endpoint Security (Elastic Defend)" SIEM rule. +# ============================================================================= +version: "1" +name: Remediate Scheduled Task Persistence +description: | + Query logs-endpoint.* for scheduled task persistence correlated with endpoint + alerts within 5-minute windows, skip tasks already deleted per response actions + history, then remove the task via schtasks /Delete. +enabled: false +tags: + - security + - endpoint + - response + - remediation + - persistence + - scheduled-task + +triggers: + - type: manual + inputs: + - name: esql_query + type: string + description: ES|QL returning agent.id, host.id, file.path, file.name per row. + required: true + default: | + FROM logs-endpoint.* + | WHERE @timestamp > NOW() - 15 minutes and + (message like "Malicious Behavior Prevention Alert*" OR (event.category == "file" and process.name == "svchost.exe" and file.path like """C:\\Windows\\System32\\Tasks\\*""" and MV_CONTAINS(event.action, "creation"))) + | EVAL is_endpoint_alert = CASE(message like "Malicious Behavior Prevention Alert*", agent.id, NULL), is_task_persistence = CASE(event.category == "file" and process.name == "svchost.exe" and file.path like """C:\\Windows\\System32\\Tasks\\*""", agent.id, NULL) + | STATS endpoint_alerts_count = COUNT_DISTINCT(is_endpoint_alert), persistence_count = COUNT_DISTINCT(is_task_persistence), file.path = VALUES(file.path), file.name = VALUES(file.name) by agent.id, host.id, DATE_TRUNC(5 minutes, @timestamp) + | WHERE endpoint_alerts_count >= 1 and persistence_count >= 1 + | STATS BY agent.id, host.id, file.path, file.name + - name: history_lookback + type: string + description: How far back to search response actions history (Date Math, e.g. now-1d). + default: now-1d + - type: scheduled + with: + every: 15m + +steps: + - name: run_esql + type: elasticsearch.esql.query + with: + query: "{{ inputs.esql_query }}" + + - name: process_hits + type: if + condition: steps.run_esql.output.values.length > 0 + steps: + - name: per_persistence_hit + type: foreach + foreach: "{{ steps.run_esql.output.values }}" + steps: + - name: set_hit_context + type: data.set + with: + file_path: "{{ foreach.item[2] | downcase }}" + task_name: >- + {% if foreach.item[3] != '' and foreach.item[3] != null %}{{ foreach.item[3] }}{% else %}{% assign fp = foreach.item[2] %}{% assign parts = fp | split: '\' %}{{ parts | last }}{% endif %} + + - name: log_hit + type: console + with: + message: "Checking scheduled task persistence — agent {% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}, task {{ steps.set_hit_context.output.task_name }}, path {{ foreach.item[2] }} (row {{ foreach.index }})." + + - name: check_execute_history + type: kibana.request + with: + method: GET + path: "/api/endpoint/action" + query: + agentIds: "{% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}" + commands: execute + startDate: "{{ inputs.history_lookback }}" + endDate: now + pageSize: 1 + headers: + kbn-xsrf: "true" + on-failure: + continue: true + + - name: capture_history + type: data.set + with: + should_skip: "{% if steps.check_execute_history.output.total == 0 %}false{% else %}{% for action in steps.check_execute_history.output.data limit:1 %}{% assign fp = steps.set_hit_context.output.file_path %}{% assign tn = steps.set_hit_context.output.task_name | downcase %}{% assign hc = action.parameters.command | downcase %}{% assign cm = action.comment | downcase %}{% if hc contains fp %}true{% elsif hc contains tn and hc contains 'schtasks' %}true{% elsif cm contains fp %}true{% elsif cm contains tn and cm contains 'scheduled task' %}true{% else %}false{% endif %}{% endfor %}{% endif %}" + + - name: already_remediated + type: if + condition: 'steps.capture_history.output.should_skip: "true"' + steps: + - name: log_already_in_history + type: console + with: + message: "Skipping task {{ steps.set_hit_context.output.task_name }} on agent {% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %} — delete already in response actions history ({{ inputs.history_lookback }})." + else: + - name: delete_scheduled_task + type: kibana.request + with: + method: POST + path: "/api/endpoint/action/execute" + headers: + kbn-xsrf: "true" + body: + endpoint_ids: + - "{% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}" + comment: "Workflow Remediate Scheduled Task Persistence — delete task {{ steps.set_hit_context.output.task_name }} ({{ foreach.item[2] }})." + parameters: + command: 'schtasks /Delete /TN "{{ steps.set_hit_context.output.task_name }}" /F' + timeout: 120 + on-failure: + continue: true + + - name: log_remediation_result + type: console + with: + message: "{% if steps.delete_scheduled_task.error %}Delete warning for task {{ steps.set_hit_context.output.task_name }}: {{ steps.delete_scheduled_task.error }}{% else %}schtasks /Delete submitted for {{ steps.set_hit_context.output.task_name }} (path: {{ foreach.item[2] }}).{% endif %}" + + else: + - name: log_no_hits + type: console + with: + message: "No scheduled task persistence correlated with endpoint alerts in the configured ES|QL window." + + - name: log_summary + type: console + with: + message: | + Processed {{ steps.run_esql.output.values | size }} unique scheduled task persistence target(s). Check Security → Endpoints → Response actions history. diff --git a/examples/security/response/remediate-persistence-windows-startup-folder.yaml b/examples/security/response/remediate-persistence-windows-startup-folder.yaml new file mode 100644 index 0000000..eaf76e7 --- /dev/null +++ b/examples/security/response/remediate-persistence-windows-startup-folder.yaml @@ -0,0 +1,161 @@ +# ============================================================================= +# Workflow: Remediate Startup Persistence +# Category: security/response +# +# Finds Startup-folder persistence (user AppData + ProgramData) via ES|QL on +# logs-endpoint.*, correlating persistence with Malicious Behavior Prevention +# alerts within 5-minute windows on the same agent/host, then for each hit: +# 0. Checks newest get-file in response history (pageSize 1) for same file.path. +# 1. get-file — downloads file.path (zip in response actions history). +# 2. execute — deletes file.path from Startup (after 20s wait). +# +# ES|QL columns: [0] agent.id, [1] host.id, [2] file.path +# +# The workflow is set to run every 15m, you can also attach it as a custom workflow action to the "Endpoint Security (Elastic Defend)" SIEM rule so it triggers everytime a behavior alert is generated. +# +# ============================================================================= +version: "1" +name: Remediate Startup Persistence +description: | + Query logs-endpoint.* for user and ProgramData Startup-folder persistence + correlated with endpoint alerts within 5-minute windows, skip hosts/files already in response actions + history, download via get-file, wait 20s, then delete the startup file. +enabled: false +tags: + - security + - endpoint + - response + - remediation + - persistence + +triggers: + - type: manual + inputs: + - name: esql_query + type: string + description: ES|QL returning agent.id, host.id, file.path per row. + required: true + default: | + FROM logs-endpoint.* + | WHERE @timestamp > NOW() - 1 hour and + (message like "Malicious Behavior Prevention Alert*" OR TO_LOWER(file.path) LIKE """?:\\users\\*\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\startup\\*""" OR TO_LOWER(file.path) LIKE """?:\\programdata\\microsoft\\windows\\start menu\\programs\\startup\\*""") + | EVAL is_endpoint_alert = CASE(message like "Malicious Behavior Prevention Alert*", agent.id, NULL), is_startup_persistence = CASE(TO_LOWER(file.path) LIKE """?:\\users\\*\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\startup\\*""" OR TO_LOWER(file.path) LIKE """?:\\programdata\\microsoft\\windows\\start menu\\programs\\startup\\*""", agent.id, NULL) + | STATS endpoint_alerts_count = COUNT_DISTINCT(is_endpoint_alert), persistence_count = COUNT_DISTINCT(is_startup_persistence), rules = VALUES(message), file.path = VALUES(file.path) by agent.id, host.id, DATE_TRUNC(5 minutes, @timestamp) + | WHERE endpoint_alerts_count >= 1 and persistence_count >= 1 + | STATS BY agent.id, host.id, file.path + - name: history_lookback + type: string + description: How far back to search response actions history (Date Math, e.g. now-1d). + default: now-1d + - type: scheduled + with: + every: 15m + +steps: + - name: run_esql + type: elasticsearch.esql.query + with: + query: "{{ inputs.esql_query }}" + + - name: process_hits + type: if + condition: steps.run_esql.output.values.length > 0 + steps: + - name: per_persistence_hit + type: foreach + foreach: "{{ steps.run_esql.output.values }}" + steps: + - name: set_hit_context + type: data.set + with: + file_path: "{{ foreach.item[2] | downcase }}" + + - name: log_hit + type: console + with: + message: "Checking startup persistence — agent {% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}, file {{ foreach.item[2] }} (row {{ foreach.index }})." + + - name: check_get_file_history + type: kibana.request + with: + method: GET + path: "/api/endpoint/action" + query: + agentIds: "{% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}" + commands: get-file + startDate: "{{ inputs.history_lookback }}" + endDate: now + pageSize: 1 + headers: + kbn-xsrf: "true" + on-failure: + continue: true + + - name: capture_history + type: data.set + with: + should_skip: "{% if steps.check_get_file_history.output.total == 0 %}false{% else %}{% for action in steps.check_get_file_history.output.data limit:1 %}{% assign fp = steps.set_hit_context.output.file_path %}{% assign hp = action.parameters.path | downcase %}{% assign hc = action.comment | downcase %}{% if hp == fp %}true{% elsif hc contains fp %}true{% else %}false{% endif %}{% endfor %}{% endif %}" + + - name: already_archived + type: if + condition: 'steps.capture_history.output.should_skip: "true"' + steps: + - name: log_already_in_history + type: console + with: + message: "Skipping {{ foreach.item[2] }} on agent {% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %} — get-file already in response actions history ({{ inputs.history_lookback }})." + else: + - name: download_file + type: kibana.request + with: + method: POST + path: "/api/endpoint/action/get_file" + headers: + kbn-xsrf: "true" + body: + endpoint_ids: + - "{% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}" + comment: "Workflow Remediate Startup Persistence — download {{ foreach.item[2] }}." + parameters: + path: "{{ foreach.item[2] }}" + on-failure: + continue: true + + - name: wait_for_download + type: wait + with: + duration: 20s + + - name: delete_startup_file + type: kibana.request + with: + method: POST + path: "/api/endpoint/action/execute" + headers: + kbn-xsrf: "true" + body: + endpoint_ids: + - "{% if foreach.item[0] != '' and foreach.item[0] != null %}{{ foreach.item[0] }}{% else %}{{ foreach.item[1] }}{% endif %}" + comment: "Workflow Remediate Startup Persistence — remove startup file {{ foreach.item[2] }}." + parameters: + command: 'if exist "{{ foreach.item[2] }}" del /f /q "{{ foreach.item[2] }}"' + timeout: 120 + on-failure: + continue: true + + - name: log_remediation_result + type: console + with: + message: "{% if steps.download_file.error %}Download warning for {{ foreach.item[2] }}: {{ steps.download_file.error }}{% else %}get-file submitted for {{ foreach.item[2] }} — download link will appear in response actions history.{% endif %} {% if steps.delete_startup_file.error %}Delete warning: {{ steps.delete_startup_file.error }}{% else %}Delete command submitted for {{ foreach.item[2] }}.{% endif %}" + + else: + - name: log_no_hits + type: console + with: + message: "No startup persistence alerts found in the configured ES|QL window." + + - name: log_summary + type: console + with: + message: | + Processed {{ steps.run_esql.output.values | size }} unique startup persistence target(s). Check Security → Endpoints → Response actions history. From def30a2539e6a23829bf38825f7e226bcbf8f5f9 Mon Sep 17 00:00:00 2001 From: Samirbous <64742097+Samirbous@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:06:37 +0100 Subject: [PATCH 2/5] Update remediate-persistence-windows-run-key.yaml --- .../response/remediate-persistence-windows-run-key.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/security/response/remediate-persistence-windows-run-key.yaml b/examples/security/response/remediate-persistence-windows-run-key.yaml index 29811dc..5c3a6a6 100644 --- a/examples/security/response/remediate-persistence-windows-run-key.yaml +++ b/examples/security/response/remediate-persistence-windows-run-key.yaml @@ -40,7 +40,7 @@ triggers: required: true default: | FROM logs-endpoint.* - | WHERE @timestamp > NOW() - 1 hour and + | WHERE @timestamp > NOW() - 15 minutes and (message like "Malicious Behavior Prevention Alert*" OR (event.category == "registry" and (registry.path like """*\\Windows\\CurrentVersion\\Run\\*""" or registry.path like """*CurrentVersion\\Policies\\Explorer\\Run\\*""") and registry.data.strings IS NOT NULL)) | EVAL is_endpoint_alert = CASE(message like "Malicious Behavior Prevention Alert*", agent.id, NULL), is_run_persistence = CASE(event.category == "registry" and (registry.path like """*\\Windows\\CurrentVersion\\Run\\*""" or registry.path like """*CurrentVersion\\Policies\\Explorer\\Run\\*""") and registry.data.strings IS NOT NULL, agent.id, NULL) | STATS endpoint_alerts_count = COUNT_DISTINCT(is_endpoint_alert), persistence_count = COUNT_DISTINCT(is_run_persistence), rules = VALUES(message), registry.hive = VALUES(registry.hive), registry.key = VALUES(registry.key), registry.value = VALUES(registry.value), registry.path = VALUES(registry.path), registry.data.strings = VALUES(registry.data.strings) by agent.id, host.id, DATE_TRUNC(5 minutes, @timestamp) From ad548223c8323a17edbae7a0fb6b0f59e590b84c Mon Sep 17 00:00:00 2001 From: Samirbous <64742097+Samirbous@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:07:06 +0100 Subject: [PATCH 3/5] Update remediate-persistence-windows-startup-folder.yaml --- .../response/remediate-persistence-windows-startup-folder.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/security/response/remediate-persistence-windows-startup-folder.yaml b/examples/security/response/remediate-persistence-windows-startup-folder.yaml index eaf76e7..17305e2 100644 --- a/examples/security/response/remediate-persistence-windows-startup-folder.yaml +++ b/examples/security/response/remediate-persistence-windows-startup-folder.yaml @@ -37,7 +37,7 @@ triggers: required: true default: | FROM logs-endpoint.* - | WHERE @timestamp > NOW() - 1 hour and + | WHERE @timestamp > NOW() - 15 minutes and (message like "Malicious Behavior Prevention Alert*" OR TO_LOWER(file.path) LIKE """?:\\users\\*\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\startup\\*""" OR TO_LOWER(file.path) LIKE """?:\\programdata\\microsoft\\windows\\start menu\\programs\\startup\\*""") | EVAL is_endpoint_alert = CASE(message like "Malicious Behavior Prevention Alert*", agent.id, NULL), is_startup_persistence = CASE(TO_LOWER(file.path) LIKE """?:\\users\\*\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\startup\\*""" OR TO_LOWER(file.path) LIKE """?:\\programdata\\microsoft\\windows\\start menu\\programs\\startup\\*""", agent.id, NULL) | STATS endpoint_alerts_count = COUNT_DISTINCT(is_endpoint_alert), persistence_count = COUNT_DISTINCT(is_startup_persistence), rules = VALUES(message), file.path = VALUES(file.path) by agent.id, host.id, DATE_TRUNC(5 minutes, @timestamp) From a459e7132ddca2be35378ba66c13fcac45108b77 Mon Sep 17 00:00:00 2001 From: Samirbous <64742097+Samirbous@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:09:09 +0100 Subject: [PATCH 4/5] Update remediate-persistence-windows-startup-folder.yaml --- .../response/remediate-persistence-windows-startup-folder.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/security/response/remediate-persistence-windows-startup-folder.yaml b/examples/security/response/remediate-persistence-windows-startup-folder.yaml index 17305e2..1cdb61b 100644 --- a/examples/security/response/remediate-persistence-windows-startup-folder.yaml +++ b/examples/security/response/remediate-persistence-windows-startup-folder.yaml @@ -11,7 +11,7 @@ # # ES|QL columns: [0] agent.id, [1] host.id, [2] file.path # -# The workflow is set to run every 15m, you can also attach it as a custom workflow action to the "Endpoint Security (Elastic Defend)" SIEM rule so it triggers everytime a behavior alert is generated. +# The workflow is set to run every 15m, you can also attach it as a custom workflow action to the "Endpoint Security (Elastic Defend)" https://www.elastic.co/guide/en/security/8.19/endpoint-security-elastic-defend.html SIEM rule so it triggers everytime a behavior alert is generated. # # ============================================================================= version: "1" From e9efad4b34a510a0d07e4755f135d82da524ef3f Mon Sep 17 00:00:00 2001 From: Samirbous <64742097+Samirbous@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:09:52 +0100 Subject: [PATCH 5/5] Update remediate-persistence-windows-run-key.yaml --- .../response/remediate-persistence-windows-run-key.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/security/response/remediate-persistence-windows-run-key.yaml b/examples/security/response/remediate-persistence-windows-run-key.yaml index 5c3a6a6..07bec36 100644 --- a/examples/security/response/remediate-persistence-windows-run-key.yaml +++ b/examples/security/response/remediate-persistence-windows-run-key.yaml @@ -13,8 +13,7 @@ # # registry.path ends with the value name; remediation uses registry.hive + key + value. # -# The workflow is set to run every 15m; you can also attach it as a custom -# workflow action to the "Endpoint Security (Elastic Defend)" SIEM rule. +## The workflow is set to run every 15m, you can also attach it as a custom workflow action to the "Endpoint Security (Elastic Defend)" https://www.elastic.co/guide/en/security/8.19/endpoint-security-elastic-defend.html SIEM rule so it triggers everytime a behavior alert is generated. # ============================================================================= version: "1" name: Remediate Run Key Persistence