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

  1. A disposable source bucket with non-sensitive test objects.
  2. A controlled second AWS account and a named test role in that account.
  3. Permission to read and replace the bucket policy.
  4. 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:PutBucketPolicy to 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:PrincipalOrgID where 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.

References

  1. AWS S3 documentation — Reviewing bucket access using IAM Access Analyzer
  2. Stratus Red Team — Backdoor an S3 bucket via its bucket policy
  3. AWS S3 documentation — Bucket policies