Back to Checks
aws

S3 bucket policy does not allow public write access

s3_bucket_policy_public_write_access
Service: s3Severity: critical
OfficialBuilt-inVerified
Categories:internet-exposed
Resource Type:AwsS3Bucket

Amazon S3 bucket policies are evaluated for public write permissions (e.g., s3:PutObject, s3:Delete*, or s3:*). Account or bucket Public Access Block that restricts public buckets is considered when determining exposure.

Risk

Public write access lets anyone upload, overwrite, or delete objects, undermining integrity and availability. Attackers can plant malware, stage phishing content, poison data, or wipe buckets, causing outages and potential legal and cost impacts from storage abuse and content hosting.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks s3_bucket_policy_public_write_access

Recommendation

Restrict writes to trusted principals using least privilege; avoid Principal: "*". Enable Public Access Block at account and bucket levels for defense in depth. Prefer IAM roles over broad bucket policies, require private access paths, and enable versioning to recover from unwanted changes.

Remediation

CLI

aws s3api put-public-access-block --bucket <BUCKET_NAME> --public-access-block-configuration RestrictPublicBuckets=true

Terraform

# Enable bucket-level Public Access Block so public bucket policies (including write) are blocked
resource "aws_s3_bucket_public_access_block" "<example_resource_name>" {
  bucket                   = "<example_resource_id>"
  restrict_public_buckets  = true  # Critical: prevents public access granted via bucket policies
}

NativeIaC

# CloudFormation: Enable bucket-level Public Access Block to prevent public policies from granting write
Resources:
  <example_resource_name>:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        RestrictPublicBuckets: true  # Critical: blocks public access granted by any public bucket policy

Other

1Open the AWS S3 console and select the target bucket
2Go to the Permissions tab
3Under Block public access (bucket settings), click Edit
4Enable "Block public and cross-account access to buckets and objects through any public bucket or access point policies"
5Click Save changes

Check Code

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.lib.policy import is_policy_public
from prowler.providers.aws.services.s3.s3_client import s3_client
from prowler.providers.aws.services.s3.s3control_client import s3control_client


class s3_bucket_policy_public_write_access(Check):
    def execute(self):
        findings = []
        for bucket in s3_client.buckets.values():
            if bucket.policy is None:
                continue
            report = Check_Report_AWS(metadata=self.metadata(), resource=bucket)
            # Check if bucket policy allow public write access
            if not bucket.policy:
                report.status = "PASS"
                report.status_extended = (
                    f"S3 Bucket {bucket.name} does not have a bucket policy."
                )
            elif (
                s3control_client.account_public_access_block
                and s3control_client.account_public_access_block.restrict_public_buckets
            ):
                report.status = "PASS"
                report.status_extended = (
                    "All S3 public access blocked at account level."
                )
            elif (
                bucket.public_access_block
                and bucket.public_access_block.restrict_public_buckets
            ):
                report.status = "PASS"
                report.status_extended = (
                    f"S3 public access blocked at bucket level for {bucket.name}."
                )
            else:
                report.status = "PASS"
                report.status_extended = f"S3 Bucket {bucket.name} does not allow public write access in the bucket policy."
                if is_policy_public(
                    bucket.policy,
                    s3_client.audited_account,
                    not_allowed_actions=[
                        "s3:PutObject",
                        "s3:DeleteObject",
                        "s3:*",
                        "s3:Put*",
                        "s3:Delete*",
                    ],
                ):
                    report.status = "FAIL"
                    report.status_extended = f"S3 Bucket {bucket.name} allows public write access in the bucket policy."

            findings.append(report)
        return findings