S3 Bucket Misconfiguration Explained

S3 Bucket Misconfiguration Explained

Written by

in

An S3 bucket misconfiguration is one of the most common ways private data ends up on the open internet. Object storage does exactly what its policy tells it to do, so one wrong line in a bucket policy or an access control list can turn a private store into a folder anyone can read or write. This post walks through the ways a bucket gets exposed and shows the exact fix for each one.

What an S3 bucket misconfiguration really is

Strip away the cloud terms and an S3 bucket misconfiguration is an access control mistake. A bucket holds objects. A policy decides who can list, read, write, or delete them. When that policy grants more than it should, the wrong people get in. This is the same class of bug as a broken permission check inside an application, just written in JSON instead of code. If you have read what an access control vulnerability is, this will feel familiar: the system trusts a request it should have denied.

That framing matters. It means the reasoning that finds a missing owner check in application code also finds an over broad storage policy. Both ask the same question: for this actor and this object, should the answer be yes or no?

The common ways a bucket gets exposed

Most incidents come from a short list of mistakes. Here is each one, with the fix.

Public read or write through a policy or ACL

The classic mistake is a bucket policy or an object ACL that grants access to everyone. In S3 that means a policy statement whose principal is *, or an ACL grant to the “All Users” group. Public read leaks whatever is inside. Public write is worse, because a stranger can drop files into your bucket, overwrite a page your site serves, or run up your bill.

Fix: remove the public grant and serve public content through a controlled path, such as a CDN with its own origin access identity, rather than opening the bucket itself.

An over broad principal, the wildcard

A policy can be scoped too wide even when it is not fully public. A principal of * paired with a condition that never really constrains it, or an action of s3:* when the app only needs to read, hands out far more than the workload uses. The bucket may look locked down in a quick glance and still be open to any account that meets a weak condition.

Fix: name the exact role or account that needs access, and grant only the actions it uses.

Authenticated users granted too much

S3 has a group called “Authenticated Users” that means every AWS account in the world, not just yours. People read the word authenticated and assume it means their own users. It does not. Granting read to that group is only a small step from public, because anyone can create an AWS account for free.

Fix: never grant to Authenticated Users. Grant to a specific principal you control.

Unencrypted or public by default

A bucket that ships without default encryption stores objects in the clear, and a bucket created without Block Public Access can be flipped open by any later policy change without a warning. Defaults set the floor for every object that lands later, so a weak default is a standing risk.

Fix: turn on default encryption and Block Public Access at the account level, so a careless policy cannot open a bucket by accident.

Sensitive data in a bucket meant to be static

A bucket built to host a static site is public on purpose. The problem starts when someone drops a database backup, a customer export, or an internal report into that same bucket. Now private data sits behind a public read policy that was correct for the marketing page and wrong for the export.

Fix: keep public assets and private data in separate buckets. A bucket’s policy should match the most sensitive thing inside it.

A policy that is too open, and the fix

Here is a real shaped policy that grants far too much. It lets any principal do any S3 action on every object in the bucket.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadWrite",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": "arn:aws:s3:::acme-reports-8842/*"
    }
  ]
}

Three things are wrong: the principal is a wildcard, the action is a wildcard, and there is no condition. Anyone can read, write, and delete. The corrected version names the one role that needs the data and limits it to reading objects.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AppReadOnly",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/reports-reader"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::acme-reports-8842/*"
    }
  ]
}

The account id 123456789012 and the bucket acme-reports-8842 are invented for the example. The point is the shape: one named principal, one action, one resource. That is least privilege written as a policy.

Block Public Access and least privilege

Two controls stop most of these mistakes before they matter.

  • Block Public Access. This is an account and bucket level switch that refuses any policy or ACL that would make objects public. Turn it on everywhere and turn it off only for the rare bucket that must serve public files, and even then prefer a CDN in front.
  • Least privilege policies. Start from deny. Add the exact principals and actions the workload needs, nothing more. A read only app gets s3:GetObject, not s3:*.

These two controls work together. Block Public Access stops the accidental wildcard, and least privilege keeps the intentional grants small.

Why a listable bucket plus predictable keys leaks data

There is a quieter failure that is easy to miss. If a bucket allows listing, anyone with read access can ask for the full index of object keys and then fetch each one. Even without listing, predictable keys leak data on their own. Suppose your app stores invoices at invoices/2026/000041.pdf. If one number works, an attacker just tries 000042.pdf, then 000043.pdf, and walks the whole set. No listing needed, only a guessable pattern and a read grant that is too wide.

A bucket is only as private as its least protected object, and predictable keys turn one working URL into every URL.

The fix is the same access control thinking. Use keys that cannot be guessed, such as a random identifier, and check on every request that the caller is allowed to read that specific object, rather than trusting that nobody knows the key. This is the storage version of the same object level authorization gap that shows up in APIs.

The same reasoning finds both bugs

A broken authorization check in application code and an over broad storage policy are the same bug in two places. Both grant access the design never intended. A reviewer who can spot a missing owner check on GET /api/invoices/42 can read a bucket policy and ask the same question about a wildcard principal. Related mistakes travel together, so it is worth reading how an exposed env file hands over the very keys that make a bad policy usable, and how those credentials can lead to IAM privilege escalation once an attacker is inside. For more teardowns of this kind, our deep dives collect them in one place.

Finding these gaps means understanding what an application and its storage are supposed to allow, then checking whether the policy agrees. That assumption testing is exactly the kind of access control work UnboundCompute is built to do. Read how we think about it on our about page.

Frequently asked questions

What is an S3 bucket misconfiguration?

It is an access control mistake in object storage, where a bucket policy or an access control list grants more than it should. The result is that people who should be denied can list, read, write, or delete objects. The most common form is a policy whose principal is a wildcard, which lets anyone in.

How do I stop a bucket from being public?

Turn on Block Public Access at the account and bucket level so no policy or ACL can make objects public by accident. Then remove any grant to everyone or to the Authenticated Users group, and scope the policy to the exact role that needs it. Serve genuinely public files through a CDN rather than by opening the bucket.

Why is granting access to Authenticated Users dangerous?

In S3 the Authenticated Users group means every AWS account in the world, not just yours. Since anyone can create an AWS account for free, a grant to that group is nearly as open as public. Always grant to a specific principal you control instead.

Can predictable object keys leak data even without listing?

Yes. If objects are stored at guessable keys such as invoices/2026/000041.pdf, one working URL invites an attacker to try the next number and walk the whole set. Use random identifiers as keys and check on every request that the caller may read that specific object.


Put an autonomous researcher on your own systems

UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.