External Principal Accessing AWS Resources Via VPC Endpoint


Description

This rule detects when a principal from one AWS account accesses resources in a different AWS account using a VPC Endpoint. While cross-account access may be expected in some cases, it could also indicate unauthorized lateral movement between AWS accounts.

Query · python

from panther_aws_helpers import aws_rule_context


def rule(event):
    # Check if this is a VPC Endpoint network activity event
    if event.get("eventType") != "AwsVpceEvent" or event.get("eventCategory") != "NetworkActivity":
        return False

    # Look for external principal pattern (limited userIdentity field)
    user_identity = event.get("userIdentity", {})

    # If it's an AWS account type without full identity details, it could be an external principal
    if (
        user_identity.get("type") == "AWSAccount"
        and "arn" not in user_identity
        and "principalId" in user_identity
    ):
        # Get the account ID from the event and compare with the principal's account
        event_account = event.get("recipientAccountId")
        principal_account = user_identity.get("accountId")

        # If the accounts don't match, it's an external principal
        if event_account and principal_account and event_account != principal_account:
            return True

    return False


def title(event):
    # Use UDM actor_user which leverages the get_actor_user helper function
    # This properly handles various identity types including AssumedRole, Root, etc.
    actor_user = event.udm("actor_user")
    principal_account = event.deep_get("userIdentity", "accountId", default="unknown")
    event_account = event.get("recipientAccountId", "unknown")

    return (
        f"External Principal [{actor_user}] from account [{principal_account}] "
        f"accessing resources in account [{event_account}]"
    )


def alert_context(event):
    principal_account = event.deep_get("userIdentity", "accountId", default="")
    event_account = event.get("recipientAccountId", "")

    context = aws_rule_context(event)
    context.update(
        {
            "event_account": event_account,
            "principal_account": principal_account,
            "principal_id": event.deep_get("userIdentity", "principalId", default="unknown"),
            "source_ip": event.get("sourceIPAddress", "unknown"),
            "event_source": event.get("eventSource", "unknown"),
            "api_call": event.get("eventName", "unknown"),
            "resources": event.get("resources", []),
            "actor_user": event.udm("actor_user"),
        }
    )

    return context

Analyst notes

  1. Identify the principal account and the accessed account from the alert context.
  2. Verify if the cross-account access is expected and authorized: - Check if the principal account is part of your organization - Review IAM policies for the VPC Endpoint to confirm if cross-account access is intentional - Check resource policies for the accessed service to confirm if the principal should have access
  3. If the access is unexpected: - Review the API calls made by the principal - Check the VPC Endpoint configuration for potential misconfiguration - Consider restricting VPC Endpoint access to prevent unauthorized cross-account access - Investigate for additional signs of unauthorized access
  4. Document findings and take appropriate remediation steps based on investigation.
Raw source External Principal Accessing AWS Resources Via VPC Endpoint · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: aws_vpce_external_principal.py
RuleID: AWS.CloudTrail.VPCE.ExternalPrincipal
DisplayName: External Principal Accessing AWS Resources Via VPC Endpoint
Enabled: true
LogTypes:
  - AWS.CloudTrail
Tags:
  - AWS
  - CloudTrail
  - VPCEndpoint
  - Network Boundary Bridging
  - Cloud Service Discovery
  - Exfiltration Over Alternative Protocol
Reports:
  MITRE ATT&CK:
    - TA0005:T1599 # Network Boundary Bridging
    - TA0007:T1526 # Cloud Service Discovery
    - TA0010:T1048 # Exfiltration Over Alternative Protocol
Severity: Medium
Description: >
  This rule detects when a principal from one AWS account accesses resources in a different AWS account using a VPC Endpoint.
  While cross-account access may be expected in some cases, it could also indicate unauthorized lateral movement between AWS accounts.
Runbook: |
  1. Identify the principal account and the accessed account from the alert context.
  2. Verify if the cross-account access is expected and authorized:
     - Check if the principal account is part of your organization
     - Review IAM policies for the VPC Endpoint to confirm if cross-account access is intentional
     - Check resource policies for the accessed service to confirm if the principal should have access
  3. If the access is unexpected:
     - Review the API calls made by the principal
     - Check the VPC Endpoint configuration for potential misconfiguration
     - Consider restricting VPC Endpoint access to prevent unauthorized cross-account access
     - Investigate for additional signs of unauthorized access
  4. Document findings and take appropriate remediation steps based on investigation.
Reference: https://www.wiz.io/blog/aws-vpc-endpoint-cloudtrail
Tests:
  - Name: External Principal Access
    ExpectedResult: true
    Log:
      {
        "eventVersion": "1.08",
        "eventCategory": "NetworkActivity",
        "eventType": "AwsVpceEvent",
        "eventTime": "2023-03-01T00:00:00Z",
        "awsRegion": "us-east-1",
        "eventSource": "s3.amazonaws.com",
        "eventName": "GetObject",
        "sourceIPAddress": "10.0.0.1",
        "userIdentity": {
          "type": "AWSAccount",
          "accountId": "111111111111",
          "principalId": "AROAEXAMPLE:session-name"
        },
        "recipientAccountId": "222222222222",
        "requestParameters": {
          "bucketName": "example-bucket",
          "key": "sensitive-file.txt"
        },
        "responseElements": null,
        "vpcEndpointId": "vpce-EXAMPLE08c1b6b9b7",
        "vpcEndpointAccountId": "222222222222"
      }

  - Name: Same Account Access
    ExpectedResult: false
    Log:
      {
        "eventVersion": "1.08",
        "eventCategory": "NetworkActivity",
        "eventType": "AwsVpceEvent",
        "eventTime": "2023-03-01T00:00:00Z",
        "awsRegion": "us-east-1",
        "eventSource": "s3.amazonaws.com",
        "eventName": "GetObject",
        "sourceIPAddress": "10.0.0.1",
        "userIdentity": {
          "type": "AWSAccount",
          "accountId": "222222222222",
          "principalId": "AROAEXAMPLE:session-name"
        },
        "recipientAccountId": "222222222222",
        "requestParameters": {
          "bucketName": "example-bucket",
          "key": "sensitive-file.txt"
        },
        "responseElements": null,
        "vpcEndpointId": "vpce-EXAMPLE08c1b6b9b7",
        "vpcEndpointAccountId": "222222222222"
      }

  - Name: Non-VPC Event
    ExpectedResult: false
    Log:
      {
        "eventVersion": "1.08",
        "eventCategory": "Management",
        "eventType": "AwsApiCall",
        "eventTime": "2023-03-01T00:00:00Z",
        "awsRegion": "us-east-1",
        "eventSource": "s3.amazonaws.com",
        "eventName": "GetObject",
        "sourceIPAddress": "10.0.0.1",
        "userIdentity": {
          "type": "AWSAccount",
          "accountId": "111111111111",
          "principalId": "AROAEXAMPLE:session-name"
        },
        "recipientAccountId": "222222222222",
        "requestParameters": {
          "bucketName": "example-bucket",
          "key": "sensitive-file.txt"
        },
        "responseElements": null
      } 

# ------ paired body: aws_vpce_external_principal.py ------

from panther_aws_helpers import aws_rule_context


def rule(event):
    # Check if this is a VPC Endpoint network activity event
    if event.get("eventType") != "AwsVpceEvent" or event.get("eventCategory") != "NetworkActivity":
        return False

    # Look for external principal pattern (limited userIdentity field)
    user_identity = event.get("userIdentity", {})

    # If it's an AWS account type without full identity details, it could be an external principal
    if (
        user_identity.get("type") == "AWSAccount"
        and "arn" not in user_identity
        and "principalId" in user_identity
    ):
        # Get the account ID from the event and compare with the principal's account
        event_account = event.get("recipientAccountId")
        principal_account = user_identity.get("accountId")

        # If the accounts don't match, it's an external principal
        if event_account and principal_account and event_account != principal_account:
            return True

    return False


def title(event):
    # Use UDM actor_user which leverages the get_actor_user helper function
    # This properly handles various identity types including AssumedRole, Root, etc.
    actor_user = event.udm("actor_user")
    principal_account = event.deep_get("userIdentity", "accountId", default="unknown")
    event_account = event.get("recipientAccountId", "unknown")

    return (
        f"External Principal [{actor_user}] from account [{principal_account}] "
        f"accessing resources in account [{event_account}]"
    )


def alert_context(event):
    principal_account = event.deep_get("userIdentity", "accountId", default="")
    event_account = event.get("recipientAccountId", "")

    context = aws_rule_context(event)
    context.update(
        {
            "event_account": event_account,
            "principal_account": principal_account,
            "principal_id": event.deep_get("userIdentity", "principalId", default="unknown"),
            "source_ip": event.get("sourceIPAddress", "unknown"),
            "event_source": event.get("eventSource", "unknown"),
            "api_call": event.get("eventName", "unknown"),
            "resources": event.get("resources", []),
            "actor_user": event.udm("actor_user"),
        }
    )

    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.