Skip to content

Commit b6aac59

Browse files
Merge branch 'esql-field-validation' of https://github.com/elastic/detection-rules into esql-field-validation
2 parents d18b493 + 9e1150c commit b6aac59

8 files changed

+75
-26
lines changed

detection_rules/rule.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,10 +1006,14 @@ class ThreatMatchRuleData(QueryRuleData):
10061006
@dataclass(frozen=True)
10071007
class Entries:
10081008
@dataclass(frozen=True)
1009-
class ThreatMapEntry:
1009+
class ThreatMapEntry(StackCompatMixin):
10101010
field: definitions.NonEmptyStr
10111011
type: Literal["mapping"]
10121012
value: definitions.NonEmptyStr
1013+
# Use dataclasses.field to avoid shadowing by attribute name "field"
1014+
negate: bool | None = dataclasses.field( # type: ignore[reportIncompatibleVariableOverride]
1015+
metadata={"metadata": {"min_compat": "9.2"}}
1016+
)
10131017

10141018
entries: list[ThreatMapEntry]
10151019

@@ -1042,6 +1046,38 @@ def validate_query(self, meta: RuleMeta) -> None:
10421046

10431047
threat_query_validator.validate(self, meta)
10441048

1049+
def validate(self, meta: RuleMeta) -> None: # noqa: ARG002
1050+
"""Validate negate usage and group semantics for threat mapping."""
1051+
1052+
for idx, group in enumerate(self.threat_mapping or []):
1053+
entries = group.entries or []
1054+
1055+
# Enforce: DOES NOT MATCH entries are allowed only if there is at least
1056+
# one MATCH (non-negated) entry in the same group
1057+
has_negate = any(bool(getattr(e, "negate", False)) for e in entries)
1058+
has_match = any(not bool(getattr(e, "negate", False)) for e in entries)
1059+
if has_negate and not has_match:
1060+
msg = (
1061+
f"threat_mapping group {idx}: DOES NOT MATCH entries require at least one MATCH "
1062+
"(non-negated) entry in the same group."
1063+
)
1064+
raise ValidationError(msg)
1065+
1066+
# Track negate presence per (source.field, indicator.field) pair to detect
1067+
# conflicts where both MATCH and DOES NOT MATCH are defined for the same pair
1068+
pair_to_negates: dict[tuple[str, str], set[bool]] = {}
1069+
for e in entries:
1070+
is_neg = bool(getattr(e, "negate", False))
1071+
pair_to_negates.setdefault((e.field, e.value), set()).add(is_neg)
1072+
1073+
for (src_field, ind_field), flags in pair_to_negates.items():
1074+
if True in flags and False in flags:
1075+
msg = (
1076+
f"threat_mapping group {idx}: cannot define both MATCH and DOES NOT MATCH for the same "
1077+
f"source and indicator fields: '{src_field}' <-> '{ind_field}'."
1078+
)
1079+
raise ValidationError(msg)
1080+
10451081

10461082
# All of the possible rule types
10471083
# Sort inverse of any inheritance - see comment in TOMLRuleContents.to_dict

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "detection_rules"
3-
version = "1.4.0"
3+
version = "1.5.0"
44
description = "Detection Rules is the home for rules used by Elastic Security. This repository is used for the development, maintenance, testing, validation, and release of rules for Elastic Security’s Detection Engine."
55
readme = "README.md"
66
requires-python = ">=3.12"

rules/linux/persistence_dbus_service_creation.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2025/01/16"
33
integration = ["endpoint", "sentinel_one_cloud_funnel"]
44
maturity = "production"
5-
updated_date = "2025/03/20"
5+
updated_date = "2025/09/09"
66

77
[rule]
88
author = ["Elastic"]
@@ -117,7 +117,6 @@ file.extension in ("service", "conf") and file.path like~ (
117117
"/usr/sbin/sshd", "/usr/bin/gitlab-runner", "/opt/gitlab/embedded/bin/ruby", "/usr/sbin/gdm", "/usr/bin/install",
118118
"/usr/local/manageengine/uems_agent/bin/dcregister"
119119
) or
120-
file.Ext.original.extension == "dpkg-new" or
121120
process.executable : (
122121
"/nix/store/*", "/var/lib/dpkg/*", "/tmp/vmis.*", "/snap/*", "/dev/fd/*", "/usr/lib/virtualbox/*"
123122
) or

rules/windows/command_and_control_common_llm_endpoint.toml

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

77

88
[rule]
@@ -78,7 +78,10 @@ network where host.os.type == "windows" and dns.question.name != null and
7878
7979
?process.code_signature.subject_name : ("AutoIt Consulting Ltd", "OpenJS Foundation", "Python Software Foundation") or
8080
81-
(process.executable : ("?:\\Users\\*.exe", "", "?:\\ProgramData\\*.exe") and ?process.code_signature.trusted != true)
81+
(
82+
process.executable : ("?:\\Users\\*.exe", "?:\\ProgramData\\*.exe") and
83+
(?process.code_signature.trusted == false or ?process.code_signature.exists == false)
84+
)
8285
) and
8386
dns.question.name : (
8487
// Major LLM APIs

rules/windows/command_and_control_dns_susp_tld.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2025/08/20"
33
integration = ["endpoint", "windows", "sentinel_one_cloud_funnel", "crowdstrike"]
44
maturity = "production"
5-
updated_date = "2025/08/20"
5+
updated_date = "2025/09/05"
66

77
[rule]
88
author = ["Elastic"]
@@ -77,9 +77,9 @@ network where host.os.type == "windows" and dns.question.name != null and
7777
process.name : ("MSBuild.exe", "mshta.exe", "wscript.exe", "powershell.exe", "pwsh.exe", "msiexec.exe", "rundll32.exe",
7878
"bitsadmin.exe", "InstallUtil.exe", "python.exe", "regsvr32.exe", "dllhost.exe", "node.exe",
7979
"java.exe", "javaw.exe", "*.pif", "*.com", "*.scr") or
80-
?process.code_signature.trusted != true or
80+
(?process.code_signature.trusted == false or ?process.code_signature.exists == false) or
8181
?process.code_signature.subject_name : ("AutoIt Consulting Ltd", "OpenJS Foundation", "Python Software Foundation") or
82-
process.executable : ("?:\\Users\\*.exe", "", "?:\\ProgramData\\*.exe")
82+
process.executable : ("?:\\Users\\*.exe", "?:\\ProgramData\\*.exe")
8383
) and
8484
dns.question.name regex """.*\.(top|buzz|xyz|rest|ml|cf|gq|ga|onion|monster|cyou|quest|cc|bar|cfd|click|cam|surf|tk|shop|club|icu|pw|ws|online|fun|life|boats|store|hair|skin|motorcycles|christmas|lol|makeup|mom|bond|beauty|biz|live|work|zip|country|accountant|date|party|science|loan|win|men|faith|review|racing|download|host)"""
8585
'''

rules/windows/command_and_control_remote_file_copy_powershell.toml

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

77
[transform]
88
[[transform.osquery]]
@@ -133,18 +133,29 @@ type = "eql"
133133

134134
query = '''
135135
sequence by process.entity_id with maxspan=30s
136-
137-
[network where host.os.type == "windows" and
138-
process.name : ("powershell.exe", "pwsh.exe", "powershell_ise.exe") and network.protocol == "dns" and
139-
not dns.question.name : (
140-
"localhost", "*.microsoft.com", "*.azureedge.net", "*.powershellgallery.com",
141-
"*.windowsupdate.com", "metadata.google.internal", "dist.nuget.org",
142-
"artifacts.elastic.co", "*.digicert.com", "packages.chocolatey.org",
143-
"outlook.office365.com"
144-
) and not user.id : "S-1-5-18"]
136+
[network where host.os.type == "windows" and
137+
process.name : ("powershell.exe", "pwsh.exe", "powershell_ise.exe") and
138+
network.protocol == "dns" and
139+
not dns.question.name : (
140+
"*.microsoft.com", "*.azureedge.net", "*.powershellgallery.com", "*.windowsupdate.com",
141+
"metadata.google.internal", "dist.nuget.org", "artifacts.elastic.co", "*.digicert.com",
142+
"*.chocolatey.org", "outlook.office365.com", "cdn.oneget.org", "ci.dot.net",
143+
"packages.icinga.com", "login.microsoftonline.com", "*.gov", "*.azure.com", "*.python.org",
144+
"dl.google.com", "sensor.cloud.tenable.com", "*.azurefd.net", "*.office.net", "*.anac*",
145+
"aka.ms", "dot.net", "*.visualstudio.com", "*.local") and
146+
not user.id == "S-1-5-18" and
147+
/* Filter out NetBIOS/LLMNR-style names (e.g. host, localhost, etc.) */
148+
dns.question.name regex """.*\.[a-zA-Z]{2,5}"""]
145149
[file where host.os.type == "windows" and event.type == "creation" and
146-
process.name : "powershell.exe" and file.extension : ("exe", "dll", "ps1", "bat") and
147-
not file.name : "__PSScriptPolicy*.ps1"]
150+
process.name : "powershell.exe" and
151+
(file.extension : ("exe", "dll", "ps1", "bat") or file.Ext.header_bytes : "4d5a*") and
152+
not file.name : "__PSScriptPolicy*.ps1" and
153+
not file.path : (
154+
"?:\\Users\\*\\AppData\\Local\\Temp\\????????.dll",
155+
"?:\\Users\\*\\AppData\\Local\\Temp\\*\\????????.dll",
156+
"?:\\Windows\\TEMP\\ansible-tmp-*\\AnsiballZ*.ps1"
157+
) and
158+
not user.id == "S-1-5-18"]
148159
'''
149160

150161

rules/windows/defense_evasion_untrusted_driver_loaded.toml

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

77
[transform]
88
[[transform.osquery]]
@@ -112,7 +112,7 @@ type = "eql"
112112

113113
query = '''
114114
driver where host.os.type == "windows" and process.pid == 4 and
115-
dll.code_signature.trusted != true and
115+
(dll.code_signature.trusted == false or dll.code_signature.exists == false) and
116116
not dll.code_signature.status : ("errorExpired", "errorRevoked", "errorCode_endpoint:*")
117117
'''
118118

rules/windows/discovery_host_public_ip_address_lookup.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2025/08/20"
33
integration = ["endpoint", "windows", "sentinel_one_cloud_funnel", "crowdstrike"]
44
maturity = "production"
5-
updated_date = "2025/08/20"
5+
updated_date = "2025/09/05"
66

77
[rule]
88
author = ["Elastic"]
@@ -78,11 +78,11 @@ network where host.os.type == "windows" and dns.question.name != null and
7878
"bitsadmin.exe", "InstallUtil.exe", "RegAsm.exe", "vbc.exe", "RegSvcs.exe", "python.exe", "regsvr32.exe", "dllhost.exe",
7979
"node.exe", "javaw.exe", "java.exe", "*.pif", "*.com") or
8080
81-
?process.code_signature.trusted != true or
81+
(?process.code_signature.trusted == false or ?process.code_signature.exists == false) or
8282
8383
?process.code_signature.subject_name : ("AutoIt Consulting Ltd", "OpenJS Foundation", "Python Software Foundation") or
8484
85-
?process.executable : ("?:\\Users\\*.exe", "", "?:\\ProgramData\\*.exe", "?\\Device\\HarddiskVolume?\\Users\\*.exe", "?\\Device\\HarddiskVolume?\\ProgramData\\*.exe")
85+
?process.executable : ("?:\\Users\\*.exe", "?:\\ProgramData\\*.exe", "?\\Device\\HarddiskVolume?\\Users\\*.exe", "?\\Device\\HarddiskVolume?\\ProgramData\\*.exe")
8686
) and
8787
dns.question.name :
8888
(

0 commit comments

Comments
 (0)