AWS RDS Snapshot Enumeration with Public or Shared Flag


Description

Detects when RDS snapshots are queried with includePublic or includeShared flags. This indicates reconnaissance for publicly accessible or shared snapshots that may contain sensitive data and be exploitable by attackers.

Query · python

from panther_aws_helpers import aws_rule_context


def rule(event):
    if event.get("eventSource") != "rds.amazonaws.com":
        return False
    describe_events = ["DescribeDBSnapshots", "DescribeDBClusterSnapshots"]
    if event.get("eventName") not in describe_events:
        return False
    if event.deep_get("errorCode") is not None:
        return False
    include_public = event.deep_get("requestParameters", "includePublic", default=False)
    include_shared = event.deep_get("requestParameters", "includeShared", default=False)
    if include_public or include_shared:
        return True
    return False


def title(event):
    event_name = event.get("eventName", "Unknown")
    user = event.deep_get("userIdentity", "userName") or event.deep_get(
        "userIdentity", "principalId", default="<UNKNOWN_USER>"
    )
    include_public = event.deep_get("requestParameters", "includePublic", default=False)
    include_shared = event.deep_get("requestParameters", "includeShared", default=False)
    if include_public and include_shared:
        scope = "Public and Shared"
    elif include_public:
        scope = "Public"
    elif include_shared:
        scope = "Shared"
    else:
        scope = "Unknown"
    resource_type = "Snapshots" if event_name == "DescribeDBSnapshots" else "Cluster Snapshots"
    return f"RDS {resource_type} Enumeration: {scope} snapshots queried by [{user}]"


def dedup(event):
    user_arn = event.deep_get("userIdentity", "arn", default="unknown")
    account_id = event.deep_get("recipientAccountId", default="unknown")
    region = event.get("awsRegion", "unknown")
    return f"{account_id}:{region}:{user_arn}"


def alert_context(event):
    context = aws_rule_context(event)
    context["include_public"] = event.deep_get("requestParameters", "includePublic", default="N/A")
    context["include_shared"] = event.deep_get("requestParameters", "includeShared", default="N/A")
    context["max_records"] = event.deep_get("requestParameters", "maxRecords", default="N/A")
    return context

Analyst notes

  1. Find all snapshot enumeration events by the user ARN in the past 6 hours to identify systematic patterns
  2. Check if the source IP address is associated with known VPN or proxy services
  3. Look for snapshot copy or restore operations from this user in the 24 hours after the enumeration
Raw source AWS RDS Snapshot Enumeration with Public or Shared Flag · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: aws_rds_snapshot_enumeration.py
RuleID: "AWS.RDS.SnapshotEnumeration"
DisplayName: "AWS RDS Snapshot Enumeration with Public or Shared Flag"
Enabled: true
Status: Experimental
LogTypes:
  - AWS.CloudTrail
Tags:
  - AWS
  - Discovery
  - Cloud Infrastructure Discovery
  - Reconnaissance
  - RDS
Severity: Low
Description: >
  Detects when RDS snapshots are queried with includePublic or includeShared flags.
  This indicates reconnaissance for publicly accessible or shared snapshots that may
  contain sensitive data and be exploitable by attackers.
Runbook: |
  1. Find all snapshot enumeration events by the user ARN in the past 6 hours to identify systematic patterns
  2. Check if the source IP address is associated with known VPN or proxy services
  3. Look for snapshot copy or restore operations from this user in the 24 hours after the enumeration
Reference: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ListSnapshots.html
Reports:
  MITRE ATT&CK:
    - TA0007:T1580  # Cloud Infrastructure Discovery
DedupPeriodMinutes: 60
SummaryAttributes:
  - eventName
  - userIdentity:principalId
  - requestParameters:includePublic
  - requestParameters:includeShared
  - sourceIPAddress
  - p_any_aws_account_ids
Threshold: 1
Tests:
  - Name: Snapshot Enumeration with Public Flag
    ExpectedResult: true
    Log:
      eventVersion: "1.08"
      userIdentity:
        type: IAMUser
        principalId: "AIDAI23HXS3EXAMPLE"
        arn: "arn:aws:iam::123456789012:user/researcher"
        accountId: "123456789012"
        accessKeyId: "AKIAIOSFODNN7EXAMPLE"
        userName: researcher
      eventTime: "2024-01-18T05:00:00Z"
      eventSource: rds.amazonaws.com
      eventName: DescribeDBSnapshots
      awsRegion: us-east-1
      sourceIPAddress: "185.220.101.75"
      userAgent: "Boto3/1.26.0 Python/3.9.0"
      requestParameters:
        includePublic: true
        maxRecords: 100
      responseElements: null
      requestID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
      eventID: "f1e2d3c4-b5a6-7890-1234-567890abcdef"
      readOnly: true
      eventType: AwsApiCall
      managementEvent: true
      recipientAccountId: "123456789012"
      eventCategory: Management

  - Name: Cluster Snapshot Enumeration with Shared Flag
    ExpectedResult: true
    Log:
      eventVersion: "1.08"
      userIdentity:
        type: AssumedRole
        principalId: "AIDAI23HXS3EXAMPLE:user"
        arn: "arn:aws:sts::123456789012:assumed-role/SecurityAuditor/user"
        accountId: "123456789012"
        accessKeyId: "ASIAIOSFODNN7EXAMPLE"
      eventTime: "2024-01-18T05:00:00Z"
      eventSource: rds.amazonaws.com
      eventName: DescribeDBClusterSnapshots
      awsRegion: us-west-2
      sourceIPAddress: "203.0.113.150"
      userAgent: "aws-cli/2.13.0"
      requestParameters:
        includeShared: true
        maxRecords: 50
      responseElements: null
      requestID: "b2c3d4e5-f6a7-8901-bcde-f1234567890a"
      eventID: "g2f3e4d5-c6b7-8901-2345-678901bcdefg"
      readOnly: true
      eventType: AwsApiCall
      managementEvent: true
      recipientAccountId: "123456789012"
      eventCategory: Management

  - Name: Normal Snapshot Query - No Public or Shared Flag
    ExpectedResult: false
    Log:
      eventVersion: "1.08"
      userIdentity:
        type: AssumedRole
        principalId: "AIDAI23HXS3EXAMPLE:admin"
        arn: "arn:aws:sts::123456789012:assumed-role/DBARole/admin"
        accountId: "123456789012"
        accessKeyId: "ASIAIOSFODNN7EXAMPLE"
      eventTime: "2024-01-18T05:00:00Z"
      eventSource: rds.amazonaws.com
      eventName: DescribeDBSnapshots
      awsRegion: us-east-1
      sourceIPAddress: "10.0.1.100"
      userAgent: "console.amazonaws.com"
      requestParameters:
        dBInstanceIdentifier: "my-database"
        maxRecords: 20
      responseElements: null
      requestID: "c3d4e5f6-a7b8-9012-cdef-1234567890ab"
      eventID: "h3g4f5e6-d7c8-9012-3456-789012cdefgh"
      readOnly: true
      eventType: AwsApiCall
      managementEvent: true
      recipientAccountId: "123456789012"
      eventCategory: Management

  - Name: Query Failed
    ExpectedResult: false
    Log:
      eventVersion: "1.08"
      userIdentity:
        type: IAMUser
        principalId: "AIDAI23HXS3EXAMPLE"
        arn: "arn:aws:iam::123456789012:user/developer"
        accountId: "123456789012"
        accessKeyId: "AKIAIOSFODNN7EXAMPLE"
        userName: developer
      eventTime: "2024-01-18T05:00:00Z"
      eventSource: rds.amazonaws.com
      eventName: DescribeDBSnapshots
      awsRegion: us-east-1
      sourceIPAddress: "10.0.2.100"
      userAgent: "aws-cli/2.13.0"
      requestParameters:
        includePublic: true
      errorCode: AccessDenied
      errorMessage: "User is not authorized to perform: rds:DescribeDBSnapshots"
      requestID: "d4e5f6a7-b8c9-0123-def0-1234567890bc"
      eventID: "i4h5g6f7-e8d9-0123-4567-890123defghi"
      readOnly: true
      eventType: AwsApiCall
      managementEvent: true
      recipientAccountId: "123456789012"
      eventCategory: Management


# ------ paired body: aws_rds_snapshot_enumeration.py ------

from panther_aws_helpers import aws_rule_context


def rule(event):
    if event.get("eventSource") != "rds.amazonaws.com":
        return False
    describe_events = ["DescribeDBSnapshots", "DescribeDBClusterSnapshots"]
    if event.get("eventName") not in describe_events:
        return False
    if event.deep_get("errorCode") is not None:
        return False
    include_public = event.deep_get("requestParameters", "includePublic", default=False)
    include_shared = event.deep_get("requestParameters", "includeShared", default=False)
    if include_public or include_shared:
        return True
    return False


def title(event):
    event_name = event.get("eventName", "Unknown")
    user = event.deep_get("userIdentity", "userName") or event.deep_get(
        "userIdentity", "principalId", default="<UNKNOWN_USER>"
    )
    include_public = event.deep_get("requestParameters", "includePublic", default=False)
    include_shared = event.deep_get("requestParameters", "includeShared", default=False)
    if include_public and include_shared:
        scope = "Public and Shared"
    elif include_public:
        scope = "Public"
    elif include_shared:
        scope = "Shared"
    else:
        scope = "Unknown"
    resource_type = "Snapshots" if event_name == "DescribeDBSnapshots" else "Cluster Snapshots"
    return f"RDS {resource_type} Enumeration: {scope} snapshots queried by [{user}]"


def dedup(event):
    user_arn = event.deep_get("userIdentity", "arn", default="unknown")
    account_id = event.deep_get("recipientAccountId", default="unknown")
    region = event.get("awsRegion", "unknown")
    return f"{account_id}:{region}:{user_arn}"


def alert_context(event):
    context = aws_rule_context(event)
    context["include_public"] = event.deep_get("requestParameters", "includePublic", default="N/A")
    context["include_shared"] = event.deep_get("requestParameters", "includeShared", default="N/A")
    context["max_records"] = event.deep_get("requestParameters", "maxRecords", default="N/A")
    return context

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.