Detection rules › Panther

AWS CloudTrail S3 Bucket Public

Severity
high
Compliance
CIS 2.3
Tags
AWS, Data Protection, Collection:Data From Cloud Storage Object
Reference
https://docs.aws.amazon.com/AmazonS3/latest/user-guide/block-public-access.html
Source
github.com/panther-labs/panther-analysis

This policy validates that CloudTrail S3 buckets are not publicly accessible.

MITRE ATT&CK coverage

TacticTechniques
CollectionT1530 Data from Cloud Storage

Rule body yaml

AnalysisType: policy
Filename: aws_cloudtrail_s3_bucket_public.py
PolicyID: "AWS.CloudTrail.S3Bucket.Public"
DisplayName: "AWS CloudTrail S3 Bucket Public"
Enabled: true
ResourceTypes:
  - AWS.CloudTrail
Tags:
  - AWS
  - Data Protection
  - Collection:Data From Cloud Storage Object
Reports:
  CIS:
    - 2.3
  MITRE ATT&CK:
    - TA0009:T1530
Severity: High
Description: >
  This policy validates that CloudTrail S3 buckets are not publicly accessible.
Runbook: >
  https://docs.runpanther.io/alert-runbooks/built-in-policies/aws-cloudtrail-logs-s3-bucket-not-publicly-accessible
Reference: >
  https://docs.aws.amazon.com/AmazonS3/latest/user-guide/block-public-access.html

# Unit testing not supported for policies that might network calls

Detection logic

Rule logic imperative Python

from panther_aws_helpers import BadLookup, aws_regions, resource_lookup
from panther_base_helpers import deep_get
BAD_PERMISSIONS = {
    "http://acs.amazonaws.com/groups/global/AuthenticatedUsers",
    "http://acs.amazonaws.com/groups/global/AllUsers",
}
EXCLUDED_BUCKET_NAMES = {
    f"codepipeline-cloudtrail-placeholder-bucket-{region}" for region in aws_regions()
}
def policy(resource):
    bucket_arn = "arn:aws:s3:::" + resource["S3BucketName"]
    try:
        bucket = resource_lookup(bucket_arn)
    except BadLookup:
        return True
    for grant in bucket["Grants"] or []:
        if deep_get(grant, "Grantee", "URI") in BAD_PERMISSIONS and not any(
            bucket_name in bucket_arn for bucket_name in EXCLUDED_BUCKET_NAMES
        ):
            return False
    return True

The parser cannot express this rule's logic as a field filter; the imperative Python above is the detection.