Auditing AWS S3 Buckets

Table of Contents

• 1. Background: How S3 Controls Public Access

• 1.1. Public Access Block

• 1.2. Bucket Policy Status

• 1.3. ACLs

• 1.4. How the Three Layers Interact

• 2. What the Script Does

• 3. Prerequisites

• 4. Deriving Overall Public Status

• 5. Reading the Output

• 6. Common Exceptions and False Positives

• 7. How to Write Up the Finding

This is the latest in my series of posts on auditing AWS, a cloud platform that has existed for around two decades but can still be a mystery to auditors who aren't familiar with how cloud platforms operate.

One of the older, and most popular, offerings from AWS is Simple Storage Service (S3), a scalable object storage service that can hold any type of data. With this ease of use comes risk.

Public S3 buckets are one of the most common and highest-profile AWS misconfigurations. The challenge for auditors is that "public" in S3 isn't a single setting. It's the combination of three separate controls, and a bucket can appear restricted at one layer while still being exposed at another.

This post walks through a script that checks all three layers for every bucket in the account and produces a CSV report.

The script used in this post is available at audit-labs/audit-tools.

1. Background: How S3 Controls Public Access

Let's start with the basics. Before running anything, it helps to understand the three layers the script checks and how they interact.

1.1. Public Access Block

Public Access Block (PAB) is a set of four flags that can be applied at the account level, the bucket level, or both. When all four are enabled, they override any bucket policy or ACL that would otherwise grant public access.

The four flags are:

• BlockPublicAcls: Prevents new ACLs that grant public access and ignores existing ones.

• IgnorePublicAcls: Ignores all public ACLs on the bucket.

• BlockPublicPolicy: Prevents bucket policies that grant public access.

• RestrictPublicBuckets: Restricts access to buckets with public policies to only AWS services and authorized users within the account.

The script checks whether all four flags are enabled at the bucket level. If any one of them is missing or disabled, the bucket is marked FALSE-VULNERABLE. If PAB is missing entirely (no configuration exists at all), the bucket is marked CRITICAL-MISSING, which is the highest-risk state.

Note: While this script checks the bucket level PAB, an account-level PAB may exist as well. If it's enabled at the account level, the bucket is safe regardless of its individual settings.

1.2. Bucket Policy Status

AWS evaluates each bucket policy and exposes an IsPublic flag that reflects whether the policy grants public access. The script checks this flag directly using get-bucket-policy-status. If no bucket policy exists, this column shows No Policy, which is not a finding on its own. Rather, it's a data point that instructs you to keep looking at further evidence.

1.3. ACLs

S3 ACLs predate bucket policies and are largely considered legacy at this point, but they're still in use and still a source of public exposure. The script checks whether any ACL grants READ or WRITE permissions to the AllUsers group, which represents the public internet.

1.4. How the Three Layers Interact

PAB is the highest authority. If PAB is fully enabled at the bucket level, it overrides any public bucket policy or ACL. This means a bucket can have a publicly permissive policy and still be safe, as long as PAB is fully restricted.

The reverse is also true. A bucket with no public policy and no public ACLs is still at risk if PAB is missing or incomplete, because nothing is in place to prevent a future policy or ACL change from exposing it.

2. What the Script Does

The script lists every bucket in the account, determines each bucket's region, runs all three checks against it, and appends the results to a CSV file.

It runs in three steps for each bucket:

1. Determines the bucket's region by trying get-bucket-location against a list of configured regions;

2. Checks PAB, bucket policy status, and ACLs independently;

3. Derives an OverallPublicStatus from the three checks and writes the row to s3_full_public_access_audit.csv.

3. Prerequisites

You'll need:

• AWS CLI installed (or access to CloudShell) and configured with credentials that have read access to s3:ListAllMyBuckets, s3:GetBucketLocation, s3:GetBucketPublicAccessBlock, s3:GetBucketPolicyStatus, and s3:GetBucketAcl

• jq installed

• The AWS_REGIONS variable in the script updated to include any regions your organization uses

Check and update the region list at the top of the script before running:

Then run it:

4. Deriving Overall Public Status

The most important logic in the script is how it combines the three checks into a single OverallPublicStatus. PAB is evaluated first and takes precedence:

If PAB is fully restricted (TRUE), the overall status stays FALSE regardless of what the policy or ACL checks find. If PAB is missing entirely, the overall status is immediately set to critical. If PAB is present but incomplete (FALSE-VULNERABLE), the overall status reflects whatever the policy or ACL checks found.

5. Reading the Output

The script prints progress to the terminal as it runs and saves the full results to s3_full_public_access_audit.csv:

Figure 1: S3 Public Access Audit Results

Here's how to read each column:

• PAB_FullyRestricted: TRUE means all four PAB flags are enabled at the bucket level. FALSE-VULNERABLE means PAB exists but is incomplete. CRITICAL-MISSING means no PAB configuration exists at all.

• Policy_IsPublic: true means AWS has determined the bucket policy grants public access. false means it doesn't. No Policy means no bucket policy is attached.

• ACL_AllUsersRead / ACL_AllUsersWrite: TRUE means the bucket has an ACL granting that permission to the public internet. FALSE means it doesn't.

• OverallPublicStatus: FALSE means the bucket is not publicly accessible based on all three checks. TRUE values include the specific reason (e.g., TRUE - ACL Read, TRUE - Policy, TRUE - PAB Missing (CRITICAL)).

Note the first bucket in the example above: PAB_FullyRestricted is FALSE-VULNERABLE but OverallPublicStatus is still FALSE. This means the bucket isn't currently public, but it's missing the PAB configuration that would prevent it from becoming public if a policy or ACL were changed.

6. Common Exceptions and False Positives

• Static website hosting: Buckets used for static website hosting are intentionally public. These will show up with TRUE overall status and public ACLs or policies. Confirm the business purpose with IT and document them as accepted exceptions rather than findings.

• Policy_IsPublic with No Policy: A No Policy result in the policy column is not a finding. It simply means no bucket policy is attached. The overall status depends on PAB and ACLs.

• FALSE-VULNERABLE with FALSE overall status: This is a configuration weakness rather than an active exposure finding. The bucket isn't currently public, but PAB is not fully enabled, meaning a future change could expose it. Write this up separately from buckets that are actively public, as the risk and remediation are different.

• Cross-account or service-specific policies: Some bucket policies grant access to specific AWS accounts or services (e.g., CloudFront, Config, ELB logging). AWS may flag these as IsPublic even though they're not publicly accessible in practice. Review the actual bucket policy before raising it as a finding.

• Region coverage: If a bucket's region isn't in the AWS_REGIONS list, the script can't determine its location and will skip it with a warning. Make sure the region list in the script covers your organization's full footprint before treating the CSV as a complete population.

7. How to Write Up the Finding

There are two distinct finding types this script can surface, and they should be written up separately.

Finding 1: Bucket with incomplete or missing PAB (configuration weakness)

Deficiency: S3 bucket 13bf5920-a09f-47bc-a75a-394a09f18d6a does not have all four Public Access Block flags enabled at the bucket level (PAB_FullyRestricted: FALSE-VULNERABLE).

Risk: Without fully enabled PAB, a future bucket policy or ACL change could expose the bucket to the public internet without additional controls in place to prevent it.

Finding 2: Bucket actively accessible to the public

Deficiency: S3 bucket example-bucket has an ACL granting READ access to the AllUsers group (ACL_AllUsersRead: TRUE, OverallPublicStatus: TRUE - ACL Read).

Risk: Publicly accessible S3 buckets expose any objects stored within them to the internet, potentially including sensitive data.

To filter the CSV to only buckets with a non-FALSE overall status:

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论