Detection rules › Panther

AWS IMDS Credential Usage Outside Expected Services

Status
Experimental
Severity
high
Log types
AWS.CloudTrail
Tags
AWS, AWS STS, Privilege Escalation:Valid Accounts, Defense Evasion:Valid Accounts
Reference
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
Source
github.com/panther-labs/panther-analysis

Detects when an EC2 instance identity (credentials obtained via IMDS) is used to make API calls outside of expected internal AWS services like SSM. This indicates that IMDS credentials may have been exfiltrated from a compromised instance and are being used externally for lateral movement or privilege escalation.

MITRE ATT&CK coverage

Rule body yaml

AnalysisType: rule
Filename: aws_imds_credential_exfiltration.py
RuleID: "AWS.CloudTrail.IMDSCredentialExfiltration"
DisplayName: "AWS IMDS Credential Usage Outside Expected Services"
Enabled: true
LogTypes:
  - AWS.CloudTrail
Tags:
  - AWS
  - AWS STS
  - Privilege Escalation:Valid Accounts
  - Defense Evasion:Valid Accounts
Reports:
  MITRE ATT&CK:
    - TA0004:T1078.004
    - TA0005:T1078.004
Status: Experimental
Severity: High
Description: >
  Detects when an EC2 instance identity (credentials obtained via IMDS) is used to
  make API calls outside of expected internal AWS services like SSM. This indicates
  that IMDS credentials may have been exfiltrated from a compromised instance and
  are being used externally for lateral movement or privilege escalation.
Runbook: |
  1. Query CloudTrail for all API calls by userIdentity:arn in the 24 hours before and after this alert to identify the full scope of actions taken with the exfiltrated instance credentials
  2. Check if sourceIPAddress is external to AWS or outside the expected VPC CIDR ranges for the EC2 instance associated with this role
  3. Find all other alerts associated with this userIdentity:arn or sourceIPAddress in the past 7 days to assess whether this is part of a broader compromise campaign
Reference: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
SummaryAttributes:
  - userAgent
  - sourceIpAddress
  - recipientAccountId
  - p_any_aws_arns
DedupPeriodMinutes: 60
Threshold: 1
Tests:
  - Name: IMDS Credentials Used From Public IP
    ExpectedResult: true
    Log:
      {
        "awsRegion": "us-east-1",
        "eventName": "DescribeInstances",
        "eventSource": "ec2.amazonaws.com",
        "eventTime": "2024-01-15T10:30:00Z",
        "eventType": "AwsApiCall",
        "recipientAccountId": "123456789012",
        "sourceIPAddress": "45.33.32.156",
        "userAgent": "aws-cli/2.15.0",
        "userIdentity": {
          "accessKeyId": "ASIAIOSFODNN7EXAMPLE",
          "accountId": "123456789012",
          "arn": "arn:aws:sts::123456789012:assumed-role/MyAppRole/i-0abcdef1234567890",
          "principalId": "AROAEXAMPLE:i-0abcdef1234567890",
          "type": "AssumedRole",
          "sessionContext": {
            "sessionIssuer": {
              "type": "Role",
              "arn": "arn:aws:iam::123456789012:role/MyAppRole"
            }
          }
        }
      }
  - Name: Normal EC2 Workload From Private IP
    ExpectedResult: false
    Log:
      {
        "awsRegion": "us-east-1",
        "eventName": "PutObject",
        "eventSource": "s3.amazonaws.com",
        "eventTime": "2024-01-15T10:30:00Z",
        "eventType": "AwsApiCall",
        "recipientAccountId": "123456789012",
        "sourceIPAddress": "10.0.1.50",
        "userAgent": "aws-sdk-java/2.20.0",
        "userIdentity": {
          "arn": "arn:aws:sts::123456789012:assumed-role/MyAppRole/i-0abcdef1234567890",
          "type": "AssumedRole"
        }
      }
  - Name: AWS Service Hostname as Source IP
    ExpectedResult: false
    Log:
      {
        "awsRegion": "us-east-1",
        "eventName": "Decrypt",
        "eventSource": "kms.amazonaws.com",
        "eventTime": "2024-01-15T10:30:00Z",
        "eventType": "AwsApiCall",
        "recipientAccountId": "123456789012",
        "sourceIPAddress": "cloudformation.amazonaws.com",
        "userIdentity": {
          "arn": "arn:aws:sts::123456789012:assumed-role/MyAppRole/i-0abcdef1234567890",
          "type": "AssumedRole"
        }
      }
  - Name: Legitimate SSM Activity
    ExpectedResult: false
    Log:
      {
        "awsRegion": "us-east-1",
        "eventName": "UpdateInstanceInformation",
        "eventSource": "ssm.amazonaws.com",
        "eventTime": "2024-01-15T10:30:00Z",
        "eventType": "AwsApiCall",
        "recipientAccountId": "123456789012",
        "sourceIPAddress": "10.0.1.50",
        "userAgent": "amazon-ssm-agent/3.2.0",
        "userIdentity": {
          "arn": "arn:aws:sts::123456789012:assumed-role/MyAppRole/i-0abcdef1234567890",
          "type": "AssumedRole"
        }
      }
  - Name: AWS Internal Source IP
    ExpectedResult: false
    Log:
      {
        "awsRegion": "us-east-1",
        "eventName": "SendHeartbeat",
        "eventSource": "ec2messages.amazonaws.com",
        "eventTime": "2024-01-15T10:30:00Z",
        "eventType": "AwsApiCall",
        "recipientAccountId": "123456789012",
        "sourceIPAddress": "AWS Internal",
        "userAgent": "amazon-ssm-agent/3.2.0",
        "userIdentity": {
          "arn": "arn:aws:sts::123456789012:assumed-role/MyAppRole/i-0abcdef1234567890",
          "type": "AssumedRole"
        }
      }
  - Name: RegisterManagedInstance Event
    ExpectedResult: false
    Log:
      {
        "awsRegion": "us-east-1",
        "eventName": "RegisterManagedInstance",
        "eventSource": "ec2.amazonaws.com",
        "eventTime": "2024-01-15T10:30:00Z",
        "eventType": "AwsApiCall",
        "recipientAccountId": "123456789012",
        "sourceIPAddress": "10.0.1.50",
        "userIdentity": {
          "arn": "arn:aws:sts::123456789012:assumed-role/MyAppRole/i-0abcdef1234567890",
          "type": "AssumedRole"
        }
      }
  - Name: Non-Instance Role
    ExpectedResult: false
    Log:
      {
        "awsRegion": "us-east-1",
        "eventName": "DescribeInstances",
        "eventSource": "ec2.amazonaws.com",
        "eventTime": "2024-01-15T10:30:00Z",
        "eventType": "AwsApiCall",
        "recipientAccountId": "123456789012",
        "sourceIPAddress": "45.33.32.156",
        "userAgent": "aws-cli/2.15.0",
        "userIdentity": {
          "arn": "arn:aws:sts::123456789012:assumed-role/MyCustomRole/session",
          "type": "AssumedRole"
        }
      }

Detection logic

Condition

eventSource not in "ssm.amazonaws.com"
eventName not in "RegisterManagedInstance"
sourceIPAddress not in "AWS Internal"

This rule also runs imperative logic the parser cannot express as a filter; the conditions above are the structured part it could extract.

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
eventNameeqRegisterManagedInstance
eventSourceeqssm.amazonaws.com
sourceIPAddresseqAWS Internal

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
eventName
eventSource
awsRegion
recipientAccountId
sourceIPAddress
userAgent
userIdentity
arnuserIdentity.arn