Do not use for Azure Blob Storage or GCP Cloud Storage misconfigurations, for S3 data classification (see implementing-cloud-dlp-policy), or for S3 access pattern analysis unrelated to security.
Use multiple detection methods to identify S3 buckets with public access. Rely on AWS Config rules, S3 Access Analyzer, and Macie rather than manual inspection.
# Enable S3 Access Analyzer for external access detection
aws accessanalyzer create-analyzer \
--analyzer-name s3-analyzer \
--type ACCOUNT
# List all S3 buckets with public access indicators
aws s3api list-buckets --query 'Buckets[*].Name' --output text | while read bucket; do
public_status=$(aws s3api get-public-access-block --bucket "$bucket" 2>/dev/null)
if [ $? -ne 0 ]; then
echo "NO PUBLIC ACCESS BLOCK: $bucket"
fi
done
# Check bucket policies for public access grants
aws s3api list-buckets --query 'Buckets[*].Name' --output text | while read bucket; do
policy=$(aws s3api get-bucket-policy --bucket "$bucket" 2>/dev/null)
if echo "$policy" | grep -q '"Principal":"*"' 2>/dev/null; then
echo "PUBLIC POLICY DETECTED: $bucket"
fi
done
# Use AWS Config to find non-compliant buckets
aws configservice get-compliance-details-by-config-rule \
--config-rule-name s3-bucket-public-read-prohibited \
--compliance-types NON_COMPLIANT \
--query 'EvaluationResults[*].EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId'
Apply the four Block Public Access settings at the AWS account level as a safety net. This prevents any bucket in the account from being made public, regardless of individual bucket policies or ACLs.
# Enable account-level Block Public Access (all four settings)
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration '{
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}'
# Verify account-level settings
aws s3control get-public-access-block --account-id 123456789012
# Enable at bucket level for defense in depth
aws s3api put-public-access-block \
--bucket production-data-bucket \
--public-access-block-configuration '{
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}'
Review all bucket policies for overly permissive Principal statements and remove legacy ACLs. Enforce bucket ownership controls to disable ACLs entirely.
# Remove a public bucket policy
aws s3api delete-bucket-policy --bucket exposed-bucket
# Replace with a restrictive policy
aws s3api put-bucket-policy --bucket exposed-bucket --policy '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnencryptedTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::exposed-bucket",
"arn:aws:s3:::exposed-bucket/*"
],
"Condition": {
"Bool": {"aws:SecureTransport": "false"}
}
},
{
"Sid": "AllowOnlyVPCEndpoint",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::exposed-bucket",
"arn:aws:s3:::exposed-bucket/*"
],
"Condition": {
"StringNotEquals": {"aws:SourceVpce": "vpce-0abc123def456"}
}
}
]
}'
# Enforce bucket owner for all objects (disable ACLs)
aws s3api put-bucket-ownership-controls --bucket exposed-bucket \
--ownership-controls '{"Rules": [{"ObjectOwnership": "BucketOwnerEnforced"}]}'
Enable default server-side encryption with AWS KMS or AES-256 for all buckets. Add a bucket policy denying unencrypted object uploads.
# Enable default KMS encryption
aws s3api put-bucket-encryption --bucket production-data-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/key-id"
},
"BucketKeyEnabled": true
}]
}'
# Deny unencrypted uploads via bucket policy
aws s3api put-bucket-policy --bucket production-data-bucket --policy '{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyUnencryptedUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::production-data-bucket/*",
"Condition": {
"StringNotEquals": {"s3:x-amz-server-side-encryption": ["aws:kms", "AES256"]}
}
}]
}'
Configure S3 server access logging and CloudTrail data events to track all object-level operations. Set up EventBridge rules to alert on suspicious access patterns.
# Enable server access logging
aws s3api put-bucket-logging --bucket production-data-bucket \
--bucket-logging-status '{
"LoggingEnabled": {
"TargetBucket": "s3-access-logs-bucket",
"TargetPrefix": "production-data-bucket/"
}
}'
# Enable CloudTrail S3 data events
aws cloudtrail put-event-selectors --trail-name management-trail \
--event-selectors '[{
"ReadWriteType": "All",
"DataResources": [{
"Type": "AWS::S3::Object",
"Values": ["arn:aws:s3:::production-data-bucket/"]
}]
}]'
Use Service Control Policies to prevent disabling Block Public Access across the organization. Deploy AWS Config rules with auto-remediation.
# SCP preventing Block Public Access removal
aws organizations create-policy \
--name PreventS3PublicAccess \
--type SERVICE_CONTROL_POLICY \
--content '{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyRemovePublicAccessBlock",
"Effect": "Deny",
"Action": [
"s3:PutBucketPublicAccessBlock",
"s3:PutAccountPublicAccessBlock"
],
"Resource": "*",
"Condition": {
"StringNotLike": {"aws:PrincipalArn": "arn:aws:iam::*:role/SecurityAdmin"}
}
}]
}'
| Term | Definition |
|---|---|
| S3 Block Public Access | Four account-level and bucket-level settings that override any policy or ACL granting public access to S3 resources |
| Bucket Policy | JSON-based resource policy attached to an S3 bucket defining who can access what objects under which conditions |
| ACL (Access Control List) | Legacy S3 access mechanism that grants permissions at the bucket or object level; should be disabled via BucketOwnerEnforced |
| BucketOwnerEnforced | Ownership control setting that disables all ACLs on a bucket, making the bucket owner the sole authority for access control |
| Server-Side Encryption | Automatic encryption of objects at rest using AES-256 (SSE-S3), AWS KMS (SSE-KMS), or customer-provided keys (SSE-C) |
| VPC Endpoint | Private connection between a VPC and S3 that restricts bucket access to traffic originating from within the VPC |
| S3 Access Analyzer | IAM Access Analyzer capability that identifies S3 buckets shared with external entities outside the account or organization |
Context: A security researcher reports that an S3 bucket containing 273,000 bank transfer PDFs is publicly readable. The bucket was created by a developer who needed to share files with an external partner and set the ACL to public-read.
Approach:
Pitfalls: Enabling Block Public Access without notifying the team that set up the public access breaks their workflow. Not running access log analysis before remediation loses evidence of who accessed the exposed data.
S3 Bucket Security Remediation Report
=======================================
Account: 123456789012
Assessment Date: 2025-02-23
Buckets Scanned: 156
ACCOUNT-LEVEL CONTROLS:
Block Public Access: ENABLED (all four settings)
SCP Preventing Removal: DEPLOYED
CRITICAL FINDINGS (Remediated):
[S3-001] production-uploads - Public READ via ACL
Status: REMEDIATED - BucketOwnerEnforced applied
Objects Exposed: 273,412
Duration of Exposure: 47 days
Unique External IPs Accessed: 1,247
[S3-002] analytics-export - Public bucket policy (Principal: *)
Status: REMEDIATED - Policy replaced with VPC endpoint restriction
Sensitive Data (Macie): 12,400 objects with PII detected
HIGH FINDINGS:
[S3-003] 14 buckets missing default encryption
Status: REMEDIATED - KMS encryption enabled
[S3-004] 8 buckets without server access logging
Status: REMEDIATED - Logging enabled to centralized log bucket
SUMMARY:
Buckets Remediated: 24/156
Encryption Coverage: 100%
Access Logging Coverage: 100%
Block Public Access: 156/156 buckets