aws-s3-put-bucket-acl-can-expose

s3:PutBucketAcl on a bucket with ObjectOwnership != BucketOwnerEnforced and Block Public Access IgnorePublicAcls=false enables ACL-based public exposure.

derived aws emits ExposedToInternet

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'principal': None} HasPermission {'bucket': None}

where

node_type(?bucket) == ObjectStorage ?bucket.provider_type == 'AWS::S3::Bucket' ?principal has EFFECTIVE s3:PutBucketAcl on ?bucket ARN ?bucket.properties.ObjectOwnership != 'BucketOwnerEnforced' (ACLs are honored) ?bucket.properties.PublicAccessBlockConfiguration.IgnorePublicAcls == false (bucket-level BPA) account-level PublicAccessBlockConfiguration.IgnorePublicAcls == false (or not collected)

emit

source typeObjectStorage
target typeAnonymousIdentity
source?bucket
target<AnonymousIdentity>
permissionss3:PutBucketAcl
conditionsiam_permission scp_or_org_policy
state logicPOTENTIAL when s3:PutBucketAcl is confirmed EFFECTIVE, ObjectOwnership is not BucketOwnerEnforced, and account-level + bucket-level IgnorePublicAcls are both false. The bucket is NOT currently exposed, but the attacker can unconditionally grant AllUsers READ via PutBucketAcl, bypassing bucket policy restrictions. Upgrade to ACTIVE via rule aws-s3-acl-public-exposure when the ACL is actually written with AllUsers READ. BLOCKED when IgnorePublicAcls=true (at account or bucket level) or ObjectOwnership=BucketOwnerEnforced (ACLs silently ignored). BLOCKED when an SCP denies s3:PutBucketAcl.

Narrative

{principal.name} has s3:PutBucketAcl on {bucket.name}; the principal can grant AllUsers READ via the legacy ACL system (ObjectOwnership != BucketOwnerEnforced, IgnorePublicAcls=false), bypassing bucket policy restrictions to expose the bucket to the internet.

Raw rule rules/derived/aws/s3.yaml

id: aws-s3-put-bucket-acl-can-expose
emits: ExposedToInternet
description: s3:PutBucketAcl on a bucket with ObjectOwnership != BucketOwnerEnforced and Block Public
  Access IgnorePublicAcls=false enables ACL-based public exposure.
match:
- - principal: null
  - HasPermission
  - bucket: null
where:
- node_type(?bucket) == ObjectStorage
- ?bucket.provider_type == 'AWS::S3::Bucket'
- ?principal has EFFECTIVE s3:PutBucketAcl on ?bucket ARN
- ?bucket.properties.ObjectOwnership != 'BucketOwnerEnforced'  (ACLs are honored)
- ?bucket.properties.PublicAccessBlockConfiguration.IgnorePublicAcls == false  (bucket-level BPA)
- account-level PublicAccessBlockConfiguration.IgnorePublicAcls == false  (or not collected)
emit:
  source_type: ObjectStorage
  target_type: AnonymousIdentity
  source: ?bucket
  target: <AnonymousIdentity>
  permissions:
  - s3:PutBucketAcl
  conditions:
  - iam_permission
  - scp_or_org_policy
  state_logic: POTENTIAL when s3:PutBucketAcl is confirmed EFFECTIVE, ObjectOwnership is not BucketOwnerEnforced,
    and account-level + bucket-level IgnorePublicAcls are both false. The bucket is NOT currently exposed,
    but the attacker can unconditionally grant AllUsers READ via PutBucketAcl, bypassing bucket policy
    restrictions. Upgrade to ACTIVE via rule aws-s3-acl-public-exposure when the ACL is actually written
    with AllUsers READ. BLOCKED when IgnorePublicAcls=true (at account or bucket level) or ObjectOwnership=BucketOwnerEnforced
    (ACLs silently ignored). BLOCKED when an SCP denies s3:PutBucketAcl.
  confidence: min(contributing_confidences) * 0.85
  derived_from:
  - ?principal HasPermission ?bucket (s3:PutBucketAcl effective permission)
  false_positive_note: "This edge models the CAPABILITY to grant public access via ACL (state POTENTIAL),\
    \ not the current state. The bucket is not yet public; the attacker must execute the PutBucketAcl\
    \ call with AllUsers READ to actualize this path. ObjectOwnership is the gating factor: if ObjectOwnership=BucketOwnerEnforced,\
    \ all ACL grants are ignored (silently rejected). Check this field carefully \u2014 it was introduced\
    \ in 2020 and many older buckets do not set it (default is ObjectOwnershipRecommended, which honors\
    \ ACLs). Block Public Access IgnorePublicAcls is the secondary gate; if set at account or bucket level,\
    \ even a public ACL is overridden. Distinguish this from rule aws-s3-acl-public-exposure, which detects\
    \ the CURRENT state (ACL already contains AllUsers READ). This rule surfaces the capability to CREATE\
    \ that state."
  narrative: '{principal.name} has s3:PutBucketAcl on {bucket.name}; the principal can grant AllUsers
    READ via the legacy ACL system (ObjectOwnership != BucketOwnerEnforced, IgnorePublicAcls=false), bypassing
    bucket policy restrictions to expose the bucket to the internet.'
move · open · esc close