Skip to content

Commit 1dc3926

Browse files
authored
[New Rules] External Promotion Alerts (#4903)
1 parent f2fac1b commit 1dc3926

15 files changed

+670
-12
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ Our rules should be written generically when possible. We use [Elastic Common Sc
169169

170170
If the relevant [categorization values](https://www.elastic.co/guide/en/ecs/current/ecs-category-field-values-reference.html) are already defined for ECS, we use these to narrow down the event type before adding the query. Typically, the query starts with the broadest grouping possible and gets narrower for each clause. For example, we might write `event.category:process and event.type:start and process.name:net.exe and process.args:group`. First, we match process events with `event.category`, then narrow to creation events with `event.type`. Of the process creation events, we're looking for the process `net.exe` with `process.name` and finally we check the arguments `group` by looking at `process.args`. This flow has little effect on the generated Elasticsearch query, but is the most intuitive to read for rule developers.
171171

172-
Sometimes, it might not make sense for ECS to standardize a field, value, or category. Occasionally, we may encounter fields that specific to a single use-case or vendor. When that happens, we add an exception in [detection_rules/etc/non-ecs-schema.json](detection_rules/etc/non-ecs-schema.json). We automatically detect beats by looking at the index patterns used in a rule. If we see `winlogbeat-*`, for example, then we can validate the rule against ECS + Winlogbeat. When using a particular beat, please use `event.module` and `event.dataset` to make the rule more precise and to better nudge the validation logic. Similar to our logic flow for ECS categorization, we recommend searches progress from `event.module``event.dataset``event.action``<additional criteria>`.
172+
Sometimes, it might not make sense for ECS to standardize a field, value, or category. Occasionally, we may encounter fields that specific to a single use-case or vendor. When that happens, we add an exception in [detection_rules/etc/non-ecs-schema.json](detection_rules/etc/non-ecs-schema.json). We automatically detect beats by looking at the index patterns used in a rule. If we see `winlogbeat-*`, for example, then we can validate the rule against ECS + Winlogbeat. When using a particular beat, please use `event.module` and `data_stream.dataset` to make the rule more precise and to better nudge the validation logic. Similar to our logic flow for ECS categorization, we recommend searches progress from `event.module``data_stream.dataset``event.action``<additional criteria>`.
173173

174174
When a Pull Request is missing a necessary ECS change, please add an issue to [elastic/ecs](https://github.com/elastic/ecs) and link it from the pull request. We don't want to leave PRs blocked for too long, so if the ECS issue isn't progressing, then we can add a note and use the vendor- or beat-specific fields. We'll create another issue, reminding us to update the rule logic to switch to the ECS field when it becomes available. To maximize compatibility, we may add an `or` clause for a release or two to handle the different permutatations. After a few releases, we'll remove this and strictly require the ECS fields.
175175

detection_rules/beats.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def get_datasets_and_modules(tree: eql.ast.BaseNode | kql.ast.BaseNode) -> tuple
256256
modules: set[str] = set()
257257
datasets: set[str] = set()
258258

259-
# extract out event.module and event.dataset from the query's AST
259+
# extract out event.module, data_stream.dataset, and event.dataset from the query's AST
260260
for node in tree: # type: ignore[reportUnknownVariableType]
261261
if (
262262
isinstance(node, eql.ast.Comparison)
@@ -265,17 +265,23 @@ def get_datasets_and_modules(tree: eql.ast.BaseNode | kql.ast.BaseNode) -> tuple
265265
):
266266
if node.left == eql.ast.Field("event", ["module"]):
267267
modules.add(node.right.render()) # type: ignore[reportUnknownMemberType]
268-
elif node.left == eql.ast.Field("event", ["dataset"]):
268+
elif node.left == eql.ast.Field("event", ["dataset"]) or node.left == eql.ast.Field(
269+
"data_stream", ["dataset"]
270+
):
269271
datasets.add(node.right.render()) # type: ignore[reportUnknownMemberType]
270272
elif isinstance(node, eql.ast.InSet):
271273
if node.expression == eql.ast.Field("event", ["module"]):
272274
modules.update(node.get_literals()) # type: ignore[reportUnknownMemberType]
273-
elif node.expression == eql.ast.Field("event", ["dataset"]):
275+
elif node.expression == eql.ast.Field("event", ["dataset"]) or node.expression == eql.ast.Field(
276+
"data_stream", ["dataset"]
277+
):
274278
datasets.update(node.get_literals()) # type: ignore[reportUnknownMemberType]
275279
elif isinstance(node, kql.ast.FieldComparison) and node.field == kql.ast.Field("event.module"): # type: ignore[reportUnknownMemberType]
276280
modules.update(child.value for child in node.value if isinstance(child, kql.ast.String)) # type: ignore[reportUnknownMemberType, reportUnknownVariableType]
277281
elif isinstance(node, kql.ast.FieldComparison) and node.field == kql.ast.Field("event.dataset"): # type: ignore[reportUnknownMemberType]
278282
datasets.update(child.value for child in node.value if isinstance(child, kql.ast.String)) # type: ignore[reportUnknownMemberType, reportUnknownVariableType]
283+
elif isinstance(node, kql.ast.FieldComparison) and node.field == kql.ast.Field("data_stream.dataset"): # type: ignore[reportUnknownMemberType]
284+
datasets.update(child.value for child in node.value if isinstance(child, kql.ast.String)) # type: ignore[reportUnknownMemberType]
279285

280286
return datasets, modules
281287

992 Bytes
Binary file not shown.
51.5 KB
Binary file not shown.

detection_rules/rule.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1428,7 +1428,7 @@ def get_packaged_integrations(
14281428
datasets, _ = beats.get_datasets_and_modules(data.get("ast") or []) # type: ignore[reportArgumentType]
14291429

14301430
# integration is None to remove duplicate references upstream in Kibana
1431-
# chronologically, event.dataset is checked for package:integration, then rule tags
1431+
# chronologically, event.dataset, data_stream.dataset is checked for package:integration, then rule tags
14321432
# if both exist, rule tags are only used if defined in definitions for non-dataset packages
14331433
# of machine learning analytic packages
14341434

detection_rules/rule_validators.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def validate_stack_combos(self, data: QueryRuleData, meta: RuleMeta) -> KQL_ERRO
188188
message = exc.error_msg
189189
trailer = err_trailer
190190
if "Unknown field" in message and beat_types:
191-
trailer = f"\nTry adding event.module or event.dataset to specify beats module\n\n{trailer}"
191+
trailer = f"\nTry adding event.module or data_stream.dataset to specify beats module\n\n{trailer}"
192192

193193
return kql.KqlParseError(
194194
exc.error_msg, # type: ignore[reportUnknownArgumentType]
@@ -258,7 +258,7 @@ def validate_integration( # noqa: PLR0912
258258
if exc.error_msg == "Unknown field":
259259
field = extract_error_field(self.query, exc)
260260
trailer = (
261-
f"\n\tTry adding event.module or event.dataset to specify integration module\n\t"
261+
f"\n\tTry adding event.module or data_stream.dataset to specify integration module\n\t"
262262
f"Will check against integrations {meta.integration} combined.\n\t"
263263
f"{package=}, {integration=}, {integration_schema_data['package_version']=}, "
264264
f"{integration_schema_data['stack_version']=}, "
@@ -512,7 +512,7 @@ def validate_integration( # noqa: PLR0912
512512
if message == "Unknown field" or "Field not recognized" in message:
513513
field = extract_error_field(self.query, exc)
514514
trailer = (
515-
f"\n\tTry adding event.module or event.dataset to specify integration module\n\t"
515+
f"\n\tTry adding event.module or data_stream.dataset to specify integration module\n\t"
516516
f"Will check against integrations {meta.integration} combined.\n\t"
517517
f"{package=}, {integration=}, {package_version=}, "
518518
f"{stack_version=}, {ecs_version=}"
@@ -571,7 +571,7 @@ def validate_query_with_schema(
571571
message = exc.error_msg
572572
trailer = err_trailer
573573
if "Unknown field" in message and beat_types:
574-
trailer = f"\nTry adding event.module or event.dataset to specify beats module\n\n{trailer}"
574+
trailer = f"\nTry adding event.module or data_stream.dataset to specify beats module\n\n{trailer}"
575575
elif "Field not recognized" in message:
576576
text_fields = self.text_fields(schema)
577577
if text_fields:

detection_rules/schemas/definitions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def validator_wrapper(value: Any) -> Any:
154154
"OS: Linux",
155155
"OS: macOS",
156156
"OS: Windows",
157+
"Promotion: External Alerts",
157158
"Rule Type: BBR",
158159
"Resources: Investigation Guide",
159160
"Rule Type: Higher-Order Rule",

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.3.15"
3+
version = "1.3.16"
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"
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
[metadata]
2+
creation_date = "2025/07/31"
3+
integration = ["crowdstrike"]
4+
maturity = "production"
5+
promotion = true
6+
min_stack_version = "8.18.0"
7+
min_stack_comments = "Introduced support for CrowdStrike alert promotion"
8+
updated_date = "2025/07/31"
9+
10+
[rule]
11+
author = ["Elastic"]
12+
description = """
13+
Generates a detection alert for each CrowdStrike alert written to the configured indices. Enabling this rule allows you
14+
to immediately begin investigating CrowdStrike alerts in the app.
15+
"""
16+
from = "now-2m"
17+
index = ["logs-crowdstrike.alert-*"]
18+
interval = "1m"
19+
language = "kuery"
20+
license = "Elastic License v2"
21+
max_signals = 1000
22+
name = "CrowdStrike External Alerts"
23+
note = """## Triage and analysis
24+
25+
### Investigating CrowdStrike External Alerts
26+
27+
CrowdStrike Falcon is a cloud-native endpoint protection platform that delivers real-time threat detection and response capabilities. The 'Behavior - Detected - CrowdStrike Alerts' rule captures security alerts generated by Falcon and enables analysts to investigate threats rapidly based on behavioral indicators and threat intelligence.
28+
29+
### Possible investigation steps
30+
31+
- Review the associated process, file path, and command line to determine whether the activity is legitimate or suspicious.
32+
- Investigate the user account and host involved in the alert to validate whether the activity was authorized.
33+
- Cross-reference the alert with CrowdStrike Falcon console for additional context, including process tree, behavioral tags, and threat intelligence matches.
34+
- Check for any related alerts from the same host, user, or file hash to identify whether this is part of a larger attack chain.
35+
- Consult the Crowdstrike investigation guide and resources tagged in the alert for specific guidance on handling similar threats.
36+
37+
### False positive analysis
38+
39+
- Alerts involving known and trusted software tools (e.g., remote administration tools) may be false positives. Confirm intent before excluding.
40+
- Security assessments or penetration testing activities might mimic real threats. Validate the activity with responsible teams.
41+
- Scheduled jobs, IT scripts, or automation tools may trigger alerts if they behave similarly to malicious code.
42+
- Review alerts based on detection confidence levels and behavioral scoring to filter out low-confidence or known-benign triggers.
43+
44+
### Response and remediation
45+
46+
- Isolate affected endpoints to prevent lateral movement if malicious behavior is confirmed.
47+
- Quarantine any identified malicious files and block related hashes or domains.
48+
- Investigate how the threat entered the environment and close any exploited vulnerabilities.
49+
- Reset credentials for compromised user accounts or escalate to incident response.
50+
- Review CrowdStrike Falcon policies and detections to fine-tune future alerting and response coverage.
51+
- Document the findings and update detection logic or exceptions accordingly.
52+
"""
53+
references = ["https://docs.elastic.co/en/integrations/crowdstrike"]
54+
risk_score = 47
55+
rule_id = "aeebe561-c338-4118-9924-8cb4e478aa58"
56+
rule_name_override = "message"
57+
setup = """## Setup
58+
59+
### CrowdStrike Alert Integration
60+
This rule is designed to capture alert events generated by the CrowdStrike integration and promote them as Elastic detection alerts.
61+
62+
To capture CrowdStrike alerts, install and configure the CrowdStrike integration to ingest alert events into the `logs-crowdstrike.alert-*` index pattern.
63+
64+
If this rule is enabled alongside the External Alerts promotion rule (UUID: eb079c62-4481-4d6e-9643-3ca499df7aaa), you may receive duplicate alerts for the same CrowdStrike events. Consider adding a rule exception for the External Alert rule to exclude data_stream.dataset:crowdstrike.alert to avoid receiving duplicate alerts.
65+
66+
### Additional notes
67+
68+
For information on troubleshooting the maximum alerts warning please refer to this [guide](https://www.elastic.co/guide/en/security/current/alerts-ui-monitor.html#troubleshoot-max-alerts).
69+
"""
70+
severity = "medium"
71+
tags = ["Data Source: CrowdStrike", "Use Case: Threat Detection", "Resources: Investigation Guide", "Promotion: External Alerts"]
72+
timestamp_override = "event.ingested"
73+
type = "query"
74+
75+
query = '''
76+
event.kind: alert and data_stream.dataset: crowdstrike.alert
77+
'''
78+
79+
[[rule.risk_score_mapping]]
80+
field = "crowdstrike.alert.incident.score"
81+
operator = "equals"
82+
value = ""
83+
84+
[[rule.severity_mapping]]
85+
field = "event.severity"
86+
operator = "equals"
87+
severity = "low"
88+
value = "21"
89+
90+
[[rule.severity_mapping]]
91+
field = "event.severity"
92+
operator = "equals"
93+
severity = "medium"
94+
value = "47"
95+
96+
[[rule.severity_mapping]]
97+
field = "event.severity"
98+
operator = "equals"
99+
severity = "high"
100+
value = "73"
101+
102+
[[rule.severity_mapping]]
103+
field = "event.severity"
104+
operator = "equals"
105+
severity = "critical"
106+
value = "99"
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
[metadata]
2+
creation_date = "2025/07/31"
3+
integration = ["elastic_security"]
4+
maturity = "production"
5+
promotion = true
6+
min_stack_version = "8.18.0"
7+
min_stack_comments = "Introduced support for Elastic Security alert promotion"
8+
updated_date = "2025/07/31"
9+
10+
[rule]
11+
author = ["Elastic"]
12+
description = """
13+
Generates a detection alert for each Elastic Security alert written to the configured indices. Enabling this rule
14+
allows you to immediately begin investigating Elastic Security alerts in the app.
15+
"""
16+
from = "now-2m"
17+
index = ["logs-elastic_security.alert-*"]
18+
interval = "1m"
19+
language = "kuery"
20+
license = "Elastic License v2"
21+
max_signals = 1000
22+
name = "Elastic Security External Alerts"
23+
note = """
24+
## Triage and analysis
25+
26+
### Investigating Elastic Security External Alerts
27+
28+
Elastic Security is a comprehensive security platform that provides real-time visibility into your environment, helping you detect and respond to threats effectively. The 'Behavior - Detected - Elastic Security Alerts' rule identifies such threats by monitoring specific alert events, enabling analysts to swiftly investigate and mitigate potential security incidents.
29+
30+
### Possible investigation steps
31+
32+
- Correlate the alert with recent activity on the affected endpoint to identify any unusual or suspicious behavior patterns.
33+
- Check for any additional alerts or logs related to the same endpoint or user to determine if this is part of a broader attack or isolated incident.
34+
- Investigate the source and destination IP addresses involved in the alert to assess if they are known to be malicious or associated with previous threats.
35+
- Analyze any files or processes flagged in the alert to determine if they are legitimate or potentially malicious, using threat intelligence sources if necessary.
36+
- Consult the Elastic Security investigation guide and resources tagged in the alert for specific guidance on handling similar threats.
37+
38+
### False positive analysis
39+
40+
- Alerts triggered by routine software updates or patches can be false positives. Review the context of the alert to determine if it aligns with scheduled maintenance activities.
41+
- Legitimate administrative tools or scripts may trigger alerts. Identify and whitelist these tools if they are verified as non-threatening.
42+
- Frequent alerts from known safe applications or processes can be excluded by creating exceptions for these specific behaviors in the Elastic Security configuration.
43+
- Network scanning or monitoring tools used by IT teams might be flagged. Ensure these tools are documented and excluded from triggering alerts if they are part of regular operations.
44+
- User behavior that is consistent with their role but triggers alerts should be reviewed. If deemed non-malicious, adjust the rule to exclude these specific user actions.
45+
46+
### Response and remediation
47+
48+
- Isolate the affected endpoint immediately to prevent lateral movement and further compromise within the network.
49+
- Analyze the specific alert details to identify the nature of the threat and any associated indicators of compromise (IOCs).
50+
- Remove or quarantine any malicious files or processes identified by the Elastic Security alert to neutralize the threat.
51+
- Apply relevant security patches or updates to address any exploited vulnerabilities on the affected endpoint.
52+
- Conduct a thorough scan of the network to identify any additional endpoints that may have been compromised or are exhibiting similar behavior.
53+
- Document the incident and escalate to the appropriate security team or management if the threat is part of a larger attack campaign or if additional resources are needed for remediation.
54+
- Review and update endpoint protection policies and configurations to enhance detection and prevention capabilities against similar threats in the future.
55+
"""
56+
references = ["https://docs.elastic.co/en/integrations/elastic_security"]
57+
risk_score = 47
58+
rule_id = "720fc1aa-e195-4a1d-81d8-04edfe5313ed"
59+
rule_name_override = "rule.name"
60+
setup = """## Setup
61+
62+
### Elastic Security Alert Integration
63+
This rule is designed to capture alert events generated by the Elastic Security integration and promote them as Elastic detection alerts.
64+
65+
To capture Elastic Security alerts, install and configure the Elastic Security integration to ingest alert events into the `logs-elastic_security.alert-*` index pattern.
66+
67+
If this rule is enabled alongside the External Alerts promotion rule (UUID: eb079c62-4481-4d6e-9643-3ca499df7aaa), you may receive duplicate alerts for the same Elastic Security events. Consider adding a rule exception for the External Alert rule to exclude data_stream.dataset:elastic_security.alert to avoid receiving duplicate alerts.
68+
69+
### Additional notes
70+
71+
For information on troubleshooting the maximum alerts warning please refer to this [guide](https://www.elastic.co/guide/en/security/current/alerts-ui-monitor.html#troubleshoot-max-alerts).
72+
"""
73+
severity = "medium"
74+
tags = ["Data Source: Elastic Security", "Use Case: Threat Detection", "Resources: Investigation Guide", "Promotion: External Alerts"]
75+
timestamp_override = "event.ingested"
76+
type = "query"
77+
78+
query = '''
79+
event.kind: alert and data_stream.dataset: elastic_security.alert
80+
'''
81+
82+
83+
[[rule.risk_score_mapping]]
84+
field = "event.risk_score"
85+
operator = "equals"
86+
value = ""
87+
88+
[[rule.severity_mapping]]
89+
field = "event.severity"
90+
operator = "equals"
91+
severity = "low"
92+
value = "21"
93+
94+
[[rule.severity_mapping]]
95+
field = "event.severity"
96+
operator = "equals"
97+
severity = "medium"
98+
value = "47"
99+
100+
[[rule.severity_mapping]]
101+
field = "event.severity"
102+
operator = "equals"
103+
severity = "high"
104+
value = "73"
105+
106+
[[rule.severity_mapping]]
107+
field = "event.severity"
108+
operator = "equals"
109+
severity = "critical"
110+
value = "99"

0 commit comments

Comments
 (0)