Skip to main content
cd ../config-traps
risk/register/s3-wildcard-principal-without-conditions-the-leak.html
AWS S3 Access Control

S3 Wildcard Principal Without Conditions: The Leak

Overview

S3 Wildcard Principal Without Conditions: The Leak sits in the AWS S3 Access Control risk register because a small configuration shortcut can widen access, weaken control boundaries, or hide failure conditions. The risk usually starts with aWS's own documentation examples for CloudFront legacy OAI access, ALB/NLB access log delivery, and SES inbound receipt rules all ship with Principal: "*" paired In production, the concern is s3 Block Public Access normally intercepts PutBucketPolicy calls that grant public

The Trap

Wildcard Principal (“Principal”: “*”) left in an S3 bucket policy with no accompanying Condition block. This is distinct from a plain public bucket toggle; the policy looks scoped because it references a specific service, action, or resource, but nothing actually constrains who that wildcard resolves to.

The Default State

AWS’s own documentation examples for CloudFront legacy OAI access, ALB/NLB access log delivery, and SES inbound receipt rules all ship with Principal: “*” paired with a Condition using aws:SourceArn or aws:SourceAccount. Engineers copy the Principal and Action blocks into Terraform’s aws_s3_bucket_policy resource or a CloudFormation template, then hit an AccessDenied error during testing. The fastest fix under deadline pressure is deleting the Condition block rather than diagnosing why the ARN or account ID doesn’t match — the policy applies cleanly once the constraint is gone, and the ticket closes as resolved.

The Blast Radius

S3 Block Public Access normally intercepts PutBucketPolicy calls that grant public access via Principal “*” unless the statement includes a recognised narrowing condition key — aws:SourceArn, aws:SourceIp, aws:SourceVpce, aws:PrincipalOrgID, or similar. But BlockPublicPolicy is bucket-overridable, and buckets provisioned before the 2023 account-level default, or via IaC modules that explicitly set BlockPublicPolicy=false for compatibility with older CloudFront setups, accept the policy without complaint. Once attached, s3:GetObject or s3:PutObject becomes callable by any unauthenticated request. Internet-wide scanners fingerprint newly created buckets from CloudTrail-adjacent DNS patterns and S3 naming conventions within hours; exposed write access invites ransomware-style overwrite of objects, exposed read access invites bulk exfiltration via aws s3 sync run anonymously with –no-sign-request.

The Lead Mechanic Fix

Restore the Condition and scope it to the actual caller, not the service name: “Condition”: {“StringEquals”: {“aws:PrincipalOrgID”: “o-xxxxxxxxxx”}, “ArnLike”: {“aws:SourceArn”: “arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE”}}. Enforce account-wide guardrails with aws s3api put-public-access-block --bucket BUCKET --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true and remove any bucket-level override that disables it. Before deployment, validate every candidate policy with aws accessanalyzer validate-policy --policy-document file://policy.json --policy-type RESOURCE_POLICY, which flags PUBLIC_S3_BUCKET findings distinct from cosmetic syntax errors. Run an IAM Access Analyzer external-access scan continuously against the account, and treat any finding with an empty condition set as a Sev-1, not a backlog item.