[New Rule][Rule Tuning] AWS Organizations/Account Discovery Coverage (#5910)

* [New Rule][Rule Tuning] AWS Organizations/Account Discovery Coverage

In response to the supply chain attack highlighted in (Kudelski’s Trivy / TeamPCP analysis)[https://kudelskisecurity.com/research/investigating-two-variants-of-the-trivy-supply-chain-compromise], I've added coverage for AWS Organization and Account reconnaissance which was called out in the research.

### AWS Discovery API Calls via CLI from a Single Resource
- Expanded our existing Multi-service discovery rule to include `event.provider: oraganizations.amazonaws.com`
- added the new `aws.cloudtrail.session_credential_from_console` field to exclude console behavior from this rule, and added appropriate `min_stack` to account for introduction of the field.

GAP: This rule detects aws-cli usage only. In the mentioned reference, attackers used Botocore and Boto3 tooling for this recon activity.

SOLUTION:

### AWS Account Discovery By Rare User
- Created a new Discovery rule focused solely on Organization/Account reconnaissance.
- Made it a new terms rule to reduce false positive noise from common behavior that might be seen using Boto3 or Botocore tooling.
- excluded console session behavior and service account behavior

Testing:
- Ran PACU's organization__enum module
- created a script that can be run to validate the query
- plenty of test data in our stack to run the query against

* Update rules/integrations/aws/discovery_organization_discovery_by_rare_user.toml

Co-authored-by: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com>

---------

Co-authored-by: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com>
This commit is contained in:
Isai
2026-04-03 14:54:25 -04:00
committed by GitHub
parent ae5ecd5346
commit c0b852a23d
2 changed files with 200 additions and 25 deletions
@@ -2,7 +2,9 @@
creation_date = "2024/11/04"
integration = ["aws"]
maturity = "production"
updated_date = "2026/03/24"
min_stack_comments = "aws.cloudtrail.session_credential_from_console field introduced in AWS integration version 4.6.0"
min_stack_version = "9.2.0"
updated_date = "2026/04/01"
[rule]
author = ["Elastic"]
@@ -14,8 +16,9 @@ gain a better understanding of the target's infrastructure.
"""
false_positives = [
"""
Administrators or automated systems may legitimately perform multiple `Describe`, `List`, `Get` and `Generate` API calls in a short
time frame. Verify the user identity and the purpose of the API calls to determine if the behavior is expected.
Administrators or automated systems may legitimately perform multiple `Describe`, `List`, `Get` and `Generate` API
calls in a short time frame. Verify the user identity and the purpose of the API calls to determine if the behavior
is expected.
""",
]
from = "now-6m"
@@ -29,7 +32,7 @@ note = """## Triage and analysis
This rule detects when a single AWS identity executes more than five unique discovery-related API calls (`Describe*`, `List*`, `Get*`, or `Generate*`) within a 10-second window using the AWS CLI.
High volumes of diverse “read-only” API calls in such a short period can indicate scripted reconnaissance, often an early phase of compromise after credential exposure or access to a compromised EC2 instance.
#### Possible Investigation Steps
### Possible investigation steps
**Identify the actor and session context**
- **Actor ARN (`aws.cloudtrail.user_identity.arn`)**: Determine which IAM user, role, or service principal performed the actions.
@@ -103,7 +106,10 @@ If the activity is unexpected or originates from unrecognized credentials, follo
- [AWS Customer Playbook Framework](https://github.com/aws-samples/aws-customer-playbook-framework)
"""
references = ["https://stratus-red-team.cloud/attack-techniques/AWS/aws.discovery.ec2-enumerate-from-instance/"]
references = [
"https://stratus-red-team.cloud/attack-techniques/AWS/aws.discovery.ec2-enumerate-from-instance/",
"https://kudelskisecurity.com/research/investigating-two-variants-of-the-trivy-supply-chain-compromise",
]
risk_score = 21
rule_id = "74f45152-9aee-11ef-b0a5-f661ea17fbcd"
severity = "low"
@@ -122,6 +128,7 @@ tags = [
"Data Source: AWS Cloudfront",
"Data Source: AWS DynamoDB",
"Data Source: AWS Elastic Load Balancing",
"Data Source: AWS Organizations",
"Use Case: Threat Detection",
"Tactic: Discovery",
"Resources: Investigation Guide",
@@ -136,7 +143,10 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index
| where
event.dataset == "aws.cloudtrail"
// filter on CloudTrail audit logs for IAM, EC2, S3, etc.
// exclude service account and console behavior
and source.ip IS NOT NULL
and aws.cloudtrail.session_credential_from_console IS NULL
and event.provider in (
"iam.amazonaws.com",
"ec2.amazonaws.com",
@@ -149,7 +159,8 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index
"elasticloadbalancing.amazonaws.com",
"cloudtrail.amazonaws.com",
"sts.amazonaws.com",
"ses.amazonaws.com"
"ses.amazonaws.com",
"organizations.amazonaws.com"
)
// ignore AWS service actions
and aws.cloudtrail.user_identity.type != "AWSService"
@@ -157,6 +168,7 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index
and user_agent.name == "aws-cli"
// exclude DescribeCapacityReservations events related to AWS Config
and event.action != "DescribeCapacityReservations"
and user.name != "AWSServiceRoleForConfig"
// filter for Describe, Get, List, and Generate API calls
| where true in (
@@ -166,11 +178,6 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index
starts_with(event.action, "Generate")
)
// extract owner, identity type, and actor from the ARN
| dissect aws.cloudtrail.user_identity.arn "%{}::%{Esql_priv.aws_cloudtrail_user_identity_arn_owner}:%{Esql.aws_cloudtrail_user_identity_arn_type}/%{Esql.aws_cloudtrail_user_identity_arn_roles}"
| where starts_with(Esql.aws_cloudtrail_user_identity_arn_roles, "AWSServiceRoleForConfig") != true
// keep relevant fields (preserving ECS fields and computed time window)
| keep
@timestamp,
@@ -210,6 +217,15 @@ from logs-aws.cloudtrail-* metadata _id, _version, _index
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1087"
name = "Account Discovery"
reference = "https://attack.mitre.org/techniques/T1087/"
[[rule.threat.technique.subtechnique]]
id = "T1087.004"
name = "Cloud Account"
reference = "https://attack.mitre.org/techniques/T1087/004/"
[[rule.threat.technique]]
id = "T1526"
@@ -221,23 +237,25 @@ id = "T1580"
name = "Cloud Infrastructure Discovery"
reference = "https://attack.mitre.org/techniques/T1580/"
[rule.threat.tactic]
id = "TA0007"
name = "Discovery"
reference = "https://attack.mitre.org/tactics/TA0007/"
[rule.investigation_fields]
field_names = [
"Esql.event_action_count_distinct",
"Esql.time_window_date_trunc",
"aws.cloudtrail.user_identity.arn",
"Esql.aws_cloudtrail_user_identity_type_values",
"Esql.aws_cloudtrail_user_identity_access_key_id_values",
"Esql.source_ip_values",
"Esql.source_as_organization_name_values",
"Esql.event_provider_values",
"Esql.event_action_values",
"Esql.cloud_account_id_values",
"Esql.cloud_region_values",
"Esql.data_stream_namespace_values"
]
"Esql.event_action_count_distinct",
"Esql.time_window_date_trunc",
"aws.cloudtrail.user_identity.arn",
"Esql.aws_cloudtrail_user_identity_type_values",
"Esql.aws_cloudtrail_user_identity_access_key_id_values",
"Esql.source_ip_values",
"Esql.source_as_organization_name_values",
"Esql.event_provider_values",
"Esql.event_action_values",
"Esql.cloud_account_id_values",
"Esql.cloud_region_values",
"Esql.data_stream_namespace_values",
]
@@ -0,0 +1,157 @@
[metadata]
creation_date = "2026/04/01"
integration = ["aws"]
maturity = "production"
updated_date = "2026/04/01"
min_stack_version = "9.2.0"
min_stack_comments = "aws.cloudtrail.session_credential_from_console field introduced in AWS integration version 4.6.0"
[rule]
author = ["Elastic"]
description = """
Identifies the first time, within a lookback window, an identity performs AWS Organizations or IAM account enumeration
APIs. Attackers with compromised credentials often map the organization (accounts, OUs, roots, delegated admins) and
account-level metadata (aliases, summary) using the AWS CLI or SDKs. This is a New Terms rule detecting a rare
occurrence of the `cloud.account.id` and `user.name` pair for these actions.
"""
false_positives = [
"""
Organization and security administrators, billing tooling, landing-zone automation, and delegated administrator
workflows may call these APIs legitimately. Interactive or one-off use from unusual principals warrants review.
""",
]
from = "now-6m"
index = ["logs-aws.cloudtrail-*"]
language = "kuery"
license = "Elastic License v2"
name = "AWS Account Discovery By Rare User"
note = """## Triage and analysis
### Investigating AWS Account Discovery By Rare User
AWS Organizations and IAM expose read APIs that reveal organization structure, member accounts, delegation, and
account-level aliases. Threat actors and tools such as Pacu (`organizations__enum`) chain these calls to understand
multi-account layout after credential access.
This rule uses [New Terms](https://www.elastic.co/guide/en/security/current/rules-ui-create.html#create-new-terms-rule) to detect when an identity makes a discovery API call that has not been seen in the configured history window.
### Possible investigation steps
**Identify the actor and session context**
- Confirm who `user.name` and `aws.cloudtrail.user_identity.arn` represent (human, workload role, automation).
**Analyze the source and origin**
- Review source.ip, geolocation, and whether the call aligns with normal egress for that principal.
- Inspect user_agent.original for CLI, Boto3/Botocore, consoles, or unfamiliar tooling.
**Correlate with additional events**
- Correlate with STS*(`GetCallerIdentity`, `AssumeRole`) and broader discovery or privilege changes in the same session.
- If the principal is new or rarely used, review IAM policies and recent key rotation.
### False positive analysis
- Documented org-admin or security roles in the management account; add exceptions by ARN if needed.
- Centralized compliance or CSPM that enumerates org structure on a schedule.
### Response and remediation
- If unexpected, rotate credentials for the implicated principal, review CloudTrail for follow-on API activity, and
tighten least privilege on Organizations/IAM read APIs where appropriate.
### Additional information
- **[AWS IR Playbooks](https://github.com/aws-samples/aws-incident-response-playbooks)**
"""
references = [
"https://kudelskisecurity.com/research/investigating-two-variants-of-the-trivy-supply-chain-compromise",
"https://github.com/RhinoSecurityLabs/pacu/blob/master/pacu/modules/organizations__enum/main.py",
]
risk_score = 21
rule_id = "444c8fad-874f-4f59-b0ea-cf26cea478bd"
severity = "low"
tags = [
"Domain: Cloud",
"Domain: Identity",
"Data Source: AWS",
"Data Source: Amazon Web Services",
"Data Source: AWS CloudTrail",
"Data Source: AWS Organizations",
"Data Source: AWS IAM",
"Use Case: Threat Detection",
"Tactic: Discovery",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "new_terms"
query = '''
event.dataset: "aws.cloudtrail"
and event.outcome: "success"
and source.ip:*
and not aws.cloudtrail.session_credential_from_console: "true"
and not aws.cloudtrail.user_identity.type: "AWSService"
and (
(
event.provider: "organizations.amazonaws.com"
and event.action: (
"DescribeOrganization" or "DescribeOrgnanizationalUnit" or "ListAccounts" or "ListRoots"
or "ListOrganizationalUnitsForParent" or "ListAccountsForParent" or "ListPolicies"
or "ListAWSServiceAccessForOrganization" or "ListDelegatedAdministrators"
or "ListDelegatedServicesForAccount" or "DescribeResourcePolicy"
)
)
or (
event.provider: "iam.amazonaws.com"
and event.action: ("ListAccountAliases" or "GetAccountSummary")
)
)
'''
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1087"
name = "Account Discovery"
reference = "https://attack.mitre.org/techniques/T1087/"
[[rule.threat.technique.subtechnique]]
id = "T1087.004"
name = "Cloud Account"
reference = "https://attack.mitre.org/techniques/T1087/004/"
[[rule.threat.technique]]
id = "T1580"
name = "Cloud Infrastructure Discovery"
reference = "https://attack.mitre.org/techniques/T1580/"
[rule.threat.tactic]
id = "TA0007"
name = "Discovery"
reference = "https://attack.mitre.org/tactics/TA0007/"
[rule.investigation_fields]
field_names = [
"@timestamp",
"user.name",
"user_agent.original",
"source.ip",
"aws.cloudtrail.user_identity.arn",
"aws.cloudtrail.user_identity.type",
"aws.cloudtrail.user_identity.access_key_id",
"event.action",
"event.outcome",
"event.provider",
"cloud.account.id",
"cloud.region",
]
[rule.new_terms]
field = "new_terms_fields"
value = ["cloud.account.id", "user.name"]
[[rule.new_terms.history_window_start]]
field = "history_window_start"
value = "now-10d"