Skip to content

Commit 22f72c9

Browse files
authored
Merge branch 'main' into new-rule-potential-rfi
2 parents 6108683 + 4920e9a commit 22f72c9

File tree

31 files changed

+2285
-494
lines changed

31 files changed

+2285
-494
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",

detection_rules/schemas/definitions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def validator_wrapper(value: Any) -> Any:
7676
CONDITION_VERSION_PATTERN = re.compile(rf"^\^{_version}$")
7777
VERSION_PATTERN = f"^{_version}$"
7878
MINOR_SEMVER = re.compile(r"^\d+\.\d+$")
79-
FROM_SOURCES_REGEX = re.compile(r"^\s*FROM\s+(?P<sources>.+?)\s*(?:\||\bmetadata\b|//|$)", re.IGNORECASE | re.MULTILINE)
79+
FROM_SOURCES_REGEX = re.compile(
80+
r"^\s*FROM\s+(?P<sources>(?:.+?(?:,\s*)?\n?)+?)\s*(?:\||\bmetadata\b|//|$)", re.IGNORECASE | re.MULTILINE
81+
)
8082
BRANCH_PATTERN = f"{VERSION_PATTERN}|^master$"
8183
ELASTICSEARCH_EQL_FEATURES = {
8284
"allow_negation": (Version.parse("8.9.0"), None),

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.17"
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"
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
[metadata]
2+
creation_date = "2025/12/01"
3+
integration = ["aws", "gcp", "azure"]
4+
maturity = "production"
5+
updated_date = "2025/12/01"
6+
7+
[rule]
8+
author = ["Elastic"]
9+
description = """
10+
This rule detects authenticated sessions accessing secret stores across multiple cloud providers from the same source
11+
address within a short period of time. Adversaries with access to compromised credentials or session tokens may attempt
12+
to retrieve secrets from services such as AWS Secrets Manager, Google Secret Manager, or Azure Key Vault in rapid
13+
succession to expand their access or exfiltrate sensitive information.
14+
"""
15+
from = "now-9m"
16+
interval = "5m"
17+
language = "esql"
18+
license = "Elastic License v2"
19+
name = "Multiple Cloud Secrets Accessed by Source Address"
20+
note = """## Triage and analysis
21+
22+
### Multiple Cloud Secrets Accessed by Source Address
23+
24+
This alert identifies a single source IP address accessing secret-management APIs across **multiple cloud providers**
25+
(e.g., AWS Secrets Manager, Google Secret Manager, Azure Key Vault) within a short timeframe.
26+
This behavior is strongly associated with **credential theft, session hijacking, or token replay**, where an adversary
27+
uses stolen authenticated sessions to harvest secrets across cloud environments.
28+
29+
Unexpected cross-cloud secret retrieval is uncommon and typically indicates automation misuse or malicious activity.
30+
31+
### Possible investigation steps
32+
33+
- Validate the principal
34+
- Identify the user, service account, workload identity, or application making the requests.
35+
- Confirm whether this identity is expected to operate across more than one cloud provider.
36+
- Review related activity
37+
- Look for additional alerts involving the same identity, source IP, or token over the last 24–48 hours.
38+
- Identify whether the source IP has been observed performing unusual authentication, privilege escalation,
39+
or reconnaissance.
40+
- Check application or service context
41+
- Determine whether any workload legitimately pulls secrets from multiple cloud providers.
42+
- Review deployment pipelines or integration layers that might legitimately bridge AWS, Azure, and GCP.
43+
- Analyze user agent and invocation patterns
44+
- Compare `user_agent.original` or equivalent fields against expected SDKs or automation tools.
45+
- Suspicious indicators include CLI tools, unknown libraries, browser user agents, or custom scripts.
46+
- Inspect IP reputation and origin
47+
- Determine whether the source IP corresponds to a managed workload (EC2, GCE, Azure VM) or an unexpected host.
48+
- Validate that the associated instance or host is under your control and behaving normally.
49+
- Review IAM permissions and accessed secrets
50+
- Check the policies attached to the identity.
51+
- Verify whether the accessed secrets are sensitive, unused, or unrelated to the identity’s purpose.
52+
- Assess potential compromise scope
53+
- If compromise is suspected, enumerate other assets accessed by the same identity in the last 24 hours.
54+
- Look for lateral movement, privilege escalation, or abnormal API usage.
55+
56+
### False positive analysis
57+
58+
- Validate whether the source IP is associated with a legitimate multi-cloud orchestration tool, automation pipeline,
59+
or shared CI/CD system.
60+
- Confirm that the identity is authorized to access secrets across multiple cloud services.
61+
- If activity is expected, consider adding exceptions that pair account identity, source IP, and expected user agent
62+
to reduce noise.
63+
64+
### Response and remediation
65+
66+
- Initiate incident response** if the activity is unauthorized or suspicious.
67+
- Restrict or disable** the affected credentials or service accounts.
68+
- Rotate all accessed secrets** and review other secrets the identity can access.
69+
- Analyze systems** that may have leaked credentials, such as compromised hosts or exposed tokens.
70+
- Harden identity security:
71+
- Enforce MFA for users where applicable.
72+
- Reduce permissions to least privilege.
73+
- Review trust relationships, workload identities, and cross-cloud integrations.
74+
- Search for persistence mechanisms** such as newly created keys, roles, or service accounts.
75+
- Improve monitoring and audit visibility** by ensuring logging is enabled across all cloud environments.
76+
- Determine root cause** (phishing, malware, token replay, exposed credential, etc.) and close the vector to prevent recurrence.
77+
"""
78+
references = [
79+
"https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html",
80+
"https://docs.cloud.google.com/secret-manager/docs/samples/secretmanager-access-secret-version",
81+
"https://learn.microsoft.com/en-us/azure/key-vault/secrets/about-secrets",
82+
"https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack",
83+
]
84+
risk_score = 73
85+
rule_id = "472b4944-d810-43cf-83dc-7d080ae1b8dd"
86+
setup = """
87+
This multi-datasource rule relies on additional configurations from each hyperscaler.
88+
89+
- GCP Audit: [Enable DATA_READ for the Secret Manager API service](https://docs.cloud.google.com/logging/docs/audit/configure-data-access)
90+
- Azure: [Enable Diagnostic Logging for the Key Vault Service](https://learn.microsoft.com/en-us/azure/key-vault/general/howto-logging?tabs=azure-cli)
91+
- AWS: Secrets Manager read access is automatically logged by CloudTrail.
92+
"""
93+
severity = "high"
94+
tags = [
95+
"Domain: Cloud",
96+
"Domain: IAM",
97+
"Domain: Storage",
98+
"Data Source: AWS",
99+
"Data Source: Amazon Web Services",
100+
"Data Source: AWS Secrets Manager",
101+
"Data Source: Azure",
102+
"Data Source: Azure Activity Logs",
103+
"Data Source: GCP",
104+
"Data Source: Google Cloud Platform",
105+
"Tactic: Credential Access",
106+
"Resources: Investigation Guide",
107+
]
108+
timestamp_override = "event.ingested"
109+
type = "esql"
110+
111+
query = '''
112+
FROM logs-azure.platformlogs-*, logs-azure.activitylogs-*, logs-aws.cloudtrail-*, logs-gcp.audit-* METADATA _id, _version, _index
113+
| WHERE
114+
(
115+
/* AWS Secrets Manager */
116+
(event.dataset == "aws.cloudtrail" AND event.provider == "secretsmanager.amazonaws.com" AND event.action == "GetSecretValue") OR
117+
// Azure Key Vault (platform logs)
118+
(event.dataset == "azure.platformlogs" AND event.action IN ("SecretGet", "KeyGet")) or
119+
/* Azure Key Vault (activity logs) */
120+
(event.dataset == "azure.activitylogs" AND azure.activitylogs.operation_name IN ("MICROSOFT.KEYVAULT/VAULTS/SECRETS/LIST", "MICROSOFT.KEYVAULT/VAULTS/SECRETS/GET")) OR
121+
/* Azure Managed HSM secret */
122+
(event.dataset == "azure.activitylogs" AND azure.activitylogs.operation_name LIKE "MICROSOFT.KEYVAULT/managedHSM/keys/*") OR
123+
/* Google Secret Manager */
124+
(event.dataset IN ("googlecloud.audit", "gcp.audit") AND
125+
event.action IN ("google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion", "google.cloud.secretmanager.v1.SecretManagerService.GetSecretRequest"))
126+
) AND source.ip IS NOT NULL
127+
// Unified user identity (raw)
128+
| EVAL Esql_priv.user_id =
129+
COALESCE(
130+
client.user.id,
131+
aws.cloudtrail.user_identity.arn,
132+
azure.platformlogs.identity.claim.upn,
133+
NULL
134+
)
135+
// Cloud vendor label based on dataset
136+
| EVAL Esql.cloud_vendor = CASE(
137+
event.dataset == "aws.cloudtrail", "aws",
138+
event.dataset IN ("azure.platformlogs","azure.activitylogs"), "azure",
139+
event.dataset IN ("googlecloud.audit","gcp.audit"), "gcp",
140+
"unknown"
141+
)
142+
// Vendor+tenant label, e.g. aws:123456789012, azure:tenant, gcp:project
143+
| EVAL Esql.tenant_label = CASE(
144+
Esql.cloud_vendor == "aws", CONCAT("aws:", cloud.account.id),
145+
Esql.cloud_vendor == "azure", CONCAT("azure:", cloud.account.id),
146+
Esql.cloud_vendor == "gcp", CONCAT("gcp:", cloud.account.id),
147+
NULL
148+
)
149+
| STATS
150+
// Core counts
151+
Esql.events_count = COUNT(*),
152+
Esql.vendor_count_distinct = COUNT_DISTINCT(Esql.cloud_vendor),
153+
// Action & data source context
154+
Esql.event_action_values = VALUES(event.action),
155+
Esql.data_source_values = VALUES(event.dataset),
156+
// Cloud vendor + tenant context
157+
Esql.cloud_vendor_values = VALUES(Esql.cloud_vendor),
158+
Esql.tenant_label_values = VALUES(Esql.tenant_label),
159+
// Hyperscaler-specific IDs
160+
Esql.aws_account_id_values = VALUES(CASE(Esql.cloud_vendor == "aws", cloud.account.id, NULL)),
161+
Esql.azure_tenant_id_values = VALUES(CASE(Esql.cloud_vendor == "azure", cloud.account.id, NULL)),
162+
Esql.gcp_project_id_values = VALUES(CASE(Esql.cloud_vendor == "gcp", cloud.account.id, NULL)),
163+
// Generic cloud metadata
164+
Esql.cloud_region_values = VALUES(cloud.region),
165+
Esql.cloud_service_name_values = VALUES(cloud.service.name),
166+
// Identity (privileged)
167+
Esql_priv.user_values = VALUES(Esql_priv.user_id),
168+
Esql_priv.client_user_id_values = VALUES(client.user.id),
169+
Esql_priv.aws_user_identity_arn_values = VALUES(aws.cloudtrail.user_identity.arn),
170+
Esql_priv.azure_upn_values = VALUES(azure.platformlogs.identity.claim.upn),
171+
// Namespace values
172+
Esql.data_stream_namespace_values = VALUES(data_stream.namespace)
173+
BY source.ip
174+
// Require multi-vendor cred-access from same source IP
175+
| WHERE Esql.vendor_count_distinct >= 2
176+
| SORT Esql.events_count DESC
177+
| KEEP Esql.*, Esql_priv.*, source.ip
178+
'''
179+
180+
181+
182+
[[rule.threat]]
183+
framework = "MITRE ATT&CK"
184+
[[rule.threat.technique]]
185+
id = "T1555"
186+
name = "Credentials from Password Stores"
187+
reference = "https://attack.mitre.org/techniques/T1555/"
188+
[[rule.threat.technique.subtechnique]]
189+
id = "T1555.006"
190+
name = "Cloud Secrets Management Stores"
191+
reference = "https://attack.mitre.org/techniques/T1555/006/"
192+
193+
194+
195+
[rule.threat.tactic]
196+
id = "TA0006"
197+
name = "Credential Access"
198+
reference = "https://attack.mitre.org/tactics/TA0006/"
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
[metadata]
2+
creation_date = "2025/11/27"
3+
integration = ["nginx", "apache", "apache_tomcat"]
4+
maturity = "production"
5+
updated_date = "2025/11/27"
6+
7+
[rule]
8+
author = ["Elastic"]
9+
description = """
10+
Through the new_terms rule type, this rule detects potential HTTP downgrade attacks by identifying
11+
HTTP traffic that uses a different HTTP version than the one typically used in the environment. An
12+
HTTP downgrade attack occurs when an attacker forces a connection via an older HTTP version,
13+
resulting in potentially less secure communication. For example, an attacker might downgrade a
14+
connection from HTTP/2 to HTTP/1.1 or HTTP/1.0 to exploit known vulnerabilities or weaknesses in
15+
the older protocol versions.
16+
"""
17+
from = "now-9m"
18+
index = [
19+
"logs-nginx.access-*",
20+
"logs-apache.access-*",
21+
"logs-apache_tomcat.access-*",
22+
]
23+
language = "kuery"
24+
license = "Elastic License v2"
25+
name = "Potential HTTP Downgrade Attack"
26+
risk_score = 21
27+
rule_id = "9797d2c8-8ec9-48e6-a022-350cdfbf2d5e"
28+
severity = "low"
29+
tags = [
30+
"Domain: Web",
31+
"Use Case: Threat Detection",
32+
"Tactic: Defense Evasion",
33+
"Data Source: Nginx",
34+
"Data Source: Apache",
35+
"Data Source: Apache Tomcat",
36+
]
37+
timestamp_override = "event.ingested"
38+
type = "new_terms"
39+
query = '''
40+
http.version:*
41+
'''
42+
43+
[[rule.threat]]
44+
framework = "MITRE ATT&CK"
45+
46+
[[rule.threat.technique]]
47+
id = "T1562"
48+
name = "Impair Defenses"
49+
reference = "https://attack.mitre.org/techniques/T1562/"
50+
51+
[[rule.threat.technique.subtechnique]]
52+
id = "T1562.010"
53+
name = "Downgrade Attack"
54+
reference = "https://attack.mitre.org/techniques/T1562/010/"
55+
56+
[rule.threat.tactic]
57+
id = "TA0005"
58+
name = "Defense Evasion"
59+
reference = "https://attack.mitre.org/tactics/TA0005/"
60+
61+
[rule.new_terms]
62+
field = "new_terms_fields"
63+
value = ["http.version", "agent.id"]
64+
65+
[[rule.new_terms.history_window_start]]
66+
field = "history_window_start"
67+
value = "now-7d"

0 commit comments

Comments
 (0)