S3 Bucket Cross-Account Access
Overview
This check identifies S3 buckets that have policies granting access to AWS accounts other than the bucket owner. Cross-account access means that principals (users, roles, or accounts) outside your AWS account can access your bucket.
While cross-account access is sometimes intentional and necessary, unreviewed or overly permissive policies can expose sensitive data to unauthorized parties.
Risk
If cross-account access is not properly configured and monitored:
- Data exposure: External accounts may read sensitive objects
- Data tampering: Unauthorized parties could modify or delete your data
- Data exfiltration: Attackers could copy your data to accounts you do not control
- Compliance violations: Uncontrolled access may violate GDPR, HIPAA, PCI, and other regulatory requirements
Remediation Steps
Prerequisites
You need permission to view and modify S3 bucket policies. Typically, this requires the s3:GetBucketPolicy, s3:PutBucketPolicy, and s3:DeleteBucketPolicy permissions.
AWS Console Method
- Open the S3 Console
- Click on the bucket name flagged by Prowler
- Select the Permissions tab
- Scroll down to Bucket policy and click Edit
- Review each
Principalin the policy:- Look for account IDs that are not your own (e.g.,
arn:aws:iam::123456789012:root) - Look for wildcard principals (
"*"or"AWS": "*")
- Look for account IDs that are not your own (e.g.,
- For each cross-account principal, decide:
- Remove it if the access is no longer needed
- Restrict it by adding conditions (e.g., source VPC, IP address)
- Keep it if it is intentional and documented
- Click Save changes
If the bucket does not need any bucket policy, you can delete the entire policy by clicking Delete in the Bucket policy section.
AWS CLI (optional)
View the current bucket policy:
aws s3api get-bucket-policy \
--bucket <your-bucket-name> \
--region us-east-1 \
--query Policy \
--output text | jq .
Remove the bucket policy entirely (if no policy is needed):
aws s3api delete-bucket-policy \
--bucket <your-bucket-name> \
--region us-east-1
Replace with a same-account-only policy:
First, create a policy file (policy.json):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RestrictToSameAccount",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<your-account-id>:root"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<your-bucket-name>",
"arn:aws:s3:::<your-bucket-name>/*"
]
}
]
}
Then apply it:
aws s3api put-bucket-policy \
--bucket <your-bucket-name> \
--policy file://policy.json \
--region us-east-1
Replace <your-bucket-name> and <your-account-id> with your actual values.
CloudFormation (optional)
This template creates an S3 bucket with a policy that restricts access to principals within the same AWS account only.
AWSTemplateFormatVersion: '2010-09-09'
Description: S3 bucket with policy restricted to same account access only
Parameters:
BucketName:
Type: String
Description: Name of the S3 bucket
AccountId:
Type: String
Description: AWS Account ID that should have access
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: RestrictToSameAccount
Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AccountId}:root'
Action:
- 's3:GetObject'
- 's3:PutObject'
- 's3:ListBucket'
Resource:
- !GetAtt S3Bucket.Arn
- !Sub '${S3Bucket.Arn}/*'
Outputs:
BucketName:
Description: Name of the S3 bucket
Value: !Ref S3Bucket
BucketArn:
Description: ARN of the S3 bucket
Value: !GetAtt S3Bucket.Arn
Deploy the stack:
aws cloudformation deploy \
--template-file template.yaml \
--stack-name s3-same-account-bucket \
--parameter-overrides \
BucketName=<your-bucket-name> \
AccountId=<your-account-id> \
--region us-east-1
Terraform (optional)
This configuration creates an S3 bucket with a policy that restricts access to the same AWS account.
variable "bucket_name" {
description = "Name of the S3 bucket"
type = string
}
variable "account_id" {
description = "AWS Account ID that should have access"
type = string
}
resource "aws_s3_bucket" "main" {
bucket = var.bucket_name
}
resource "aws_s3_bucket_policy" "same_account_only" {
bucket = aws_s3_bucket.main.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "RestrictToSameAccount"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${var.account_id}:root"
}
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.main.arn,
"${aws_s3_bucket.main.arn}/*"
]
}
]
})
}
output "bucket_name" {
description = "Name of the S3 bucket"
value = aws_s3_bucket.main.id
}
output "bucket_arn" {
description = "ARN of the S3 bucket"
value = aws_s3_bucket.main.arn
}
Apply the configuration:
terraform init
terraform apply -var="bucket_name=<your-bucket-name>" -var="account_id=<your-account-id>"
Verification
After making changes, verify that cross-account access has been removed:
- Go to the S3 Console
- Select your bucket and click the Permissions tab
- Review the Bucket policy section
- Confirm that all
Principalvalues reference only your AWS account ID
CLI verification
aws s3api get-bucket-policy \
--bucket <your-bucket-name> \
--region us-east-1 \
--query Policy \
--output text | jq .
Check that the output shows only your account ID in the Principal field, or that no policy exists (if you deleted it).
You can also re-run the Prowler check:
prowler aws --checks s3_bucket_cross_account_access
Additional Resources
- AWS S3 Bucket Policies Documentation
- IAM Policy Principal Element
- Cross-Account Access Best Practices
- S3 Block Public Access
Notes
-
Intentional cross-account access: Some use cases legitimately require cross-account access (e.g., sharing data with partners, centralized logging). If cross-account access is intentional, document the business justification and ensure the policy follows least-privilege principles.
-
AWS Organizations: If your accounts are part of an AWS Organization, consider using Service Control Policies (SCPs) or AWS Resource Access Manager (RAM) for more controlled cross-account sharing.
-
Conditions for defense-in-depth: When cross-account access is necessary, add conditions to limit access by source IP, VPC, or require MFA. For example:
"Condition": {
"StringEquals": {
"aws:SourceVpc": "vpc-12345678"
}
} -
S3 Access Points: For complex access patterns, consider using S3 Access Points, which provide more granular control over cross-account access.
-
Audit regularly: Even if cross-account access is intentional today, requirements change. Review bucket policies periodically to ensure they still reflect current business needs.