Skip to content

Commit 7bde0a9

Browse files
[Tuning] Mis Rules Tuning (#5817)
* [Tuning] Mis Rules Tuning tuning of recently created or tuned rules. * Apply suggestion from @Mikaayenson Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com> * Update command_and_control_dns_rmm_domains_non_browser.toml * Update credential_access_bruteforce_admin_account.toml * ++ * ++ --------- Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com>
1 parent 5216bf2 commit 7bde0a9

9 files changed

Lines changed: 183 additions & 57 deletions

rules/cross-platform/defense_evasion_missing_events_after_alert.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2026/02/10"
33
integration = ["endpoint"]
44
maturity = "production"
5-
updated_date = "2026/02/10"
5+
updated_date = "2026/03/09"
66

77
[rule]
88
author = ["Elastic"]
@@ -73,7 +73,7 @@ type = "eql"
7373
query = '''
7474
sequence by host.id with maxspan=5m
7575
[any where event.dataset == "endpoint.alerts"]
76-
![any where event.category in ("process", "library", "registry", "network", "dns")]
76+
![any where event.category in ("process", "library", "registry", "network", "dns", "file")]
7777
'''
7878

7979
[[rule.threat]]

rules/cross-platform/impact_alert_from_a_process_with_cpu_spike.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[metadata]
22
creation_date = "2026/01/26"
33
maturity = "production"
4-
updated_date = "2026/02/16"
4+
updated_date = "2026/03/09"
55

66
[rule]
77
author = ["Elastic"]
@@ -78,15 +78,20 @@ FROM metrics-*, .alerts-security.* METADATA _index
7878
process_path = VALUES(process.executable),
7979
parent_process_path = VALUES(process.parent.executable),
8080
user_name = VALUES(user.name),
81+
host_name = VALUES(host.name),
8182
cmdline = VALUES(process.command_line) by process.pid, process.name, host.id
8283
| where pid_with_cpu_spike > 0 and pid_with_alerts > 0
8384
// populate fields to use in rule exceptions
8485
| eval process.hash.sha256 = MV_FIRST(Esql.process_hash_sha256),
8586
process.executable = MV_FIRST(process_path),
8687
process.parent.executable = MV_FIRST(parent_process_path),
8788
process.command_line = MV_FIRST(cmdline),
88-
user.name = MV_FIRST(user_name)
89-
| KEEP user.name, host.id, process.*, Esql.*
89+
user.name = MV_FIRST(user_name),
90+
host.name = MV_FIRST(host_name)
91+
| KEEP user.name, host.id, host.name, process.*, Esql.*
92+
| where `process.executable` != "C:\\Program Files\\ESET\\ESET Security\\ekrn.exe" and
93+
`process.executable` != "C:\\Windows\\System32\\CompatTelRunner.exe" and
94+
`process.executable` != "C:\\Program Files\\UiPath\\Studio\\UiPath.ActivityCompiler.CommandLine.exe"
9095
'''
9196
note = """## Triage and analysis
9297

rules/cross-platform/initial_access_fortigate_ssl_vpn_login_followed_by_siem_alert.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2026/02/20"
33
integration = ["fortinet_fortigate"]
44
maturity = "production"
5-
updated_date = "2026/02/20"
5+
updated_date = "2026/03/09"
66

77
[rule]
88
author = ["Elastic"]
@@ -35,7 +35,8 @@ type = "eql"
3535

3636
query = '''
3737
sequence by user.name with maxspan=10m
38-
[authentication where event.dataset == "fortinet_fortigate.log" and event.action == "login" and event.code in ("0101039426", "0101039427")]
38+
[authentication where event.dataset == "fortinet_fortigate.log" and event.action == "login" and event.code in ("0101039426", "0101039427") and
39+
user.name != "root"]
3940
[any where event.kind == "signal" and kibana.alert.rule.name != null and event.dataset != "fortinet_fortigate.log" and
4041
kibana.alert.risk_score > 21 and kibana.alert.rule.rule_id != "a7f2c1b4-5d8e-4f3a-9b0c-2e1d4a6b8f3e" and user.name != null]
4142
'''

rules/windows/command_and_control_dns_rmm_domains_non_browser.toml

Lines changed: 123 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,17 @@
22
creation_date = "2026/03/03"
33
integration = ["endpoint", "windows"]
44
maturity = "production"
5-
updated_date = "2026/03/10"
5+
updated_date = "2026/03/23"
66

77
[rule]
88
author = ["Elastic"]
99
description = """
1010
Detects DNS queries to commonly abused remote monitoring and management (RMM) or remote access software domains from processes that are not browsers.
1111
Intended to surface RMM clients, scripts, or other non-browser activity contacting these services.
1212
"""
13-
from = "now-9m"
14-
index = [
15-
"logs-endpoint.events.network-*",
16-
"logs-windows.sysmon_operational-*",
17-
]
18-
language = "kuery"
13+
from = "now-7205m"
14+
interval = "5m"
15+
language = "esql"
1916
license = "Elastic License v2"
2017
name = "First Time Seen DNS Query to RMM Domain"
2118
note = """## Triage and analysis
@@ -26,10 +23,10 @@ This rule flags DNS queries to commonly abused RMM or remote access domains when
2623
2724
### Possible investigation steps
2825
29-
- Identify the process (process.name, process.executable) that performed the DNS query and verify if it is an approved RMM or remote access tool.
26+
- Identify the process process.executable that performed the DNS query and verify if it is an approved RMM or remote access tool.
3027
- Review the full process tree and parent process to understand how the binary was launched.
3128
- Check process.code_signature for trusted RMM publishers; unsigned or unexpected signers may indicate abuse or trojanized installers.
32-
- Correlate with the companion rule "First Time Seen Commonly Abused RMM Execution" for the same host to see if the RMM process was first-time seen.
29+
- Correlate with the companion rule "First Time Seen Remote Monitoring and Management Tool" for the same host to see if the RMM process was first-time seen.
3330
- Investigate other alerts for the same host or user in the past 48 hours.
3431
3532
### False positive analysis
@@ -59,13 +56,125 @@ tags = [
5956
"Data Source: Sysmon"
6057
]
6158
timestamp_override = "event.ingested"
62-
type = "new_terms"
59+
type = "esql"
6360

6461
query = '''
65-
host.os.type: "windows" and
66-
event.category: "network" and
67-
dns.question.name: (*teamviewer.com or *logmein* or *.anydesk.com or *screenconnect.com or *connectwise.com or *splashtop.com or assist.zoho.com or zohoassist.com or downloads.zohocdn.com or join.zoho.com or dwservice.net or express.gotoassist.com or getgo.com or *rustdesk.com or rs-* or remoteutilities.com or app.atera.com or agentreporting.atera.com or pubsub.atera.com or ammyy.com or n-able.com or cdn.kaseya.net or relay.kaseya.net or license.bomgar.com or beyondtrustcloud.com or api.parsec.app or parsecusercontent.com or tailscale.com or twingate.com or agent.jumpcloud.com or kickstart.jumpcloud.com or services.vnc.com or static.remotepc.com or netsupportsoftware.com or getscreen.me or client.teamviewer.com or integratedchat.teamviewer.com or relay.screenconnect.com or control.connectwise.com or authentication.logmeininc.com or secure.logmeinrescue.com or logmeincdn.http.internapcdn.net or remoteassistance.support.services.microsoft.com or remotedesktop-pa.googleapis.com or comserver.corporate.beanywhere.com or swi-rc.com or swi-tc.com or telemetry.servers.qetqo.com or tmate.io or api.playanext.com) and not process.name: (chrome.exe or msedge.exe or MicrosoftEdge.exe or MicrosoftEdgeCP.exe or firefox.exe or iexplore.exe or safari.exe or brave.exe or opera.exe or vivaldi.exe or msedgewebview2.exe or agent.tiflux.com or *.gotoresolve.com) and
68-
not (process.code_signature.subject_name: ("Google LLC" or "Google Inc." or "Mozilla Corporation" or "Mozilla Foundation" or "Microsoft Corporation" or "Apple Inc." or "Brave Software, Inc." or "Opera Software AS" or "Vivaldi Technologies AS") and process.code_signature.trusted: true)
62+
FROM logs-endpoint.events.network-*, logs-windows.sysmon_operational-* METADATA _index
63+
| WHERE host.os.type == "windows"
64+
AND event.category == "network"
65+
AND event.action in ("lookup_requested", "DNSEvent (DNS query)")
66+
AND dns.question.name IS NOT NULL
67+
68+
// Exclude browser processes
69+
| WHERE NOT
70+
process.name IN (
71+
"chrome.exe", "msedge.exe", "MicrosoftEdge.exe", "MicrosoftEdgeCP.exe",
72+
"firefox.exe", "iexplore.exe", "safari.exe", "brave.exe",
73+
"opera.exe", "vivaldi.exe", "msedgewebview2.exe"
74+
)
75+
76+
// Extract the parent domain (last two labels, e.g. example.com)
77+
| GROK dns.question.name """(?:[^.]+\.)+(?<parent_domain>[^.]+\.[^.]+)$"""
78+
| EVAL parent_domain = COALESCE(parent_domain, dns.question.name)
79+
80+
// Known RMM parent domains, add or remove entries here as your environment changes.
81+
| WHERE parent_domain IN (
82+
"teamviewer.com",
83+
"logmein.com",
84+
"logmeinrescue.com",
85+
"logmeininc.com",
86+
"internapcdn.net",
87+
"anydesk.com",
88+
"screenconnect.com",
89+
"connectwise.com",
90+
"splashtop.com",
91+
"zohoassist.com",
92+
"dwservice.net",
93+
"gotoassist.com",
94+
"getgo.com",
95+
"logmeinrescue.com",
96+
"rustdesk.com",
97+
"remoteutilities.com",
98+
"atera.com",
99+
"ammyy.com",
100+
"n-able.com",
101+
"kaseya.net",
102+
"bomgar.com",
103+
"beyondtrustcloud.com",
104+
"parsec.app",
105+
"parsecusercontent.com",
106+
"tailscale.com",
107+
"twingate.com",
108+
"jumpcloud.com",
109+
"vnc.com",
110+
"remotepc.com",
111+
"netsupportsoftware.com",
112+
"getscreen.me",
113+
"beanywhere.com",
114+
"swi-rc.com",
115+
"swi-tc.com",
116+
"qetqo.com",
117+
"tmate.io",
118+
"playanext.com",
119+
"supremocontrol.com",
120+
"itarian.com",
121+
"datto.com",
122+
"auvik.com",
123+
"syncromsp.com",
124+
"pulseway.com",
125+
"immy.bot",
126+
"immybot.com",
127+
"level.io",
128+
"ninjarmm.com",
129+
"ninjaone.com",
130+
"centrastage.net",
131+
"datto.net",
132+
"liongard.com",
133+
"naverisk.com",
134+
"panorama9.com",
135+
"superops.ai",
136+
"superops.com",
137+
"tacticalrmm.com",
138+
"meshcentral.com",
139+
"remotly.com",
140+
"fixme.it",
141+
"islonline.com",
142+
"zoho.eu",
143+
"goverlan.com",
144+
"iperius.net",
145+
"iperiusremote.com",
146+
"remotix.com",
147+
"mikogo.com",
148+
"r-hud.net",
149+
"pcvisit.de",
150+
"netviewer.com",
151+
"helpwire.app",
152+
"remotetopc.com",
153+
"rport.io",
154+
"action1.com",
155+
"tiflux.com",
156+
"gotoresolve.com"
157+
)
158+
159+
// Aggregate by parent domain and get 1st time seen timestamp as well as unique count of agents
160+
| STATS
161+
event_count = COUNT(*),
162+
Esql.first_time_seen = MIN(@timestamp),
163+
Esql.count_distinct_host_id = COUNT_DISTINCT(host.id),
164+
Esql.process_executable_values = VALUES(process.executable),
165+
Esql.dns_question_name_values = VALUES(dns.question.name),
166+
Esql.host_name_values = VALUES(host.name) BY parent_domain
167+
168+
// Calculate the time difference between first time seen and rule execution time
169+
| eval Esql.recent = DATE_DIFF("minute", Esql.first_time_seen, now())
170+
171+
// First time seen is within 6m of the rule execution time and first seen in the last 5 days as per the rule from schedule and limited to 1 unique host
172+
| where Esql.recent <= 6 and Esql.count_distinct_host_id == 1
173+
174+
// populate fields for rule exception
175+
| eval host.name = MV_FIRST(Esql.host_name_values),
176+
process.executable = MV_FIRST(Esql.process_executable_values), dns.question.name = MV_FIRST(Esql.dns_question_name_values)
177+
| keep host.name, process.executable, dns.question.name, Esql.*
69178
'''
70179

71180

@@ -85,9 +194,3 @@ id = "TA0011"
85194
name = "Command and Control"
86195
reference = "https://attack.mitre.org/tactics/TA0011/"
87196

88-
[rule.new_terms]
89-
field = "new_terms_fields"
90-
value = ["host.id", "dns.question.name"]
91-
[[rule.new_terms.history_window_start]]
92-
field = "history_window_start"
93-
value = "now-7d"

rules/windows/credential_access_bruteforce_admin_account.toml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2020/08/29"
33
integration = ["system", "windows"]
44
maturity = "production"
5-
updated_date = "2025/12/11"
5+
updated_date = "2026/03/09"
66

77
[transform]
88
[[transform.osquery]]
@@ -33,21 +33,21 @@ authenticode.path JOIN hash ON services.path = hash.path WHERE authenticode.resu
3333
[rule]
3434
author = ["Elastic"]
3535
description = """
36-
Identifies multiple consecutive logon failures targeting an Admin account from the same source address and within a
36+
Identifies multiple consecutive logon failures targeting more than one Admin account from the same source address and within a
3737
short time interval. Adversaries will often brute force login attempts across multiple users with a common or known
3838
password, in an attempt to gain access to accounts.
3939
"""
4040
from = "now-9m"
4141
language = "esql"
4242
license = "Elastic License v2"
43-
name = "Privileged Account Brute Force"
43+
name = "Privileged Accounts Brute Force"
4444
note = """## Triage and analysis
4545
46-
### Investigating Privileged Account Brute Force
46+
### Investigating Privileged Accounts Brute Force
4747
4848
Adversaries with no prior knowledge of legitimate credentials within the system or environment may guess passwords to attempt access to accounts. Without knowledge of the password for an account, an adversary may opt to guess the password using a repetitive or iterative mechanism systematically. More details can be found [here](https://attack.mitre.org/techniques/T1110/001/).
4949
50-
This rule identifies potential password guessing/brute force activity from a single address against an account that contains the `admin` pattern on its name, which is likely a highly privileged account.
50+
This rule identifies potential password guessing/brute force activity from a single address against multiple accounts that contains the `admin` pattern on its name, which is likely a highly privileged account.
5151
5252
> **Note**:
5353
> This investigation guide uses the [Osquery Markdown Plugin](https://www.elastic.co/guide/en/security/current/invest-guide-run-osquery.html) introduced in Elastic Stack version 8.5.0. Older Elastic Stack versions will display unrendered Markdown in this guide.
@@ -121,9 +121,15 @@ from logs-system.security*, logs-windows.forwarded*, winlogbeat-* metadata _id,
121121
not winlog.event_data.Status in ("0xc000015b", "0xc000005e", "0xc0000133", "0xc0000192", "0xc00000dc")
122122
// truncate the timestamp to a 60-second window
123123
| eval Esql.time_window = date_trunc(60 seconds, @timestamp)
124-
| stats Esql.failed_auth_count = COUNT(*), Esql.target_user_name_values = VALUES(winlog.event_data.TargetUserName), Esql.user_domain_values = VALUES(user.domain), Esql.error_codes = VALUES(winlog.event_data.Status), Esql.data_stream_namespace.values = VALUES(data_stream.namespace) by winlog.computer_name, source.ip, Esql.time_window, winlog.logon.type
125-
| where Esql.failed_auth_count >= 50
126-
| KEEP winlog.computer_name, source.ip, Esql.time_window, winlog.logon.type, Esql.*
124+
| stats Esql.failed_auth_count = COUNT(*),
125+
Esql.target_user_name_values = VALUES(winlog.event_data.TargetUserName),
126+
Esql.count_distinct_user_name = count_distinct(winlog.event_data.TargetUserName),
127+
Esql.user_domain_values = VALUES(user.domain),
128+
Esql.error_codes = VALUES(winlog.event_data.Status),
129+
Esql.data_stream_namespace.values = VALUES(data_stream.namespace) by winlog.computer_name, source.ip, Esql.time_window, winlog.logon.type
130+
| where Esql.failed_auth_count >= 50 and Esql.count_distinct_user_name >= 2
131+
| eval user.name = mv_first(Esql.target_user_name_values)
132+
| KEEP winlog.computer_name, source.ip, user.name, Esql.time_window, winlog.logon.type, Esql.*
127133
'''
128134

129135

rules/windows/credential_access_bruteforce_multiple_logon_failure_same_srcip.toml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2020/08/29"
33
integration = ["system", "windows"]
44
maturity = "production"
5-
updated_date = "2025/12/11"
5+
updated_date = "2026/03/09"
66

77
[transform]
88
[[transform.osquery]]
@@ -136,9 +136,15 @@ from logs-system.security*, logs-windows.forwarded*, winlogbeat-* metadata _id,
136136
not winlog.event_data.Status in ("0xc000015b", "0xc000005e", "0xc0000133", "0xc0000192", "0xc00000dc")
137137
// truncate the timestamp to a 60-second window
138138
| eval Esql.time_window = date_trunc(60 seconds, @timestamp)
139-
| stats Esql.failed_auth_count = COUNT(*), Esql.target_user_name_values = VALUES(winlog.event_data.TargetUserName), Esql.user_domain_values = VALUES(user.domain), Esql.error_codes = VALUES(winlog.event_data.Status), Esql.data_stream_namespace.values = VALUES(data_stream.namespace) by winlog.computer_name, source.ip, Esql.time_window, winlog.logon.type
140-
| where Esql.failed_auth_count >= 100
141-
| KEEP winlog.computer_name, source.ip, Esql.time_window, winlog.logon.type, Esql.*
139+
| stats Esql.failed_auth_count = COUNT(*),
140+
Esql.count_distinct_target_user_name = count_distinct(winlog.event_data.TargetUserName),
141+
Esql.target_user_name_values = VALUES(winlog.event_data.TargetUserName),
142+
Esql.user_domain_values = VALUES(user.domain),
143+
Esql.error_codes = VALUES(winlog.event_data.Status),
144+
Esql.data_stream_namespace.values = VALUES(data_stream.namespace) by winlog.computer_name, source.ip, Esql.time_window, winlog.logon.type
145+
| where Esql.failed_auth_count >= 100 and Esql.count_distinct_target_user_name >= 2
146+
| eval user.name = MV_FIRST(Esql.target_user_name_values)
147+
| KEEP winlog.computer_name, source.ip, user.name, Esql.time_window, winlog.logon.type, Esql.*
142148
'''
143149

144150

rules/windows/execution_notepad_markdown_child_process.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2026/02/16"
33
integration = ["endpoint", "windows", "m365_defender", "sentinel_one_cloud_funnel"]
44
maturity = "production"
5-
updated_date = "2026/02/16"
5+
updated_date = "2026/03/09"
66

77
[rule]
88
author = ["Elastic"]
@@ -74,7 +74,8 @@ type = "eql"
7474

7575
query = '''
7676
process where host.os.type == "windows" and event.type == "start" and
77-
process.parent.name : "notepad.exe" and process.parent.args : "*.md"
77+
process.parent.name : "notepad.exe" and process.parent.args : "*.md" and
78+
not process.executable : "C:\\Program Files\\WindowsApps\\Microsoft.WindowsNotepad_*\\Notepad\\Notepad.exe"
7879
'''
7980

8081

rules/windows/privilege_escalation_account_takeover_mixed_logon_types.toml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2026/02/25"
33
integration = ["system", "windows"]
44
maturity = "production"
5-
updated_date = "2026/02/25"
5+
updated_date = "2026/03/23"
66

77
[rule]
88
author = ["Elastic"]
@@ -12,8 +12,8 @@ suddenly showing successful logons using a different logon type with low count.
1212
takeover or use of stolen credentials from a new context (e.g. interactive or network logon where only batch/service
1313
was expected).
1414
"""
15-
from = "now-30m"
16-
interval = "15m"
15+
from = "now-15m"
16+
interval = "14m"
1717
language = "esql"
1818
license = "Elastic License v2"
1919
name = "Potential Account Takeover - Mixed Logon Types"
@@ -60,17 +60,19 @@ from logs-system.security*, logs-windows.forwarded*, winlogbeat-* metadata _id,
6060
| WHERE event.category == "authentication" and event.action == "logged-in" and winlog.event_id == "4624" and
6161
event.outcome == "success" and not user.id in ("S-1-5-18", "S-1-5-19", "S-1-5-20") and
6262
to_lower(user.name) != "administrator"
63-
| STATS logon_count = COUNT(*) by user.name, winlog.logon.type
63+
| STATS logon_count = COUNT(*), host_names = VALUES(host.name) by user.name, user.id, winlog.logon.type
6464
| STATS
6565
Esql.max_logon = MAX(logon_count),
6666
Esql.min_logon = MIN(logon_count),
67+
Esql.unique_host_count = COUNT_DISTINCT(host_names),
68+
Esql.host_name_values = VALUES(host_names),
6769
Esql.logon_type_values = VALUES(winlog.logon.type),
68-
Esql.count_distinct_logon_types = COUNT_DISTINCT(winlog.logon.type) by user.name
70+
Esql.count_distinct_logon_types = COUNT_DISTINCT(winlog.logon.type) by user.name, user.id
6971
7072
// high count of logons is often associated with service account tied to a specific service, if observed in use with a different logon type it's suspicious
71-
| WHERE Esql.count_distinct_logon_types >= 2 and Esql.max_logon >= 1000 and (Esql.min_logon >= 1 and Esql.min_logon <= 10)
72-
| EVAL winlog.logon.type = MV_FIRST(Esql.logon_type_values)
73-
| KEEP user.name, winlog.logon.type, Esql.*
73+
| WHERE Esql.count_distinct_logon_types >= 2 and Esql.max_logon >= 1000 and (Esql.min_logon >= 1 and Esql.min_logon <= 10) and Esql.unique_host_count >= 2
74+
| EVAL winlog.logon.type = MV_FIRST(Esql.logon_type_values), host.name = MV_FIRST(Esql.host_name_values)
75+
| KEEP user.name, user.id, host.name, winlog.logon.type, Esql.*
7476
'''
7577

7678
[[rule.threat]]

0 commit comments

Comments
 (0)