Skip to content

Commit e34efb0

Browse files
Merge branch 'main' into new-rule-potential-lfi-request
2 parents 773afdb + f32db7b commit e34efb0

File tree

7 files changed

+168
-23
lines changed

7 files changed

+168
-23
lines changed

detection_rules/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def parse_rules_config(path: Path | None = None) -> RulesConfig: # noqa: PLR091
227227
raise ValueError(f"rules config file does not exist: {path}")
228228
loaded = yaml.safe_load(path.read_text())
229229
elif CUSTOM_RULES_DIR:
230-
path = Path(CUSTOM_RULES_DIR) / "_config.yaml"
230+
path = Path(CUSTOM_RULES_DIR).expanduser() / "_config.yaml"
231231
if not path.exists():
232232
raise FileNotFoundError(
233233
"""

detection_rules/etc/non-ecs-schema.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145
"kibana.alert.rule.threat.tactic.id": "keyword",
146146
"kibana.alert.workflow_status": "keyword",
147147
"kibana.alert.rule.rule_id": "keyword",
148-
"kibana.alert.rule.name": "keyword",
148+
"kibana.alert.rule.name": "keyword",
149149
"kibana.alert.risk_score": "long",
150150
"kibana.alert.rule.type": "keyword",
151151
"kibana.alert.rule.threat.tactic.name": "keyword"
@@ -237,7 +237,8 @@
237237
"o365.audit.ExtendedProperties.ResultStatusDetail": "keyword",
238238
"o365.audit.OperationProperties.Name": "keyword",
239239
"o365.audit.OperationProperties.Value": "keyword",
240-
"o365.audit.OperationCount": "long"
240+
"o365.audit.OperationCount": "long",
241+
"o365.audit.AppAccessContext.AADSessionId": "keyword"
241242
},
242243
"logs-okta*": {
243244
"okta.debug_context.debug_data.flattened.requestedScopes": "keyword",

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.5.18"
3+
version = "1.5.19"
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/integrations/aws/persistence_redshift_instance_creation.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2022/04/12"
33
integration = ["aws"]
44
maturity = "production"
5-
updated_date = "2025/01/15"
5+
updated_date = "2025/11/25"
66

77
[rule]
88
author = ["Elastic"]
@@ -23,13 +23,13 @@ index = ["filebeat-*", "logs-aws.cloudtrail-*"]
2323
interval = "10m"
2424
language = "kuery"
2525
license = "Elastic License v2"
26-
name = "AWS Redshift Cluster Creation"
26+
name = "Deprecated - AWS Redshift Cluster Creation"
2727
note = """## Triage and analysis
2828
2929
> **Disclaimer**:
3030
> This investigation guide was created using generative AI technology and has been reviewed to improve its accuracy and relevance. While every effort has been made to ensure its quality, we recommend validating the content and adapting it to suit your specific environment and operational needs.
3131
32-
### Investigating AWS Redshift Cluster Creation
32+
### Investigating Deprecated - AWS Redshift Cluster Creation
3333
3434
Amazon Redshift is a data warehousing service that allows for scalable data storage and analysis. In a secure environment, only authorized users should create Redshift clusters. Adversaries might exploit misconfigured permissions to create clusters, potentially leading to data exfiltration or unauthorized data processing. The detection rule monitors for successful cluster creation events, especially by non-admin users, to identify potential misuse or misconfigurations.
3535
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
[metadata]
2+
creation_date = "2025/12/02"
3+
integration = ["azure"]
4+
maturity = "production"
5+
updated_date = "2025/12/02"
6+
7+
[rule]
8+
author = ["Elastic"]
9+
description = """
10+
Identifies concurrent Entra ID sign-in events for the same user and session from multiple sources, and where one of the
11+
authentication event has some suspicious properties often associated to DeviceCode and OAuth phishing. Adversaries may
12+
steal Refresh Tokens (RTs) via phishing to bypass multi-factor authentication (MFA) and gain unauthorized access to
13+
Azure resources.
14+
"""
15+
false_positives = [
16+
"""
17+
Users authenticating from multiple devices and using the deviceCode protocol or the Visual Studio Code client.
18+
""",
19+
]
20+
from = "now-9m"
21+
language = "esql"
22+
license = "Elastic License v2"
23+
name = "Suspicious Microsoft Entra ID Concurrent Sign-Ins via DeviceCode"
24+
note = """## Triage and analysis
25+
26+
### Investigating Suspicious Microsoft Entra ID Concurrent Sign-Ins via DeviceCode
27+
28+
### Possible investigation steps
29+
30+
- Review the sign-in logs to assess the context and reputation of the source.ip address.
31+
- Investigate the user account associated with the successful sign-in to determine if the activity aligns with expected behavior or if it appears suspicious.
32+
- Check for any recent changes or anomalies in the user's account settings or permissions that could indicate compromise.
33+
- Review the history of sign-ins for the user to identify any patterns or unusual access times that could suggest unauthorized access.
34+
- Assess the device from which the sign-in was attempted to ensure it is a recognized and authorized device for the user.
35+
36+
### Response and remediation
37+
38+
- Immediately revoke the compromised Primary Refresh Tokens (PRTs) to prevent further unauthorized access. This can be done through the Azure portal by navigating to the user's account and invalidating all active sessions.
39+
- Enforce a password reset for the affected user accounts to ensure that any credentials potentially compromised during the attack are no longer valid.
40+
- Implement additional Conditional Access policies that require device compliance checks and restrict access to trusted locations or devices only, to mitigate the risk of future PRT abuse.
41+
- Conduct a thorough review of the affected accounts' recent activity logs to identify any unauthorized actions or data access that may have occurred during the compromise.
42+
- Escalate the incident to the security operations team for further investigation and to determine if there are any broader implications or additional compromised accounts.
43+
- Enhance monitoring by configuring alerts for unusual sign-in patterns or device code authentication attempts from unexpected locations or devices, to improve early detection of similar threats.
44+
- Coordinate with the incident response team to perform a post-incident analysis and update the incident response plan with lessons learned from this event."""
45+
references = [
46+
"https://learn.microsoft.com/en-us/entra/identity/",
47+
"https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-sign-ins",
48+
"https://docs.microsoft.com/en-us/azure/active-directory/reports-monitoring/reference-azure-monitor-sign-ins-log-schema",
49+
"https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/",
50+
"https://www.wiz.io/blog/recent-oauth-attacks-detection-strategies"
51+
]
52+
risk_score = 73
53+
rule_id = "3db029b3-fbb7-4697-ad07-33cbfd5bd080"
54+
setup = """#### Required Azure Entra Sign-In Logs
55+
This rule requires the Azure logs integration be enabled and configured to collect all logs, including sign-in logs from Entra. In Entra, sign-in logs must be enabled and streaming to the Event Hub used for the Azure logs integration.
56+
"""
57+
severity = "high"
58+
tags = [
59+
"Domain: Cloud",
60+
"Domain: Identity",
61+
"Data Source: Azure",
62+
"Data Source: Entra ID",
63+
"Data Source: Entra ID Sign-in",
64+
"Use Case: Identity and Access Audit",
65+
"Use Case: Threat Detection",
66+
"Tactic: Credential Access",
67+
"Resources: Investigation Guide",
68+
]
69+
timestamp_override = "event.ingested"
70+
type = "esql"
71+
72+
query = '''
73+
from logs-azure.signinlogs-* metadata _id, _version, _index
74+
75+
| where event.category == "authentication" and event.dataset == "azure.signinlogs" and
76+
azure.signinlogs.properties.original_transfer_method == "deviceCodeFlow"
77+
78+
| Eval Esql.interactive_logon = CASE(azure.signinlogs.category == "SignInLogs", source.ip, null),
79+
Esql.non_interactive_logon = CASE(azure.signinlogs.category == "NonInteractiveUserSignInLogs", source.ip, null)
80+
81+
| stats Esql.count_logon = count(*),
82+
Esql.timestamp_values = values(@timestamp),
83+
Esql.source_ip_count_distinct = count_distinct(source.ip),
84+
Esql.is_interactive = count(Esql.interactive_logon),
85+
Esql.is_non_interactive = count(Esql.non_interactive_logon),
86+
Esql.user_agent_count_distinct = COUNT_DISTINCT(user_agent.original),
87+
Esql.user_agent_values = VALUES(user_agent.original),
88+
Esql.azure_signinlogs_properties_client_app_values = values(azure.signinlogs.properties.app_display_name),
89+
Esql.azure_signinlogs_properties_client_app_values = values(azure.signinlogs.properties.app_id),
90+
Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),
91+
Esql.azure_signinlogs_properties_auth_requirement_values = values(azure.signinlogs.properties.authentication_requirement),
92+
Esql.azure_signinlogs_properties_tenant_id = values(azure.tenant_id),
93+
Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code),
94+
Esql.message_values = values(message),
95+
Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id),
96+
Esql.source_ip_values = VALUES(source.ip) by azure.signinlogs.properties.session_id, azure.signinlogs.identity
97+
98+
| where Esql.is_interactive >= 2 and Esql.is_non_interactive >= 1 and (Esql.source_ip_count_distinct >= 2 or Esql.user_agent_count_distinct >= 2)
99+
| keep
100+
Esql.*,
101+
azure.signinlogs.properties.session_id,
102+
azure.signinlogs.identity
103+
'''
104+
105+
106+
[[rule.threat]]
107+
framework = "MITRE ATT&CK"
108+
[[rule.threat.technique]]
109+
id = "T1528"
110+
name = "Steal Application Access Token"
111+
reference = "https://attack.mitre.org/techniques/T1528/"
112+
113+
114+
[rule.threat.tactic]
115+
id = "TA0006"
116+
name = "Credential Access"
117+
reference = "https://attack.mitre.org/tactics/TA0006/"
118+
[[rule.threat]]
119+
framework = "MITRE ATT&CK"
120+
[[rule.threat.technique]]
121+
id = "T1566"
122+
name = "Phishing"
123+
reference = "https://attack.mitre.org/techniques/T1566/"
124+
[[rule.threat.technique.subtechnique]]
125+
id = "T1566.002"
126+
name = "Spearphishing Link"
127+
reference = "https://attack.mitre.org/techniques/T1566/002/"
128+
129+
130+
[rule.threat.tactic]
131+
id = "TA0001"
132+
name = "Initial Access"
133+
reference = "https://attack.mitre.org/tactics/TA0001/"
134+

rules/integrations/o365/collection_onedrive_excessive_file_downloads.toml

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2025/02/19"
33
integration = ["o365"]
44
maturity = "production"
5-
updated_date = "2025/09/26"
5+
updated_date = "2025/11/25"
66

77
[rule]
88
author = ["Elastic"]
@@ -83,28 +83,38 @@ type = "esql"
8383
query = '''
8484
from logs-o365.audit-*
8585
| where
86-
@timestamp > now() - 14d and
8786
event.dataset == "o365.audit" and
8887
event.provider == "OneDrive" and
8988
event.action == "FileDownloaded" and
9089
o365.audit.AuthenticationType == "OAuth" and
9190
event.outcome == "success"
92-
| eval
93-
Esql.time_window_date_trunc = date_trunc(1 minutes, @timestamp)
94-
| keep
95-
Esql.time_window_date_trunc,
96-
o365.audit.UserId,
97-
file.name,
98-
source.ip
91+
and (user.id is not null and o365.audit.ApplicationId is not null)
92+
| eval session.id = coalesce(o365.audit.AppAccessContext.AADSessionId, session.id, null)
93+
| where session.id is not null
94+
| eval Esql.time_window_date_trunc = date_trunc(1 minutes, @timestamp)
9995
| stats
96+
Esql.file_directory_values = values(file.directory),
97+
Esql.file_extension_values = values(file.extension),
98+
Esql.application_name_values = values(application.name),
10099
Esql.file_name_count_distinct = count_distinct(file.name),
100+
Esql.o365_audit_Site_values = values(o365.audit.Site),
101+
Esql.o365_audit_SiteUrl_values = values(o365.audit.SiteUrl),
102+
Esql.user_domain_values = values(user.domain),
103+
Esql.token_id_values = values(token.id),
101104
Esql.event_count = count(*)
102-
by
105+
by
103106
Esql.time_window_date_trunc,
104-
o365.audit.UserId,
105-
source.ip
106-
| where
107-
Esql.file_name_count_distinct >= 25
107+
user.id,
108+
session.id,
109+
source.ip,
110+
o365.audit.ApplicationId
111+
| where Esql.file_name_count_distinct >= 25
112+
| keep
113+
Esql.*,
114+
user.id,
115+
source.ip,
116+
o365.audit.ApplicationId,
117+
session.id
108118
'''
109119

110120

tests/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
def load_rules() -> RuleCollection:
2727
if CUSTOM_RULES_DIR:
2828
rc = RuleCollection()
29-
path = Path(CUSTOM_RULES_DIR)
29+
path = Path(CUSTOM_RULES_DIR).expanduser()
3030
assert path.exists(), f"Custom rules directory {path} does not exist"
3131
rc.load_directories(directories=RULES_CONFIG.rule_dirs)
3232
rc.freeze()
@@ -62,7 +62,7 @@ def setUpClass(cls):
6262
RULE_LOADER_FAIL = True
6363
RULE_LOADER_FAIL_MSG = str(e)
6464

65-
cls.custom_dir = Path(CUSTOM_RULES_DIR).resolve() if CUSTOM_RULES_DIR else None
65+
cls.custom_dir = Path(CUSTOM_RULES_DIR).expanduser().resolve() if CUSTOM_RULES_DIR else None
6666
cls.rules_config = RULES_CONFIG
6767

6868
@staticmethod

0 commit comments

Comments
 (0)