[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>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
creation_date = "2026/02/10"
|
||||
integration = ["endpoint"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/02/10"
|
||||
updated_date = "2026/03/09"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
@@ -73,7 +73,7 @@ type = "eql"
|
||||
query = '''
|
||||
sequence by host.id with maxspan=5m
|
||||
[any where event.dataset == "endpoint.alerts"]
|
||||
![any where event.category in ("process", "library", "registry", "network", "dns")]
|
||||
![any where event.category in ("process", "library", "registry", "network", "dns", "file")]
|
||||
'''
|
||||
|
||||
[[rule.threat]]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[metadata]
|
||||
creation_date = "2026/01/26"
|
||||
maturity = "production"
|
||||
updated_date = "2026/02/16"
|
||||
updated_date = "2026/03/09"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
@@ -78,6 +78,7 @@ FROM metrics-*, .alerts-security.* METADATA _index
|
||||
process_path = VALUES(process.executable),
|
||||
parent_process_path = VALUES(process.parent.executable),
|
||||
user_name = VALUES(user.name),
|
||||
host_name = VALUES(host.name),
|
||||
cmdline = VALUES(process.command_line) by process.pid, process.name, host.id
|
||||
| where pid_with_cpu_spike > 0 and pid_with_alerts > 0
|
||||
// populate fields to use in rule exceptions
|
||||
@@ -85,8 +86,12 @@ FROM metrics-*, .alerts-security.* METADATA _index
|
||||
process.executable = MV_FIRST(process_path),
|
||||
process.parent.executable = MV_FIRST(parent_process_path),
|
||||
process.command_line = MV_FIRST(cmdline),
|
||||
user.name = MV_FIRST(user_name)
|
||||
| KEEP user.name, host.id, process.*, Esql.*
|
||||
user.name = MV_FIRST(user_name),
|
||||
host.name = MV_FIRST(host_name)
|
||||
| KEEP user.name, host.id, host.name, process.*, Esql.*
|
||||
| where `process.executable` != "C:\\Program Files\\ESET\\ESET Security\\ekrn.exe" and
|
||||
`process.executable` != "C:\\Windows\\System32\\CompatTelRunner.exe" and
|
||||
`process.executable` != "C:\\Program Files\\UiPath\\Studio\\UiPath.ActivityCompiler.CommandLine.exe"
|
||||
'''
|
||||
note = """## Triage and analysis
|
||||
|
||||
|
||||
+3
-2
@@ -2,7 +2,7 @@
|
||||
creation_date = "2026/02/20"
|
||||
integration = ["fortinet_fortigate"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/02/20"
|
||||
updated_date = "2026/03/09"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
@@ -35,7 +35,8 @@ type = "eql"
|
||||
|
||||
query = '''
|
||||
sequence by user.name with maxspan=10m
|
||||
[authentication where event.dataset == "fortinet_fortigate.log" and event.action == "login" and event.code in ("0101039426", "0101039427")]
|
||||
[authentication where event.dataset == "fortinet_fortigate.log" and event.action == "login" and event.code in ("0101039426", "0101039427") and
|
||||
user.name != "root"]
|
||||
[any where event.kind == "signal" and kibana.alert.rule.name != null and event.dataset != "fortinet_fortigate.log" and
|
||||
kibana.alert.risk_score > 21 and kibana.alert.rule.rule_id != "a7f2c1b4-5d8e-4f3a-9b0c-2e1d4a6b8f3e" and user.name != null]
|
||||
'''
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
creation_date = "2026/03/03"
|
||||
integration = ["endpoint", "windows"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/03/10"
|
||||
updated_date = "2026/03/23"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
@@ -10,12 +10,9 @@ description = """
|
||||
Detects DNS queries to commonly abused remote monitoring and management (RMM) or remote access software domains from processes that are not browsers.
|
||||
Intended to surface RMM clients, scripts, or other non-browser activity contacting these services.
|
||||
"""
|
||||
from = "now-9m"
|
||||
index = [
|
||||
"logs-endpoint.events.network-*",
|
||||
"logs-windows.sysmon_operational-*",
|
||||
]
|
||||
language = "kuery"
|
||||
from = "now-7205m"
|
||||
interval = "5m"
|
||||
language = "esql"
|
||||
license = "Elastic License v2"
|
||||
name = "First Time Seen DNS Query to RMM Domain"
|
||||
note = """## Triage and analysis
|
||||
@@ -26,10 +23,10 @@ This rule flags DNS queries to commonly abused RMM or remote access domains when
|
||||
|
||||
### Possible investigation steps
|
||||
|
||||
- Identify the process (process.name, process.executable) that performed the DNS query and verify if it is an approved RMM or remote access tool.
|
||||
- Identify the process process.executable that performed the DNS query and verify if it is an approved RMM or remote access tool.
|
||||
- Review the full process tree and parent process to understand how the binary was launched.
|
||||
- Check process.code_signature for trusted RMM publishers; unsigned or unexpected signers may indicate abuse or trojanized installers.
|
||||
- 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.
|
||||
- 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.
|
||||
- Investigate other alerts for the same host or user in the past 48 hours.
|
||||
|
||||
### False positive analysis
|
||||
@@ -59,13 +56,125 @@ tags = [
|
||||
"Data Source: Sysmon"
|
||||
]
|
||||
timestamp_override = "event.ingested"
|
||||
type = "new_terms"
|
||||
type = "esql"
|
||||
|
||||
query = '''
|
||||
host.os.type: "windows" and
|
||||
event.category: "network" and
|
||||
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
|
||||
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)
|
||||
FROM logs-endpoint.events.network-*, logs-windows.sysmon_operational-* METADATA _index
|
||||
| WHERE host.os.type == "windows"
|
||||
AND event.category == "network"
|
||||
AND event.action in ("lookup_requested", "DNSEvent (DNS query)")
|
||||
AND dns.question.name IS NOT NULL
|
||||
|
||||
// Exclude browser processes
|
||||
| WHERE NOT
|
||||
process.name IN (
|
||||
"chrome.exe", "msedge.exe", "MicrosoftEdge.exe", "MicrosoftEdgeCP.exe",
|
||||
"firefox.exe", "iexplore.exe", "safari.exe", "brave.exe",
|
||||
"opera.exe", "vivaldi.exe", "msedgewebview2.exe"
|
||||
)
|
||||
|
||||
// Extract the parent domain (last two labels, e.g. example.com)
|
||||
| GROK dns.question.name """(?:[^.]+\.)+(?<parent_domain>[^.]+\.[^.]+)$"""
|
||||
| EVAL parent_domain = COALESCE(parent_domain, dns.question.name)
|
||||
|
||||
// Known RMM parent domains, add or remove entries here as your environment changes.
|
||||
| WHERE parent_domain IN (
|
||||
"teamviewer.com",
|
||||
"logmein.com",
|
||||
"logmeinrescue.com",
|
||||
"logmeininc.com",
|
||||
"internapcdn.net",
|
||||
"anydesk.com",
|
||||
"screenconnect.com",
|
||||
"connectwise.com",
|
||||
"splashtop.com",
|
||||
"zohoassist.com",
|
||||
"dwservice.net",
|
||||
"gotoassist.com",
|
||||
"getgo.com",
|
||||
"logmeinrescue.com",
|
||||
"rustdesk.com",
|
||||
"remoteutilities.com",
|
||||
"atera.com",
|
||||
"ammyy.com",
|
||||
"n-able.com",
|
||||
"kaseya.net",
|
||||
"bomgar.com",
|
||||
"beyondtrustcloud.com",
|
||||
"parsec.app",
|
||||
"parsecusercontent.com",
|
||||
"tailscale.com",
|
||||
"twingate.com",
|
||||
"jumpcloud.com",
|
||||
"vnc.com",
|
||||
"remotepc.com",
|
||||
"netsupportsoftware.com",
|
||||
"getscreen.me",
|
||||
"beanywhere.com",
|
||||
"swi-rc.com",
|
||||
"swi-tc.com",
|
||||
"qetqo.com",
|
||||
"tmate.io",
|
||||
"playanext.com",
|
||||
"supremocontrol.com",
|
||||
"itarian.com",
|
||||
"datto.com",
|
||||
"auvik.com",
|
||||
"syncromsp.com",
|
||||
"pulseway.com",
|
||||
"immy.bot",
|
||||
"immybot.com",
|
||||
"level.io",
|
||||
"ninjarmm.com",
|
||||
"ninjaone.com",
|
||||
"centrastage.net",
|
||||
"datto.net",
|
||||
"liongard.com",
|
||||
"naverisk.com",
|
||||
"panorama9.com",
|
||||
"superops.ai",
|
||||
"superops.com",
|
||||
"tacticalrmm.com",
|
||||
"meshcentral.com",
|
||||
"remotly.com",
|
||||
"fixme.it",
|
||||
"islonline.com",
|
||||
"zoho.eu",
|
||||
"goverlan.com",
|
||||
"iperius.net",
|
||||
"iperiusremote.com",
|
||||
"remotix.com",
|
||||
"mikogo.com",
|
||||
"r-hud.net",
|
||||
"pcvisit.de",
|
||||
"netviewer.com",
|
||||
"helpwire.app",
|
||||
"remotetopc.com",
|
||||
"rport.io",
|
||||
"action1.com",
|
||||
"tiflux.com",
|
||||
"gotoresolve.com"
|
||||
)
|
||||
|
||||
// Aggregate by parent domain and get 1st time seen timestamp as well as unique count of agents
|
||||
| STATS
|
||||
event_count = COUNT(*),
|
||||
Esql.first_time_seen = MIN(@timestamp),
|
||||
Esql.count_distinct_host_id = COUNT_DISTINCT(host.id),
|
||||
Esql.process_executable_values = VALUES(process.executable),
|
||||
Esql.dns_question_name_values = VALUES(dns.question.name),
|
||||
Esql.host_name_values = VALUES(host.name) BY parent_domain
|
||||
|
||||
// Calculate the time difference between first time seen and rule execution time
|
||||
| eval Esql.recent = DATE_DIFF("minute", Esql.first_time_seen, now())
|
||||
|
||||
// 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
|
||||
| where Esql.recent <= 6 and Esql.count_distinct_host_id == 1
|
||||
|
||||
// populate fields for rule exception
|
||||
| eval host.name = MV_FIRST(Esql.host_name_values),
|
||||
process.executable = MV_FIRST(Esql.process_executable_values), dns.question.name = MV_FIRST(Esql.dns_question_name_values)
|
||||
| keep host.name, process.executable, dns.question.name, Esql.*
|
||||
'''
|
||||
|
||||
|
||||
@@ -85,9 +194,3 @@ id = "TA0011"
|
||||
name = "Command and Control"
|
||||
reference = "https://attack.mitre.org/tactics/TA0011/"
|
||||
|
||||
[rule.new_terms]
|
||||
field = "new_terms_fields"
|
||||
value = ["host.id", "dns.question.name"]
|
||||
[[rule.new_terms.history_window_start]]
|
||||
field = "history_window_start"
|
||||
value = "now-7d"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
creation_date = "2020/08/29"
|
||||
integration = ["system", "windows"]
|
||||
maturity = "production"
|
||||
updated_date = "2025/12/11"
|
||||
updated_date = "2026/03/09"
|
||||
|
||||
[transform]
|
||||
[[transform.osquery]]
|
||||
@@ -33,21 +33,21 @@ authenticode.path JOIN hash ON services.path = hash.path WHERE authenticode.resu
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
description = """
|
||||
Identifies multiple consecutive logon failures targeting an Admin account from the same source address and within a
|
||||
Identifies multiple consecutive logon failures targeting more than one Admin account from the same source address and within a
|
||||
short time interval. Adversaries will often brute force login attempts across multiple users with a common or known
|
||||
password, in an attempt to gain access to accounts.
|
||||
"""
|
||||
from = "now-9m"
|
||||
language = "esql"
|
||||
license = "Elastic License v2"
|
||||
name = "Privileged Account Brute Force"
|
||||
name = "Privileged Accounts Brute Force"
|
||||
note = """## Triage and analysis
|
||||
|
||||
### Investigating Privileged Account Brute Force
|
||||
### Investigating Privileged Accounts Brute Force
|
||||
|
||||
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/).
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
> **Note**:
|
||||
> 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,
|
||||
not winlog.event_data.Status in ("0xc000015b", "0xc000005e", "0xc0000133", "0xc0000192", "0xc00000dc")
|
||||
// truncate the timestamp to a 60-second window
|
||||
| eval Esql.time_window = date_trunc(60 seconds, @timestamp)
|
||||
| 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
|
||||
| where Esql.failed_auth_count >= 50
|
||||
| KEEP winlog.computer_name, source.ip, Esql.time_window, winlog.logon.type, Esql.*
|
||||
| stats Esql.failed_auth_count = COUNT(*),
|
||||
Esql.target_user_name_values = VALUES(winlog.event_data.TargetUserName),
|
||||
Esql.count_distinct_user_name = count_distinct(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
|
||||
| where Esql.failed_auth_count >= 50 and Esql.count_distinct_user_name >= 2
|
||||
| eval user.name = mv_first(Esql.target_user_name_values)
|
||||
| KEEP winlog.computer_name, source.ip, user.name, Esql.time_window, winlog.logon.type, Esql.*
|
||||
'''
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
creation_date = "2020/08/29"
|
||||
integration = ["system", "windows"]
|
||||
maturity = "production"
|
||||
updated_date = "2025/12/11"
|
||||
updated_date = "2026/03/09"
|
||||
|
||||
[transform]
|
||||
[[transform.osquery]]
|
||||
@@ -136,9 +136,15 @@ from logs-system.security*, logs-windows.forwarded*, winlogbeat-* metadata _id,
|
||||
not winlog.event_data.Status in ("0xc000015b", "0xc000005e", "0xc0000133", "0xc0000192", "0xc00000dc")
|
||||
// truncate the timestamp to a 60-second window
|
||||
| eval Esql.time_window = date_trunc(60 seconds, @timestamp)
|
||||
| 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
|
||||
| where Esql.failed_auth_count >= 100
|
||||
| KEEP winlog.computer_name, source.ip, Esql.time_window, winlog.logon.type, Esql.*
|
||||
| stats Esql.failed_auth_count = COUNT(*),
|
||||
Esql.count_distinct_target_user_name = count_distinct(winlog.event_data.TargetUserName),
|
||||
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
|
||||
| where Esql.failed_auth_count >= 100 and Esql.count_distinct_target_user_name >= 2
|
||||
| eval user.name = MV_FIRST(Esql.target_user_name_values)
|
||||
| KEEP winlog.computer_name, source.ip, user.name, Esql.time_window, winlog.logon.type, Esql.*
|
||||
'''
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
creation_date = "2026/02/16"
|
||||
integration = ["endpoint", "windows", "m365_defender", "sentinel_one_cloud_funnel"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/02/16"
|
||||
updated_date = "2026/03/09"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
@@ -74,7 +74,8 @@ type = "eql"
|
||||
|
||||
query = '''
|
||||
process where host.os.type == "windows" and event.type == "start" and
|
||||
process.parent.name : "notepad.exe" and process.parent.args : "*.md"
|
||||
process.parent.name : "notepad.exe" and process.parent.args : "*.md" and
|
||||
not process.executable : "C:\\Program Files\\WindowsApps\\Microsoft.WindowsNotepad_*\\Notepad\\Notepad.exe"
|
||||
'''
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
creation_date = "2026/02/25"
|
||||
integration = ["system", "windows"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/02/25"
|
||||
updated_date = "2026/03/23"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
@@ -12,8 +12,8 @@ suddenly showing successful logons using a different logon type with low count.
|
||||
takeover or use of stolen credentials from a new context (e.g. interactive or network logon where only batch/service
|
||||
was expected).
|
||||
"""
|
||||
from = "now-30m"
|
||||
interval = "15m"
|
||||
from = "now-15m"
|
||||
interval = "14m"
|
||||
language = "esql"
|
||||
license = "Elastic License v2"
|
||||
name = "Potential Account Takeover - Mixed Logon Types"
|
||||
@@ -60,17 +60,19 @@ from logs-system.security*, logs-windows.forwarded*, winlogbeat-* metadata _id,
|
||||
| WHERE event.category == "authentication" and event.action == "logged-in" and winlog.event_id == "4624" and
|
||||
event.outcome == "success" and not user.id in ("S-1-5-18", "S-1-5-19", "S-1-5-20") and
|
||||
to_lower(user.name) != "administrator"
|
||||
| STATS logon_count = COUNT(*) by user.name, winlog.logon.type
|
||||
| STATS logon_count = COUNT(*), host_names = VALUES(host.name) by user.name, user.id, winlog.logon.type
|
||||
| STATS
|
||||
Esql.max_logon = MAX(logon_count),
|
||||
Esql.min_logon = MIN(logon_count),
|
||||
Esql.unique_host_count = COUNT_DISTINCT(host_names),
|
||||
Esql.host_name_values = VALUES(host_names),
|
||||
Esql.logon_type_values = VALUES(winlog.logon.type),
|
||||
Esql.count_distinct_logon_types = COUNT_DISTINCT(winlog.logon.type) by user.name
|
||||
Esql.count_distinct_logon_types = COUNT_DISTINCT(winlog.logon.type) by user.name, user.id
|
||||
|
||||
// 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
|
||||
| WHERE Esql.count_distinct_logon_types >= 2 and Esql.max_logon >= 1000 and (Esql.min_logon >= 1 and Esql.min_logon <= 10)
|
||||
| EVAL winlog.logon.type = MV_FIRST(Esql.logon_type_values)
|
||||
| KEEP user.name, winlog.logon.type, Esql.*
|
||||
| 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
|
||||
| EVAL winlog.logon.type = MV_FIRST(Esql.logon_type_values), host.name = MV_FIRST(Esql.host_name_values)
|
||||
| KEEP user.name, user.id, host.name, winlog.logon.type, Esql.*
|
||||
'''
|
||||
|
||||
[[rule.threat]]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
creation_date = "2026/02/25"
|
||||
integration = ["system", "windows"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/02/25"
|
||||
updated_date = "2026/03/23"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
@@ -11,8 +11,8 @@ Identifies a user account that normally logs in with high volume from one source
|
||||
source IP. This pattern (one IP with many successful logons, another IP with very few) may indicate account takeover
|
||||
or use of stolen credentials from a new location.
|
||||
"""
|
||||
from = "now-30m"
|
||||
interval = "15m"
|
||||
from = "now-15m"
|
||||
interval = "14m"
|
||||
language = "esql"
|
||||
license = "Elastic License v2"
|
||||
name = "Potential Account Takeover - Logon from New Source IP"
|
||||
@@ -59,17 +59,19 @@ from logs-system.security*, logs-windows.forwarded*, winlogbeat-* metadata _id,
|
||||
| where event.category == "authentication" and event.action == "logged-in" and winlog.event_id == "4624" and
|
||||
event.outcome == "success" and winlog.logon.type in ("Network", "RemoteInteractive") and
|
||||
source.ip is not null and source.ip != "127.0.0.1" and not to_string(source.ip) like "*::*" and not user.name like "*$"
|
||||
| stats logon_count = COUNT(*) by user.name, source.ip
|
||||
| stats logon_count = COUNT(*), host_names = VALUES(host.name) by user.name, user.id, source.ip
|
||||
| stats
|
||||
Esql.max_logon = MAX(logon_count),
|
||||
Esql.min_logon = MIN(logon_count),
|
||||
Esql.unique_host_count = COUNT_DISTINCT(host_names),
|
||||
Esql.host_name_values = VALUES(host_names),
|
||||
Esql.source_ip_values = VALUES(source.ip),
|
||||
Esql.count_distinct = COUNT_DISTINCT(source.ip) by user.name
|
||||
Esql.count_distinct_source_ip = COUNT_DISTINCT(source.ip) by user.name, user.id
|
||||
|
||||
// high count of logons is often associated with service account tied to a specific source.ip, if observed in use from a new source.ip it's suspicious
|
||||
| where Esql.max_logon >= 1000 and (Esql.min_logon >= 1 and Esql.min_logon <= 5) and Esql.count_distinct == 2
|
||||
| eval source.ip = mv_first(Esql.source_ip_values)
|
||||
| KEEP user.name, source.ip, Esql.*
|
||||
| where Esql.max_logon >= 1000 and (Esql.min_logon >= 1 and Esql.min_logon <= 5) and Esql.count_distinct_source_ip == 2 and Esql.unique_host_count >= 2
|
||||
| eval source.ip = MV_FIRST(Esql.source_ip_values), host.name = MV_FIRST(Esql.host_name_values)
|
||||
| KEEP user.name, user.id, host.name, source.ip, Esql.*
|
||||
'''
|
||||
|
||||
[[rule.threat]]
|
||||
|
||||
Reference in New Issue
Block a user