AWS Decrypt SSM Parameters


Description

Identify principals retrieving a high number of SSM Parameters of type 'SecretString'. This rule filters out known administrative roles that legitimately need bulk parameter access.

Query · python

import datetime as dt
import json

from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context
from panther_core import PantherEvent
from panther_detection_helpers.caching import get_string_set, put_string_set

# Determine how many secrets must be accessed in order to trigger an alert
PARAM_THRESHOLD = 25

# Whitelisted IAM role name patterns (case-insensitive)
WHITELISTED_ROLE_PATTERNS = [
    "AWSReservedSSO_DevAdmin_",  # SSO admin roles
    "AWSReservedSSO_Admin_",  # SSO admin roles
]

all_param_names = set()


def rule(event: PantherEvent) -> bool:
    # Exclude events of the wrong type
    if not (
        event.get("eventName") in ("GetParameter", "GetParameters")
        and event.deep_get("requestParameters", "withDecryption")
    ):
        return False

    # Check if the role is whitelisted
    role_name = event.deep_get(
        "userIdentity", "sessionContext", "sessionIssuer", "userName", default=""
    )
    if role_name and any(
        pattern.lower() in role_name.lower() for pattern in WHITELISTED_ROLE_PATTERNS
    ):
        return False

    # Determine if this actor accessed any other params in this account
    key = get_cache_key(event)
    cached_params = get_cached_param_names(key)
    accessed_params = get_param_names(event)

    # Determine if the cache needs updating with new entries
    global all_param_names  # pylint: disable=global-statement
    all_param_names = cached_params | accessed_params
    if all_param_names - cached_params:
        # Only set the TTL if this is the first time we're adding to the cache
        #   Otherwise we'll be perpetually extending the lifespan of the cached data every time we
        #   add more.
        put_string_set(key, all_param_names, epoch_seconds=(3600 if not cached_params else None))

    # Check combined number of params
    return len(all_param_names) > PARAM_THRESHOLD


def title(event: PantherEvent) -> str:
    actor = event.udm("actor_user")
    account_name = event.get("recipientAccountId")
    return f"Excessive SSM parameter decryption by [{actor}] in [{account_name}]"


def severity(event: PantherEvent) -> str:
    # Demote to LOW if attempt was denied
    if not aws_cloudtrail_success(event):
        return "LOW"
    return "DEFAULT"


def alert_context(event: PantherEvent) -> dict:
    global all_param_names
    context = aws_rule_context(event)
    context.update({"accessedParams": list(all_param_names)})
    return context


def get_cache_key(event) -> str:
    """Use the field values in the event to generate a cache key unique to this actor and
    account ID."""
    offset = (
        dt.datetime.fromisoformat(event.get("p_event_time", "1970-01-01T00:00:00")).timestamp()
        // 3600
        * 3600
    )
    actor = event.udm("actor_user")
    account = event.get("recipientAccountId")
    rule_id = "AWS.SSM.DecryptSSMParams"
    return f"{rule_id}-{account}-{actor}-{offset}"


def get_param_names(event) -> set[str]:
    """Returns the accessed SSM Param names."""
    # Params could be either a list or a single entry
    params = set(event.deep_get("requestParameters", "names", default=[]))
    if single_param := event.deep_get("requestParameters", "name"):
        params.add(single_param)

    return params


def get_cached_param_names(key: str) -> set[str]:
    """Get any previously cached parameter names. Included automatic converstion from string in
    the case of a unit test mock."""
    cached_params = get_string_set(key, force_ttl_check=True)
    if isinstance(cached_params, str):
        # This is a unit test
        cached_params = set(json.loads(cached_params))
    return cached_params

Analyst notes

  1. Query CloudTrail for all GetParameter and GetParameters events by userIdentity.arn with requestParameters.withDecryption=true in the 4 hours around this alert to identify the complete list of accessed SSM parameters
  2. Review the parameter names from the resources array to determine if they contain database credentials, API keys, or encryption keys, and assess the impact if those secrets are compromised
  3. Search CloudTrail for other suspicious API calls by the same userIdentity.arn and sourceIPAddress in the 24 hours before the first parameter access, looking for privilege escalation, IAM changes, or unusual resource access
Raw source AWS Decrypt SSM Parameters · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: aws_ssm_decrypt_ssm_params.py
RuleID: "AWS.SSM.DecryptSSMParams"
DisplayName: AWS Decrypt SSM Parameters
Enabled: true
LogTypes:
  - AWS.CloudTrail
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0006:T1555
  Stratus Red Team:
    - aws.credential-access.ssm-retrieve-securestring-parameters
Description: >
  Identify principals retrieving a high number of SSM Parameters of type 'SecretString'.
  This rule filters out known administrative roles that legitimately need bulk parameter access.
Threshold: 25
Reference: >
  https://stratus-red-team.cloud/attack-techniques/AWS/aws.credential-access.ssm-retrieve-securestring-parameters/
Runbook: |
  1. Query CloudTrail for all GetParameter and GetParameters events by userIdentity.arn with requestParameters.withDecryption=true in the 4 hours around this alert to identify the complete list of accessed SSM parameters
  2. Review the parameter names from the resources array to determine if they contain database credentials, API keys, or encryption keys, and assess the impact if those secrets are compromised
  3. Search CloudTrail for other suspicious API calls by the same userIdentity.arn and sourceIPAddress in the 24 hours before the first parameter access, looking for privilege escalation, IAM changes, or unusual resource access
SummaryAttributes:
  - sourceIpAddress
  - p_alert_context.accessedParams
Tags:
  - AWS CloudTrail
  - 'Credential Access: Credentials from Password Stores'
Status: Experimental
Tests:
  - Name: Single Secret Accessed in Single Event
    ExpectedResult: true
    Mocks:
      - objectName: get_string_set
        returnValue: '["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y"]'
      - objectName: put_string_set
        returnValue: ''
    Log:
      {
        "p_event_time": "2025-02-14 19:43:09.000000000",
        "p_log_type": "AWS.CloudTrail",
        "awsRegion": "us-west-2",
        "eventCategory": "Management",
        "eventID": "587e6d58-a653-4fd9-859f-367dc1bad98c",
        "eventName": "GetParameter",
        "eventSource": "ssm.amazonaws.com",
        "eventTime": "2025-02-14 19:43:09.000000000",
        "eventType": "AwsApiCall",
        "eventVersion": "1.11",
        "managementEvent": true,
        "readOnly": true,
        "recipientAccountId": "111122223333",
        "requestID": "a1f28efd-9f5b-4a13-9878-86f57de594dc",
        "requestParameters": {
          "name": "/credentials/stratus-red-team/credentials-25",
          "withDecryption": true
        },
        "resources": [
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-25"
          }
        ],
        "sourceIPAddress": "1.2.3.4",
        "tlsDetails": {
          "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
          "clientProvidedHostHeader": "ssm.us-west-2.amazonaws.com",
          "tlsVersion": "TLSv1.2"
        },
        "userAgent": "example-user-agent",
        "userIdentity": {
          "accessKeyId": "EXAMPLE_ACCESS_KEY",
          "accountId": "111122223333",
          "arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt",
          "principalId": "SAMPLE_PRINCIPAL_ID:bobson.dugnutt",
          "sessionContext": {
            "attributes": {
              "creationDate": "2025-02-14T19:42:05Z",
              "mfaAuthenticated": "false"
            },
            "sessionIssuer": {
              "accountId": "111122223333",
              "arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
              "principalId": "SAMPLE_PRINCIPAL_ID",
              "type": "Role",
              "userName": "SampleRole"
            }
          },
          "type": "AssumedRole"
        }
      }
  - Name: Multiple Secrets Accessed in Same Event
    ExpectedResult: true
    Mocks:
      - objectName: get_string_set
        returnValue: '["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o"]'
      - objectName: put_string_set
        returnValue: ''
    Log:
      {
        "p_event_time": "2025-02-14 19:42:57.000000000",
        "p_log_type": "AWS.CloudTrail",
        "awsRegion": "us-west-2",
        "eventCategory": "Management",
        "eventID": "ce59873d-6a27-4fa4-afc1-088fceba71e4",
        "eventName": "GetParameters",
        "eventSource": "ssm.amazonaws.com",
        "eventTime": "2025-02-14 19:42:57.000000000",
        "eventType": "AwsApiCall",
        "eventVersion": "1.11",
        "managementEvent": true,
        "readOnly": true,
        "recipientAccountId": "111122223333",
        "requestID": "b6cb0ea5-2366-47c3-a4e5-acc31bc6882a",
        "requestParameters": {
          "names": [
            "/credentials/stratus-red-team/credentials-10",
            "/credentials/stratus-red-team/credentials-11",
            "/credentials/stratus-red-team/credentials-12",
            "/credentials/stratus-red-team/credentials-15",
            "/credentials/stratus-red-team/credentials-24",
            "/credentials/stratus-red-team/credentials-30",
            "/credentials/stratus-red-team/credentials-31",
            "/credentials/stratus-red-team/credentials-32",
            "/credentials/stratus-red-team/credentials-36",
            "/credentials/stratus-red-team/credentials-40",
            "/credentials/stratus-red-team/credentials-41",
          ],
          "withDecryption": true
        },
        "resources": [
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-10"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-11"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-12"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-15"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-24"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-30"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-31"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-32"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-36"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-40"
          }
        ],
        "sourceIPAddress": "1.2.3.4",
        "tlsDetails": {
          "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
          "clientProvidedHostHeader": "ssm.us-west-2.amazonaws.com",
          "tlsVersion": "TLSv1.2"
        },
        "userAgent": "example-user-agent",
        "userIdentity": {
          "accessKeyId": "EXAMPLE_ACCESS_KEY",
          "accountId": "111122223333",
          "arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt",
          "principalId": "SAMPLE_PRINCIPAL_ID:bobson.dugnutt",
          "sessionContext": {
            "attributes": {
              "creationDate": "2025-02-14T19:42:05Z",
              "mfaAuthenticated": "false"
            },
            "sessionIssuer": {
              "accountId": "111122223333",
              "arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
              "principalId": "SAMPLE_PRINCIPAL_ID",
              "type": "Role",
              "userName": "SampleRole"
            }
          },
          "type": "AssumedRole"
        }
      }
  - Name: Multiple Secrets Accessed in Same Event With Prior Cached Parameters
    ExpectedResult: true
    Mocks:
      - objectName: get_string_set
        returnValue: '["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u"]'
      - objectName: put_string_set
        returnValue: ''
    Log:
      {
        "p_event_time": "2025-02-14 19:42:57.000000000",
        "p_log_type": "AWS.CloudTrail",
        "awsRegion": "us-west-2",
        "eventCategory": "Management",
        "eventID": "ce59873d-6a27-4fa4-afc1-088fceba71e4",
        "eventName": "GetParameters",
        "eventSource": "ssm.amazonaws.com",
        "eventTime": "2025-02-14 19:42:57.000000000",
        "eventType": "AwsApiCall",
        "eventVersion": "1.11",
        "managementEvent": true,
        "readOnly": true,
        "recipientAccountId": "111122223333",
        "requestID": "b6cb0ea5-2366-47c3-a4e5-acc31bc6882a",
        "requestParameters": {
          "names": [
            "/credentials/stratus-red-team/credentials-10",
            "/credentials/stratus-red-team/credentials-11",
            "/credentials/stratus-red-team/credentials-12",
            "/credentials/stratus-red-team/credentials-15",
            "/credentials/stratus-red-team/credentials-24"
          ],
          "withDecryption": true
        },
        "resources": [
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-10"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-11"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-12"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-15"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-24"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-30"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-31"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-32"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-36"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-40"
          }
        ],
        "sourceIPAddress": "1.2.3.4",
        "tlsDetails": {
          "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
          "clientProvidedHostHeader": "ssm.us-west-2.amazonaws.com",
          "tlsVersion": "TLSv1.2"
        },
        "userAgent": "example-user-agent",
        "userIdentity": {
          "accessKeyId": "EXAMPLE_ACCESS_KEY",
          "accountId": "111122223333",
          "arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt",
          "principalId": "SAMPLE_PRINCIPAL_ID:bobson.dugnutt",
          "sessionContext": {
            "attributes": {
              "creationDate": "2025-02-14T19:42:05Z",
              "mfaAuthenticated": "false"
            },
            "sessionIssuer": {
              "accountId": "111122223333",
              "arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
              "principalId": "SAMPLE_PRINCIPAL_ID",
              "type": "Role",
              "userName": "SampleRole"
            }
          },
          "type": "AssumedRole"
        }
      }
  - Name: Accessed Parameters Aren't Encrypted
    ExpectedResult: false
    Mocks:
      - objectName: get_string_set
        returnValue: '[]'
      - objectName: put_string_set
        returnValue: ''
    Log:
      {
        "p_event_time": "2025-02-14 19:42:57.000000000",
        "p_log_type": "AWS.CloudTrail",
        "awsRegion": "us-west-2",
        "eventCategory": "Management",
        "eventID": "ce59873d-6a27-4fa4-afc1-088fceba71e4",
        "eventName": "GetParameters",
        "eventSource": "ssm.amazonaws.com",
        "eventTime": "2025-02-14 19:42:57.000000000",
        "eventType": "AwsApiCall",
        "eventVersion": "1.11",
        "managementEvent": true,
        "readOnly": true,
        "recipientAccountId": "111122223333",
        "requestID": "b6cb0ea5-2366-47c3-a4e5-acc31bc6882a",
        "requestParameters": {
          "names": [
            "/credentials/stratus-red-team/credentials-10",
            "/credentials/stratus-red-team/credentials-11",
            "/credentials/stratus-red-team/credentials-12",
            "/credentials/stratus-red-team/credentials-15",
            "/credentials/stratus-red-team/credentials-24",
            "/credentials/stratus-red-team/credentials-30",
            "/credentials/stratus-red-team/credentials-31",
            "/credentials/stratus-red-team/credentials-32",
            "/credentials/stratus-red-team/credentials-36",
            "/credentials/stratus-red-team/credentials-40",
            "/credentials/stratus-red-team/credentials-41",
          ]
        },
        "resources": [
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-10"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-11"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-12"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-15"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-24"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-30"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-31"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-32"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-36"
          },
          {
            "accountId": "111122223333",
            "arn": "arn:aws:ssm:us-west-2:111122223333:parameter/credentials/stratus-red-team/credentials-40"
          }
        ],
        "sourceIPAddress": "1.2.3.4",
        "tlsDetails": {
          "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
          "clientProvidedHostHeader": "ssm.us-west-2.amazonaws.com",
          "tlsVersion": "TLSv1.2"
        },
        "userAgent": "example-user-agent",
        "userIdentity": {
          "accessKeyId": "EXAMPLE_ACCESS_KEY",
          "accountId": "111122223333",
          "arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt",
          "principalId": "SAMPLE_PRINCIPAL_ID:bobson.dugnutt",
          "sessionContext": {
            "attributes": {
              "creationDate": "2025-02-14T19:42:05Z",
              "mfaAuthenticated": "false"
            },
            "sessionIssuer": {
              "accountId": "111122223333",
              "arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
              "principalId": "SAMPLE_PRINCIPAL_ID",
              "type": "Role",
              "userName": "SampleRole"
            }
          },
          "type": "AssumedRole"
        }
      }
  - Name: Unrelated Event
    ExpectedResult: false
    Log:
      {
        "awsRegion": "us-west-2",
        "eventCategory": "Management",
        "eventID": "6c6de06f-eb03-44cd-a95f-928a780ce28a",
        "eventName": "DescribeParameters",
        "eventSource": "ssm.amazonaws.com",
        "eventTime": "2025-02-14 19:43:07.000000000",
        "eventType": "AwsApiCall",
        "eventVersion": "1.11",
        "managementEvent": true,
        "readOnly": true,
        "recipientAccountId": "111122223333",
        "requestID": "9ea104aa-d9af-415f-9c56-b7bb98c7c73f",
        "requestParameters": {
          "parameterFilters": [
            {
              "key": "Name",
              "option": "Equals",
              "values": [
                "/credentials/stratus-red-team/credentials-1"
              ]
            }
          ]
        },
        "sourceIPAddress": "1.2.3.4",
        "tlsDetails": {
          "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
          "clientProvidedHostHeader": "ssm.us-west-2.amazonaws.com",
          "tlsVersion": "TLSv1.2"
        },
        "userAgent": "example-user-agent",
        "userIdentity": {
          "accessKeyId": "EXAMPLE_ACCESS_KEY",
          "accountId": "111122223333",
          "arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt",
          "principalId": "SAMPLE_PRINCIPAL_ID:bobson.dugnutt",
          "sessionContext": {
            "attributes": {
              "creationDate": "2025-02-14T19:42:05Z",
              "mfaAuthenticated": "false"
            },
            "sessionIssuer": {
              "accountId": "111122223333",
              "arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
              "principalId": "SAMPLE_PRINCIPAL_ID",
              "type": "Role",
              "userName": "SampleRole"
            }
          },
          "type": "AssumedRole"
        }
      }

# ------ paired body: aws_ssm_decrypt_ssm_params.py ------

import datetime as dt
import json

from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context
from panther_core import PantherEvent
from panther_detection_helpers.caching import get_string_set, put_string_set

# Determine how many secrets must be accessed in order to trigger an alert
PARAM_THRESHOLD = 25

# Whitelisted IAM role name patterns (case-insensitive)
WHITELISTED_ROLE_PATTERNS = [
    "AWSReservedSSO_DevAdmin_",  # SSO admin roles
    "AWSReservedSSO_Admin_",  # SSO admin roles
]

all_param_names = set()


def rule(event: PantherEvent) -> bool:
    # Exclude events of the wrong type
    if not (
        event.get("eventName") in ("GetParameter", "GetParameters")
        and event.deep_get("requestParameters", "withDecryption")
    ):
        return False

    # Check if the role is whitelisted
    role_name = event.deep_get(
        "userIdentity", "sessionContext", "sessionIssuer", "userName", default=""
    )
    if role_name and any(
        pattern.lower() in role_name.lower() for pattern in WHITELISTED_ROLE_PATTERNS
    ):
        return False

    # Determine if this actor accessed any other params in this account
    key = get_cache_key(event)
    cached_params = get_cached_param_names(key)
    accessed_params = get_param_names(event)

    # Determine if the cache needs updating with new entries
    global all_param_names  # pylint: disable=global-statement
    all_param_names = cached_params | accessed_params
    if all_param_names - cached_params:
        # Only set the TTL if this is the first time we're adding to the cache
        #   Otherwise we'll be perpetually extending the lifespan of the cached data every time we
        #   add more.
        put_string_set(key, all_param_names, epoch_seconds=(3600 if not cached_params else None))

    # Check combined number of params
    return len(all_param_names) > PARAM_THRESHOLD


def title(event: PantherEvent) -> str:
    actor = event.udm("actor_user")
    account_name = event.get("recipientAccountId")
    return f"Excessive SSM parameter decryption by [{actor}] in [{account_name}]"


def severity(event: PantherEvent) -> str:
    # Demote to LOW if attempt was denied
    if not aws_cloudtrail_success(event):
        return "LOW"
    return "DEFAULT"


def alert_context(event: PantherEvent) -> dict:
    global all_param_names
    context = aws_rule_context(event)
    context.update({"accessedParams": list(all_param_names)})
    return context


def get_cache_key(event) -> str:
    """Use the field values in the event to generate a cache key unique to this actor and
    account ID."""
    offset = (
        dt.datetime.fromisoformat(event.get("p_event_time", "1970-01-01T00:00:00")).timestamp()
        // 3600
        * 3600
    )
    actor = event.udm("actor_user")
    account = event.get("recipientAccountId")
    rule_id = "AWS.SSM.DecryptSSMParams"
    return f"{rule_id}-{account}-{actor}-{offset}"


def get_param_names(event) -> set[str]:
    """Returns the accessed SSM Param names."""
    # Params could be either a list or a single entry
    params = set(event.deep_get("requestParameters", "names", default=[]))
    if single_param := event.deep_get("requestParameters", "name"):
        params.add(single_param)

    return params


def get_cached_param_names(key: str) -> set[str]:
    """Get any previously cached parameter names. Included automatic converstion from string in
    the case of a unit test mock."""
    cached_params = get_string_set(key, force_ttl_check=True)
    if isinstance(cached_params, str):
        # This is a unit test
        cached_params = set(json.loads(cached_params))
    return cached_params

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.