Skip to content

Commit e14c349

Browse files
Merge branch 'main' into deprecatemac
2 parents 010e7d3 + 96c2d0c commit e14c349

File tree

4 files changed

+145
-3
lines changed

4 files changed

+145
-3
lines changed

detection_rules/cli_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
DEFAULT_PREBUILT_RULES_DIRS, RuleCollection,
2323
dict_filter)
2424
from .schemas import definitions
25-
from .utils import clear_caches, rulename_to_filename
25+
from .utils import clear_caches, ensure_list_of_strings, rulename_to_filename
2626
from .config import parse_rules_config
2727

2828
RULES_CONFIG = parse_rules_config()
@@ -195,7 +195,8 @@ def rule_prompt(path=None, rule_type=None, required_only=True, save=True, verbos
195195
if name == "new_terms":
196196
# patch to allow new_term imports
197197
result = {"field": "new_terms_fields"}
198-
result["value"] = schema_prompt("new_terms_fields", value=kwargs.pop("new_terms_fields"))
198+
new_terms_fields_value = schema_prompt("new_terms_fields", value=kwargs.pop("new_terms_fields", None))
199+
result["value"] = ensure_list_of_strings(new_terms_fields_value)
199200
history_window_start_value = kwargs.pop("history_window_start", None)
200201
result["history_window_start"] = [
201202
{

detection_rules/utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,27 @@ def dict_hash(obj: dict) -> str:
7474
return hashlib.sha256(raw_bytes).hexdigest()
7575

7676

77+
def ensure_list_of_strings(value: str | list) -> list[str]:
78+
"""Ensure or convert a value is a list of strings."""
79+
if isinstance(value, str):
80+
# Check if the string looks like a JSON list
81+
if value.startswith('[') and value.endswith(']'):
82+
try:
83+
# Attempt to parse the string as a JSON list
84+
parsed_value = json.loads(value)
85+
if isinstance(parsed_value, list):
86+
return [str(v) for v in parsed_value]
87+
except json.JSONDecodeError:
88+
pass
89+
# If it's not a JSON list, split by commas if present
90+
# Else return a list with the original string
91+
return list(map(lambda x: x.strip().strip('"'), value.split(',')))
92+
elif isinstance(value, list):
93+
return [str(v) for v in value]
94+
else:
95+
return []
96+
97+
7798
def get_json_iter(f):
7899
"""Get an iterator over a JSON file."""
79100
first = f.read(2)

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.0.12"
3+
version = "1.0.13"
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: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
[metadata]
2+
creation_date = "2025/04/11"
3+
integration = ["aws"]
4+
maturity = "production"
5+
updated_date = "2025/04/11"
6+
7+
[rule]
8+
author = ["Elastic"]
9+
description = """
10+
This rule detects when a single IAM user's temporary session token is used from multiple IP addresses within a short
11+
time frame. This behavior may indicate that an adversary has stolen temporary credentials and is using them from a
12+
different location.
13+
"""
14+
false_positives = [
15+
"""
16+
Highly distributed environments (e.g., globally deployed automation or edge nodes) may cause a single IAM user to
17+
appear from multiple IPs. Review the geolocation and automation context to rule out benign use.
18+
""",
19+
]
20+
from = "now-30m"
21+
language = "esql"
22+
license = "Elastic License v2"
23+
name = "AWS STS Temporary IAM Session Token Used from Multiple Addresses"
24+
note = """## Triage and Analysis
25+
26+
### Investigating AWS STS Temporary IAM Session Token Used from Multiple Addresses
27+
28+
Temporary session tokens (typically starting with 'ASIA') are expected to be short-lived and bound to a single user session. Usage from multiple IP addresses may indicate the token was stolen and used elsewhere.
29+
30+
#### Possible Investigation Steps
31+
32+
- **Identify the IAM User**: Examine `aws.cloudtrail.user_identity.arn` and correlate with `source.ip` to determine how widely the token was used.
33+
- **Check Recent MFA Events**: Determine whether the user recently enabled MFA, registered devices, or assumed a role using this token.
34+
- **Review Workload Context**: Confirm whether the user was expected to be active in multiple regions or environments.
35+
- **Trace Adversary Movement**: Pivot to related actions (e.g., `s3:ListBuckets`, `iam:ListUsers`, `sts:GetCallerIdentity`) to track further enumeration.
36+
37+
### False Positive Analysis
38+
39+
- Automation frameworks that rotate through multiple IPs or cloud functions with dynamic egress IPs may cause this alert to fire.
40+
- Confirm geolocation and workload context before escalating.
41+
42+
### Response and Remediation
43+
44+
- **Revoke the Token**: Disable or rotate the IAM credentials and invalidate the temporary session token.
45+
- **Audit the Environment**: Look for signs of lateral movement or data access during the token's validity.
46+
- **Strengthen Controls**: Require MFA for high-privilege actions, restrict access via policy conditions (e.g., IP range or device).
47+
48+
### References
49+
50+
- [STS Temporary Credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html)
51+
- [Using MFA with Temporary Credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
52+
- [AWS Threat Detection Use Cases](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html)
53+
"""
54+
references = ["https://www.sygnia.co/blog/sygnia-investigation-bybit-hack/"]
55+
risk_score = 47
56+
rule_id = "0d92d30a-5f3e-4b71-bc3d-4a0c4914b7e0"
57+
severity = "medium"
58+
tags = [
59+
"Domain: Cloud",
60+
"Data Source: AWS",
61+
"Data Source: Amazon Web Services",
62+
"Data Source: AWS IAM",
63+
"Data Source: AWS CloudTrail",
64+
"Tactic: Initial Access",
65+
"Use Case: Identity and Access Audit",
66+
"Resources: Investigation Guide",
67+
]
68+
timestamp_override = "event.ingested"
69+
type = "esql"
70+
71+
query = '''
72+
from logs-aws.cloudtrail* metadata _id, _version, _index
73+
| where
74+
75+
// filter on CloudTrail logs for STS temporary session tokens used by IAM users
76+
event.dataset == "aws.cloudtrail"
77+
and aws.cloudtrail.user_identity.arn is not null
78+
and aws.cloudtrail.user_identity.type in ("IAMUser", "AssumedRole")
79+
and source.ip is not null
80+
81+
// exclude known benign IaC tools and automation frameworks
82+
and not (
83+
user_agent.original LIKE "%Terraform%"
84+
or user_agent.original LIKE "%Ansible%"
85+
or user_agent.original LIKE "%Pulumni%"
86+
)
87+
88+
// filter for ASIA in tokens, indicating temporary session tokens
89+
and starts_with(aws.cloudtrail.user_identity.access_key_id, "ASIA")
90+
91+
// create a time window for aggregation
92+
| eval time_window = DATE_TRUNC(30 minutes, @timestamp)
93+
| keep source.ip, aws.cloudtrail.user_identity.arn
94+
95+
// aggregate unique source IPs per user within the time window
96+
| stats source.ip.list = VALUES(source.ip), address_api_request_count = count_distinct(source.ip) by aws.cloudtrail.user_identity.arn
97+
98+
// filter for users with multiple unique source IPs in the time window
99+
| where address_api_request_count >= 2
100+
'''
101+
102+
103+
[[rule.threat]]
104+
framework = "MITRE ATT&CK"
105+
[[rule.threat.technique]]
106+
id = "T1078"
107+
name = "Valid Accounts"
108+
reference = "https://attack.mitre.org/techniques/T1078/"
109+
[[rule.threat.technique.subtechnique]]
110+
id = "T1078.004"
111+
name = "Cloud Accounts"
112+
reference = "https://attack.mitre.org/techniques/T1078/004/"
113+
114+
115+
116+
[rule.threat.tactic]
117+
id = "TA0001"
118+
name = "Initial Access"
119+
reference = "https://attack.mitre.org/tactics/TA0001/"
120+

0 commit comments

Comments
 (0)