S3 buckets should not be publicly readable

Description

S3 buckets should not be publicly readable. A bucket with a public ACL or bucket policy is exposed to the entire internet if all block public access settings are disabled at the resource and account level. This poses a critical security vulnerability, as any AWS user or anonymous user can access the data in the bucket.

Remediation Steps

AWS Console

  • Navigate to S3.

  • Select the S3 bucket.

  • Select Permissions > Access Control List.

  • In Public access, select Everyone and uncheck:

    • List objects

    • Write objects

    • Read bucket permissions

    • Write bucket permissions

  • Click Save.

  • Select Bucket Policy.

  • Navigate to S3.

  • In the left navigation, select Block public access (account settings).

  • Click Edit.

  • Check the Block all public access checkbox.

  • Click Save Changes.

  • Enter confirm and click confirm.

AWS CLI

To make an S3 bucket not publicly accessible:

aws s3api put-bucket-acl \
    --bucket fugue-bucket-example --acl private
aws s3api put-public-access-block \
    --bucket fugue-bucket-example \
    --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

To enable block public access settings at the account level:

aws s3control put-public-access-block \
    --account-id 123456789012 \
    --public-access-block-configuration '{"BlockPublicAcls": true, "IgnorePublicAcls": true, "BlockPublicPolicy": true, "RestrictPublicBuckets": true}'

Terraform

  • Ensure that the aws_s3_bucket acl field does NOT contain EITHER of the following:

    • “public-read”

    • “public-read-write”

  • Ensure that the grant block does NOT contain BOTH an invalid uri and permissions field:

  • If a bucket policy is defined in the bucket’s policy field, ensure the JSON document does NOT contain BOTH an invalid principal, an invalid action, and an invalid effect:

    • Invalid principals:

      • "Principal": { "AWS": "*" }

      • "Principal": "*"

    • Invalid actions:

      • "*"

      • "s3:*"

      • "s3:List*"

      • "s3:Get*"

      • "s3:ListBucket*"

      • "s3:GetObject*"

      • "s3:ListBucket"

      • "s3:ListBucketVersions"

      • "s3:ListBucketMultipartUploads"

      • "s3:GetObject"

      • "s3:GetObjectVersion"

      • "s3:GetObjectTorrent"

    • Invalid effect:

      • "Effect": "Allow"

  • If a bucket policy as defined as an aws_s3_bucket_policy, ensure the JSON document in the policy field does NOT contain BOTH an invalid principal, an invalid action, and an invalid effect, as listed above

  • While a aws_s3_bucket_public_access_block is not required for the bucket, it’s highly recommended. Ensure the following aws_s3_bucket_public_access_block fields are all set to true:

    • block_public_acls

    • block_public_policy

    • ignore_public_acls

    • restrict_public_buckets

  • You can enable block public access settings for all buckets in your account by using a aws_s3_account_public_access_block. Ensure the following fields are all set to true:

    • block_public_acls

    • block_public_policy

    • ignore_public_acls

    • restrict_public_buckets

Example Configuration

# Compliant ACL
resource "aws_s3_bucket" "b" {
  acl    = "private"
  # other required fields here
}
# Compliant grant
resource "aws_s3_bucket" "bucket" {
  bucket = "mybucket"

  grant {
    id          = data.aws_canonical_user_id.current_user.id
    type        = "CanonicalUser"
    permissions = ["FULL_CONTROL"]
  }

  # other required fields here
}
# Compliant bucket policy
resource "aws_s3_bucket" "b" {
  bucket = "my-tf-test-bucket"
  # other required fields here
}

resource "aws_s3_bucket_policy" "b" {
  bucket = aws_s3_bucket.b.id

  policy = jsonencode({
    Version = "2012-10-17"
    Id      = "MYBUCKETPOLICY"
    Statement = [
      {
        Sid       = "IPAllow"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:*"
        Resource = [
          aws_s3_bucket.b.arn,
          "${aws_s3_bucket.b.arn}/*",
        ]
        Condition = {
          NotIpAddress = {
            "aws:SourceIp" = "8.8.8.8/32"
          }
        }
      },
    ]
  })

  # other required fields here
}
# Compliant public access block - single bucket
resource "aws_s3_bucket" "b" {
  # other required fields here
}

resource "aws_s3_bucket_public_access_block" "private" {
  bucket                  = "${aws_s3_bucket.b.id}"
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
# Compliance public access block - ALL buckets
resource "aws_s3_account_public_access_block" "main" {
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}