Why the path exists
S3 bucket policies are resource-based policies. They can name a principal in another AWS account and grant that principal actions on the bucket and its objects. This is a legitimate cross-account sharing mechanism—and therefore a useful persistence or exfiltration primitive when an actor has s3:PutBucketPolicy.
An exact external account is not the same as anonymous public access. GuardDuty’s public-access finding may not cover a policy shared only with one outside account. IAM Access Analyzer is the better native control for identifying both public and cross-account access.
Preconditions
- A disposable source bucket with non-sensitive test objects.
- A controlled second AWS account and a named test role in that account.
- Permission to read and replace the bucket policy.
- A rollback copy of the original policy.
S3 Block Public Access can reject policies that S3 evaluates as public. A narrowly specified external principal may still be accepted, but account-level controls, RCPs, SCPs, VPC endpoint policies, and KMS key policies can independently block effective access.
Reproduce in a controlled pair of accounts
Save the current policy before changing it. If the command returns NoSuchBucketPolicy, record that state instead of creating a fake “baseline” file.
aws s3api get-bucket-policy \
--bucket humpty-policy-lab \
--query Policy \
--output text > baseline-policy.json
Create a policy that grants a specific role in the controlled external account permission to list the bucket and read test objects. Replace both account ID and role name.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AuthorizedCrossAccountReadTest",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:role/s3-policy-lab-reader"
},
"Action": ["s3:ListBucket", "s3:GetBucketLocation"],
"Resource": "arn:aws:s3:::humpty-policy-lab"
},
{
"Sid": "AuthorizedCrossAccountObjectReadTest",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:role/s3-policy-lab-reader"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::humpty-policy-lab/lab/*"
}
]
}
aws s3api put-bucket-policy \
--bucket humpty-policy-lab \
--policy file://lab-policy.json
From the external test role, list only the approved prefix and retrieve one non-sensitive fixture. A successful read proves effective access; an AccessDenied response means another policy layer still prevents it.
Cleanup without destroying the baseline
Restore the captured policy with put-bucket-policy. Only use delete-bucket-policy when the bucket genuinely had no baseline policy before the test.
aws s3api put-bucket-policy \
--bucket humpty-policy-lab \
--policy file://baseline-policy.json
aws s3api get-bucket-policy \
--bucket humpty-policy-lab \
--query Policy \
--output text
Detection
PutBucketPolicy is the change event, but alert quality comes from parsing the resulting policy. Determine whether it introduces a principal outside the expected account or organization and which actions and resources that principal receives.
| Signal | Fields or attributes | Decision value |
|---|---|---|
CloudTrail / PutBucketPolicy |
userIdentity.arn, requestParameters.bucketName, sourceIPAddress, userAgent |
Attributes the policy replacement. Store the new policy or retrieve it immediately because the CloudTrail record may not be sufficient for a readable semantic diff. |
| Policy analysis | Principal, Action, Resource, Condition |
Shows who gained access, what they can do, which objects are in scope, and whether constraints materially reduce reachability. |
| IAM Access Analyzer | External principal, access level, shared through, finding status | Confirms public or cross-account access outside the analyzer’s zone of trust. |
High-value logic
- Alert when a policy introduces an AWS account or role that is absent from the approved partner-account inventory.
- Increase severity when the grant includes
s3:GetObject,s3:PutObject,s3:DeleteObject, or permission-management actions. - Compare effective principals before and after the change. String matching on
"Principal":"*"misses targeted cross-account grants. - Correlate the policy change with subsequent S3 data events from the external account when data-event logging is enabled for the bucket.
Hardening
- Restrict
s3:PutBucketPolicyto deployment roles and reviewed break-glass paths. - Create an IAM Access Analyzer in every Region containing buckets; the S3 experience depends on regional account-level analyzers.
- Maintain an inventory of approved external AWS accounts and compare policy principals against it.
- Use organization conditions such as
aws:PrincipalOrgIDwhere the sharing requirement is organization-wide. - Enable S3 data events selectively for sensitive buckets so successful external reads can be correlated with the policy change.