Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Amazon S3 is the default dumping ground for logs, backups, static assets, data lake exports, and application uploads across virtually every AWS estate, which makes a misconfigured bucket one of the highest-yield targets in cloud reconnaissance. Unlike most cloud attack paths, exploiting a public S3 bucket requires no credentials at all — anonymous, unauthenticated requests are the entire attack. The vulnerability is not a bug in S3 itself; it is almost always a human granting more access than intended through an ACL, a bucket policy, or a disabled account-level guardrail.
The impact ranges from source-of-embarrassment to catastrophic breach depending on what the bucket holds: leaked customer PII, database backups, Terraform state files containing secrets, private keys, or internal source code have all been found sitting in public S3 buckets by researchers and attackers alike. Beyond read exposure, a *writable* bucket is arguably worse — an attacker who can PutObject into a bucket that backs a website, a software update channel, or a CI artifact store can achieve supply-chain-style code execution against every downstream consumer of that bucket’s contents.
Attack Prerequisites
This is largely an unauthenticated reconnaissance and exploitation technique; the attacker needs almost nothing to begin, and only needs specific misconfigurations to escalate from read to write:
- A discoverable or guessable bucket name — S3 bucket names are globally unique and often follow predictable patterns (
company-prod-backups,company-assets), or are found in JS bundles, DNS CNAMEs, breach data, or GitHub history. - No requirement for AWS credentials to enumerate or read a bucket whose ACL or bucket policy grants access to
"Principal": "*"or theAllUsers/AuthenticatedUserspredefined S3 groups. - Block Public Access disabled at the account or bucket level — this account-wide setting is the primary safety net, and its absence is what allows a permissive ACL/policy to actually take effect.
- For write/tamper impact, a policy or ACL granting
s3:PutObject(orWRITE/WRITE_ACP) to the public or to an overly broad set of cross-account principals.
How It Works
S3 access control is layered: account-level Block Public Access (BPA) settings can unconditionally override anything more permissive underneath, then bucket policies (resource-based JSON, evaluated like any IAM policy) and legacy ACLs (canned grants like public-read or public-read-write, or fine-grained grants to predefined groups) each independently grant access. A request is allowed if *any* applicable policy or ACL grants it and none explicitly denies it — meaning a single overly broad statement anywhere in this stack is enough to expose the bucket, regardless of how tight the others are.
The AllUsers group grants access to literally anyone on the internet, with or without an AWS account; AuthenticatedUsers sounds narrower but actually grants access to *any* AWS account in the world, since it is not scoped to the bucket owner’s account — a distinction that trips up developers who assume it means “authenticated to my app.” Bucket policies compound the risk because they are frequently copy-pasted from tutorials or generated by IaC modules with a wildcard Principal intended only for temporary testing, then never tightened.
Because S3’s HTTP API is simple REST, no SDK or AWS account is required to interact with a public bucket — curl, a browser, or the AWS CLI’s --no-sign-request flag (which skips SigV4 signing) all work identically. This is what makes internet-wide bucket scanning practical: tools can probe candidate names with plain unauthenticated requests and immediately know from the response code whether a bucket exists and is public.
Vulnerable Code / Configuration
The most direct misconfiguration is a bucket policy that grants read (and sometimes list) access to everyone. This statement, commonly left over from making a single static-site bucket public, instead applies to the *entire* bucket including any sensitive prefixes added later:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::company-prod-backups",
"arn:aws:s3:::company-prod-backups/*"
]
}
]
}
JSON"Principal": "*" combined with s3:ListBucket means anyone can enumerate the full key listing and download every object, with no logging beyond whatever S3 server access logging or CloudTrail data events the owner happened to enable. Note the bucket name itself signals its sensitivity (prod-backups) — exactly the kind of bucket that should never appear in a public-read policy.
The write-capable variant is more dangerous still. A canned ACL applied via the CLI or a misconfigured Terraform aws_s3_bucket_acl resource can grant public-read-write, allowing anonymous uploads and deletions:
# VULNERABLE: grants anonymous read AND write to the bucket
aws s3api put-bucket-acl \
--bucket company-cdn-assets \
--acl public-read-write
BashA bucket with this ACL serving a CDN or software distribution channel lets an attacker overwrite or plant files — a JavaScript bundle, an installer, a container layer — that every downstream client or user then fetches and trusts. This is the S3 equivalent of a supply-chain compromise, and it requires nothing more than an unauthenticated PutObject call.
Walkthrough / Exploitation
Reconnaissance starts by simply trying to interact with the candidate bucket name unauthenticated. --no-sign-request tells the AWS CLI to skip SigV4 signing entirely, so no credentials of any kind are sent:
aws s3 ls s3://company-prod-backups --no-sign-request
aws s3api get-bucket-acl --bucket company-prod-backups --no-sign-request
aws s3api get-bucket-policy --bucket company-prod-backups --no-sign-request
BashIf listing succeeds, the entire bucket (or the accessible prefix) can be pulled down in one command for offline review — a common next step is grepping the dump for secrets, credentials, and PII:
aws s3 sync s3://company-prod-backups ./loot --no-sign-request
grep -RIl -E 'AKIA[0-9A-Z]{16}|BEGIN (RSA|OPENSSH) PRIVATE KEY' ./loot
BashAt scale, testers use dedicated bucket-hunting tools rather than manual CLI probing — s3scanner and similar utilities take a wordlist of likely bucket names (company name, common suffixes like -backup, -prod, -assets, -logs) and report which ones exist, are listable, and are readable/writable:
s3scanner scan --bucket-file candidate-bucket-names.txt
BashIf the bucket permits PutObject, impact is demonstrated (in an authorized engagement only) by uploading an innocuous marker file to prove write access without tampering with real content:
echo 'pentest-write-test' > poc.txt
aws s3 cp poc.txt s3://company-cdn-assets/poc.txt --no-sign-request
BashScoutSuite, run with read-only credentials against the account being assessed (rather than anonymously), is the standard way to enumerate *every* bucket’s effective exposure in one pass, cross-referencing ACLs, bucket policies, and account-level Block Public Access in a single report rather than checking bucket-by-bucket:
scout aws --report-dir ./scoutsuite-report
BashNote: A bucket can be *listable* without being *readable*, or vice versa —
s3:ListBucketands3:GetObjectare independent permissions. A bucket that denies listing but allowsGetObjecton any key still leaks data to anyone who can guess or already knows an object key (commonly the case for pre-signed-URL-adjacent buckets or ones referenced from public web pages).
Opsec: Anonymous S3 requests are logged, if at all, only via S3 server access logging or CloudTrail data-events for the bucket — both are opt-in and frequently disabled, especially for older buckets, which means unauthenticated recon and even bulk downloads often leave the owner with zero visibility unless they specifically enabled these settings in advance.
Detection and Defense
S3 exposure is one of the most preventable classes of cloud incident because AWS provides a single account-wide kill switch plus strong continuous-audit tooling:
- Enable S3 Block Public Access at the account level (all four sub-settings) as the default posture, and require an explicit, reviewed exception for the rare bucket that must be public (e.g. static website hosting behind CloudFront with Origin Access Control instead of a public bucket).
- Avoid ACLs entirely where possible — enable S3 Object Ownership “Bucket owner enforced”, which disables ACLs altogether and forces all access control through bucket policies, which are easier to review and diff.
- Least-privilege bucket policies — never use
"Principal": "*"without aCondition(e.g.aws:SourceArnfor CloudFront OAC, or a specificaws:PrincipalOrgID) unless the bucket is genuinely meant to be world-readable. - AWS IAM Access Analyzer for S3 to continuously flag buckets reachable from outside the account or organization.
- Enable S3 server access logging or CloudTrail data events on sensitive buckets so unauthenticated access attempts are actually recorded.
- Encrypt sensitive objects with SSE-KMS using a key policy that restricts
kms:Decrypt, so even a bucket-policy misconfiguration does not hand over plaintext. - Continuous scanning with ScoutSuite or AWS Trusted Advisor/Security Hub to catch drift, since a single Terraform apply or console click can re-introduce public access after the fact.
Real-World Impact
Public S3 buckets have caused some of the largest and most publicized cloud data breaches on record — including exposures at major defense contractors, data brokers, and consumer platforms that left hundreds of millions of records reachable via plain unauthenticated HTTP requests. The pattern is consistent across nearly all of them: no exploit was needed, only a permissive ACL or bucket policy discovered through routine, low-cost bucket-name enumeration.
Conclusion
S3 exposure persists because the service makes it easy to grant broad access with a single ACL flag or a copy-pasted policy statement, and easy for that grant to outlive the temporary need it was created for. Block Public Access as a default-on account guardrail, disabling ACLs in favor of reviewable bucket policies, and routine automated scanning close off nearly all of this attack surface — the remaining risk is almost always a process failure, not a technical limitation of S3 itself.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments