S3 Access Via VPC Endpoint From External IP


Description

Detects S3 data access through VPC endpoints from external/public IP addresses, which could indicate data exfiltration attempts.

This rule can be customized with the following overrides: - S3_DATA_ACCESS_OPERATIONS: List of S3 operations to monitor

Query · python

import ipaddress

from panther_aws_helpers import aws_rule_context

# Define S3 data access operations
S3_DATA_ACCESS_OPERATIONS = [
    "GetObject",
    "GetObjectVersion",
    "GetObjectAcl",
    "GetObjectVersionAcl",
    "PutObject",
    "PutObjectAcl",
    "PutObjectVersionAcl",
    "CopyObject",
    "DeleteObject",
    "DeleteObjects",
    "DeleteObjectVersion",
]


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

    # Focus on data access operations
    if event.get("eventName") not in S3_DATA_ACCESS_OPERATIONS:
        return False

    # Check for external IP
    source_ip = event.get("sourceIPAddress", "")
    if not source_ip:
        return False

    try:
        ip_obj = ipaddress.ip_address(source_ip)
        if ip_obj.is_global:
            return True
    except ValueError:
        # If source_ip is not a valid IP address
        pass

    return False


def title(event):
    # Use UDM actor_user which leverages the get_actor_user helper function
    actor_user = event.udm("actor_user")
    source_ip = event.get("sourceIPAddress", "unknown")
    bucket_name = event.deep_get("requestParameters", "bucketName", default="unknown")

    return (
        f"S3 Access via VPC Endpoint from External IP: [{actor_user}] from "
        f"[{source_ip}] to bucket [{bucket_name}]"
    )


def alert_context(event):
    account_id = event.deep_get("userIdentity", "accountId", default="")

    context = aws_rule_context(event)
    context.update(
        {
            "account_id": account_id,
            "principal_id": event.deep_get("userIdentity", "principalId", default="unknown"),
            "actor_user": event.udm("actor_user"),
            "source_ip": event.get("sourceIPAddress", "unknown"),
            "event_source": event.get("eventSource", "unknown"),
            "api_call": event.get("eventName", "unknown"),
            "resources": event.get("resources", []),
            "request_parameters": event.get("requestParameters", {}),
            "config": {
                "operations_monitored": S3_DATA_ACCESS_OPERATIONS,
            },
        }
    )

    return context

Analyst notes

  1. Identify the principal and the specific S3 objects being accessed
  2. Verify if the external IP address belongs to a legitimate service or entity
  3. Check if the access pattern is expected for this user/role
  4. Review the contents of the S3 objects to determine sensitivity
  5. If unauthorized, determine how the principal obtained access credentials
  6. Revoke access immediately if determined to be malicious
  7. Consider implementing stricter bucket policies and VPC endpoint policies
Raw source S3 Access Via VPC Endpoint From External IP · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: aws_vpce_s3_external_ip.py
RuleID: "AWS.CloudTrail.VPCE.S3ExternalIP"
DisplayName: "S3 Access Via VPC Endpoint From External IP"
Enabled: true
LogTypes:
  - AWS.CloudTrail
Severity: Medium
Tags:
  - AWS
  - VPC
  - S3
  - Data Exfiltration
  - CloudTrail
  - Network Boundary Bridging
  - Exfiltration Over Alternative Protocol
Description: |
  Detects S3 data access through VPC endpoints from external/public IP addresses, which could indicate data exfiltration attempts.
  
  This rule can be customized with the following overrides:
  - S3_DATA_ACCESS_OPERATIONS: List of S3 operations to monitor
Runbook: |
  1. Identify the principal and the specific S3 objects being accessed
  2. Verify if the external IP address belongs to a legitimate service or entity
  3. Check if the access pattern is expected for this user/role
  4. Review the contents of the S3 objects to determine sensitivity
  5. If unauthorized, determine how the principal obtained access credentials
  6. Revoke access immediately if determined to be malicious
  7. Consider implementing stricter bucket policies and VPC endpoint policies
Reference: https://www.wiz.io/blog/aws-vpc-endpoint-cloudtrail
SummaryAttributes:
  - userIdentity.principalId
  - sourceIPAddress
  - eventSource
  - eventName
  - requestParameters
  - resources
DedupPeriodMinutes: 60
Tests:
  - Name: External IP Access
    ExpectedResult: true
    Log:
      {
        "eventType": "AwsVpceEvent",
        "eventCategory": "NetworkActivity",
        "eventSource": "s3.amazonaws.com",
        "eventName": "GetObject",
        "userIdentity": {
          "type": "IAMUser",
          "principalId": "AIDAEXAMPLE",
          "accountId": "012345678901"
        },
        "sourceIPAddress": "8.8.8.8",
        "requestParameters": {
          "bucketName": "sensitive-data-bucket",
          "key": "confidential/file.pdf"
        },
        "resources": [
          {
            "type": "AWS::S3::Object",
            "ARN": "arn:aws:s3:::sensitive-data-bucket/confidential/file.pdf"
          }
        ],
        "eventTime": "2023-01-01T12:00:00Z",
        "vpcEndpointId": "vpce-EXAMPLE08c1b6b9b7",
        "vpcEndpointAccountId": "012345678901",
        "recipientAccountId": "012345678901"
      }
  - Name: Internal IP Access
    ExpectedResult: false
    Log:
      {
        "eventType": "AwsVpceEvent",
        "eventCategory": "NetworkActivity",
        "eventSource": "s3.amazonaws.com",
        "eventName": "GetObject",
        "userIdentity": {
          "type": "IAMUser",
          "principalId": "AIDAEXAMPLE",
          "accountId": "012345678901"
        },
        "sourceIPAddress": "10.0.0.1",
        "requestParameters": {
          "bucketName": "sensitive-data-bucket",
          "key": "confidential/file.pdf"
        },
        "resources": [
          {
            "type": "AWS::S3::Object",
            "ARN": "arn:aws:s3:::sensitive-data-bucket/confidential/file.pdf"
          }
        ],
        "eventTime": "2023-01-01T12:00:00Z",
        "vpcEndpointId": "vpce-EXAMPLE08c1b6b9b7",
        "vpcEndpointAccountId": "012345678901",
        "recipientAccountId": "012345678901"
      }
  - Name: Not S3 Service
    ExpectedResult: false
    Log:
      {
        "eventType": "AwsVpceEvent",
        "eventCategory": "NetworkActivity",
        "eventSource": "ec2.amazonaws.com",
        "eventName": "DescribeInstances",
        "userIdentity": {
          "type": "IAMUser",
          "principalId": "AIDAEXAMPLE",
          "accountId": "012345678901"
        },
        "sourceIPAddress": "8.8.8.8"
      } 

# ------ paired body: aws_vpce_s3_external_ip.py ------

import ipaddress

from panther_aws_helpers import aws_rule_context

# Define S3 data access operations
S3_DATA_ACCESS_OPERATIONS = [
    "GetObject",
    "GetObjectVersion",
    "GetObjectAcl",
    "GetObjectVersionAcl",
    "PutObject",
    "PutObjectAcl",
    "PutObjectVersionAcl",
    "CopyObject",
    "DeleteObject",
    "DeleteObjects",
    "DeleteObjectVersion",
]


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

    # Focus on data access operations
    if event.get("eventName") not in S3_DATA_ACCESS_OPERATIONS:
        return False

    # Check for external IP
    source_ip = event.get("sourceIPAddress", "")
    if not source_ip:
        return False

    try:
        ip_obj = ipaddress.ip_address(source_ip)
        if ip_obj.is_global:
            return True
    except ValueError:
        # If source_ip is not a valid IP address
        pass

    return False


def title(event):
    # Use UDM actor_user which leverages the get_actor_user helper function
    actor_user = event.udm("actor_user")
    source_ip = event.get("sourceIPAddress", "unknown")
    bucket_name = event.deep_get("requestParameters", "bucketName", default="unknown")

    return (
        f"S3 Access via VPC Endpoint from External IP: [{actor_user}] from "
        f"[{source_ip}] to bucket [{bucket_name}]"
    )


def alert_context(event):
    account_id = event.deep_get("userIdentity", "accountId", default="")

    context = aws_rule_context(event)
    context.update(
        {
            "account_id": account_id,
            "principal_id": event.deep_get("userIdentity", "principalId", default="unknown"),
            "actor_user": event.udm("actor_user"),
            "source_ip": event.get("sourceIPAddress", "unknown"),
            "event_source": event.get("eventSource", "unknown"),
            "api_call": event.get("eventName", "unknown"),
            "resources": event.get("resources", []),
            "request_parameters": event.get("requestParameters", {}),
            "config": {
                "operations_monitored": S3_DATA_ACCESS_OPERATIONS,
            },
        }
    )

    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.