Most engineers think…
Most engineers think "my data is in S3, so it must be safe — AWS is secure." They assume a bucket is private unless they deliberately share it, and that turning on encryption is the main thing.
Wrong — and it's the most expensive wrong belief in cloud. S3 buckets ARE private by default, but a single careless "Principal": "*" in a bucket policy, or an old public ACL, can expose every object — and encryption does nothing against a reader you authorised. Real S3 security is layered access control: turn on all four Block Public Access settings, disable ACLs, enforce encryption in transit and at rest by policy, make data immutable with Object Lock, and then prove none of it leaks with IAM Access Analyzer and Macie. The default is safe; humans make it unsafe.
① Why S3 leaks happen — the four access layers
Sneha is an L1 cloud engineer at Infosys. A teammate needs to share a few report files from an S3 bucket with a partner, gets frustrated by permissions, finds a Stack Overflow answer, and pastes a bucket policy with "Principal": "*". It works — the partner can read the files. It also means anyone on the internet can read them, including the customer PII sitting next to those reports. That one line is the shape of almost every S3 bucket breach you've read about.
Here's the reassuring part first: in S3, buckets and objects are private by default. A brand-new bucket grants access to nobody but the owner. Leaks happen because a human deliberately (or accidentally) opens one of four access layers. Understanding those four — and the order S3 checks them in — is the whole game.
Layer one is Block Public Access (BPA) — the master safety switch at the account and bucket level. Layer two is the bucket policy, a JSON document attached to the bucket. Layer three is ACLs — the legacy model AWS now tells you to switch off. Layer four is IAM identity policies attached to your users and roles.
These layers don't vote politely — they stack. The dangerous combination is simple: if BPA is OFF and a bucket policy (or an old ACL) grants AllUsers read, the bucket is public, full stop. AWS considers an ACL public the moment it grants any permission to the predefined AllUsers or AuthenticatedUsers groups, and considers a bucket policy public if it allows a wildcard principal with no condition that pins it to a fixed account, IP range or VPC. Block Public Access exists precisely to catch these mistakes — it overrides the policy or ACL and rejects the request, regardless of what the policy says.
The four layers, one tap each
Tap each card — every S3-security interview and the SCS-C02 exam starts from these four and the order they're evaluated.
Account + bucket master switch (4 toggles). Overrides any public policy or ACL. So: turn it on and a careless '*' can't leak you.
Resource JSON on the bucket. Where 'Principal: *' lives. So: this is the layer most public-bucket breaches come from.
Old coarse model granting AllUsers/AuthenticatedUsers. So: AWS now disables these by default — keep them off.
Attached to your users/roles, says what THAT identity may do. So: least-privilege here controls your own people, not the public.
Think of your housing society. Block Public Access is the main gate guard who can refuse anyone, no matter what pass they wave. The bucket policy is the visitor register at your flat door — write "let everyone in" there and strangers walk up. ACLs are the old paper chits the previous owner left lying around. IAM policies are the resident ID cards your own family carries. A leak is leaving the flat-door register on "everyone" while the main gate guard is asleep. Keep the guard awake (BPA on) and the careless register entry stops mattering.
Rahul at TCS finds a bucket with an old ACL granting READ to the AllUsers group, but the account has all four Block Public Access settings turned ON. Is the object public right now?
Pause & Predict
Predict: a bucket policy has BOTH a statement allowing a specific partner account AND a statement with "Principal": "*". You enable RestrictPublicBuckets. What happens to the partner's cross-account access? Type your guess.
② Block Public Access + Ownership — the master switch
Now the controls. Block Public Access is four independent settings you can mix in any combination. You almost always want all four ON, and you want them ON at the account level so they apply to every current and future bucket — AWS Security Hub control S3.8 checks exactly this.
The four settings, in plain words. BlockPublicAcls rejects any new request that tries to attach a public ACL. IgnorePublicAcls makes S3 ignore every public ACL that already exists. BlockPublicPolicy rejects any attempt to attach a bucket policy that would grant public access. RestrictPublicBuckets limits a bucket that already has a public policy to AWS service principals and the owner account only. The first two are about ACLs; the last two are about policies — together they cover both leak paths.
Layer two of this section is S3 Object Ownership. Set it to Bucket owner enforced and ACLs are disabled entirely — the bucket owner automatically owns every object and you control everything with policies, not ACLs. Since April 2023, new buckets are created with this setting by default. With ACLs disabled, a PutObject that tries to set a custom ACL fails with AccessControlListNotSupported — which is good: it means nobody can sneak a public grant in through an ACL again.
aws s3api put-public-access-block \
--bucket tcs-payroll-prod \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3api put-bucket-ownership-controls \
--bucket tcs-payroll-prod \
--ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'# (both commands return no output on success — exit code 0)
$ aws s3api get-public-access-block --bucket tcs-payroll-prod \
--query PublicAccessBlockConfiguration
{
"BlockPublicAcls": true, "IgnorePublicAcls": true,
"BlockPublicPolicy": true, "RestrictPublicBuckets": true
}Symptom: a security scan still flags buckets as public even though Aditya turned on Block Public Access for the one bucket he cares about. Cause: BPA at the bucket level only protects that bucket — every other bucket someone spins up tomorrow starts unprotected. Fix: set Block Public Access at the account level (aws s3control put-public-access-block --account-id 123456789012 ...) so all current and future buckets inherit it. S3 applies the most restrictive of account + bucket settings, so account-level ON can't be undone by a single bucket.
Priya wants to be sure no future bucket in her Wipro account can ever be made public by an ACL, even ones her teammates create next month. What's the strongest single move?
Pause & Predict
Predict: you set Object Ownership to 'Bucket owner enforced' (ACLs disabled), but an app still does PutObject with --acl bucket-owner-full-control. Does the upload succeed or fail? Type your guess.
③ Encryption + data protection — at rest, in transit, immutable
Public access controls decide who can reach the data; encryption decides who can read it if they get the bytes, and immutability decides whether they can destroy it. Start with at-rest. S3 gives you three server-side options. SSE-S3 uses an AES-256 key AWS owns — free, zero config. SSE-KMS uses a KMS key you control, with a CloudTrail trail of every decrypt — the right pick for PII or regulated data. SSE-C means you supply the key on every request and AWS never stores it.
Since 5 January 2023, S3 automatically applies SSE-S3 as the base encryption on every new object in every bucket, at no cost — so "is my data encrypted at rest?" is now answered "yes" by default. The remaining decision is whether you need the control and audit trail of SSE-KMS. If you do, turn on the S3 Bucket Key: it can cut your KMS request cost by up to 99% by reducing per-object KMS calls. Forgetting the bucket key is how teams get a surprise KMS bill on a high-traffic bucket.
At-rest isn't enough on its own. Enforce encryption in transit too, with a bucket policy that denies any request where aws:SecureTransport is false — that blocks plain HTTP and forces TLS. You can also deny PutObject unless the right s3:x-amz-server-side-encryption header is present, so nobody uploads unencrypted or with the wrong key. These two policy statements are a standard SCS-C02 answer for "enforce encryption."
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::tcs-payroll-prod",
"arn:aws:s3:::tcs-payroll-prod/*"
],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
},
{
"Sid": "DenyWrongEncryption",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::tcs-payroll-prod/*",
"Condition": {
"StringNotEquals": { "s3:x-amz-server-side-encryption": "aws:kms" }
}
}
]
}# apply it, then test plain HTTP is blocked: $ aws s3api put-bucket-policy --bucket tcs-payroll-prod --policy file://policy.json $ curl http://tcs-payroll-prod.s3.amazonaws.com/test.txt# 403 over HTTP — TLS now mandatory AccessDeniedAccess Denied
Last, defend against deletion and ransomware. Turn on Versioning so an overwrite or delete never truly loses the old copy. Add Object Lock (WORM): in Governance mode only users with a special permission can remove the lock; in Compliance mode nobody — not even the account root — can delete the version until retention expires. Top it with MFA Delete so wiping versions needs a physical second factor.
▶ Watch a ransomware wipe hit a protected bucket vs an unprotected one
An attacker has stolen an IAM key with s3:PutObject and s3:DeleteObject on the bucket. Follow what happens when they try to encrypt-and-delete your objects. Press Play for the healthy path, then Break it to see the failure.
Karthik at PhonePe faces this
Karthik gets paged: a finance bucket's objects are suddenly unreadable and a 'pay-to-decrypt' text file appeared. CloudTrail shows PutObject calls with SSE-C from an IAM key that belongs to a former contractor.
A long-lived access key was never rotated and leaked. The attacker (Codefinger-style) re-uploaded every object encrypted with their own SSE-C key — which S3 happily accepts and never stores the key for — then set a 7-day delete lifecycle to pressure payment.
Karthik separates 'reachable' from 'recoverable': access wasn't public, so this is a credential + data-protection failure, not a public-bucket one. He checks whether prior object versions still exist and whether Object Lock is set.
S3 Console > (bucket) > Properties > Bucket Versioning + Object Lock; and CloudTrail > Event history filtered on PutObject/DeleteObject for the contractor's keyBecause versioning was ON, he restores the previous (unencrypted-by-attacker) object versions; revokes and deletes the leaked key; adds a policy to Deny SSE-C uploads (Deny if s3:x-amz-server-side-encryption-customer-algorithm is present); and enables Object Lock + MFA Delete so a future delete is refused outright.
Old versions are restored and readable; the leaked key is gone from IAM; a test SSE-C PutObject now returns AccessDenied; and Object Lock shows Compliance mode with a retention period.
Meera must guarantee that quarterly audit logs in S3 cannot be deleted by ANYONE — including a rogue admin with root — for seven years. Which combination does that?
Pause & Predict
Predict: your bucket already has default SSE-S3 (the free 2023 base). Why might a security reviewer STILL flag it for a PII workload and ask for SSE-KMS instead? Type your guess.
④ Detect + verify — Access Analyzer, Macie, CloudTrail
Configuring controls is half the job; proving they hold is the other half — and it's where the exam and real audits live. Three tools do the proving. IAM Access Analyzer for S3 tells you which buckets are public or shared cross-account. Amazon Macie tells you which buckets actually contain sensitive data like PAN/Aadhaar-style PII. CloudTrail S3 data events tell you who read or changed which object.
Start with Access Analyzer, because it directly answers "is anything public?". In the S3 console, open General purpose buckets and expand the External access summary — it shows active public and cross-account findings per Region (you first create an external-access analyzer once per Region in the IAM console). For each public bucket it shows whether the access comes from a bucket policy, an ACL, or an access-point policy, and gives you a one-click Block all public access button to fix it.
Then layer in Macie to find the data that actually matters. A sensitive-data discovery job (Macie console → Jobs → Create job) scans selected buckets and reports objects containing credit-card numbers, credentials, passport/PII patterns and more, using built-in managed data identifiers. Automated discovery samples your whole estate daily; targeted jobs go deep on the buckets you choose. The point: a public bucket full of cat photos is a shrug; a public bucket Macie flags as full of PII is a fire.
# 1) Ask S3 directly whether the bucket policy is public
aws s3api get-bucket-policy-status --bucket flipkart-invoices
# 2) Record object-level reads/writes (who-read-what) for the bucket
aws cloudtrail put-event-selectors --trail-name org-trail \
--event-selectors '[{"ReadWriteType":"All","IncludeManagementEvents":false,\
"DataResources":[{"Type":"AWS::S3::Object",\
"Values":["arn:aws:s3:::flipkart-invoices/"]}]}]'{
"PolicyStatus": { "IsPublic": false }
}
# IsPublic:false after the fix. Data events now on:
{ "TrailName": "org-trail",
"EventSelectors": [ { "ReadWriteType": "All",
"DataResources": [ { "Type": "AWS::S3::Object" } ] } ] }Run the checklist out loud: (1) get-public-access-block shows all four true at account AND bucket; (2) Object Ownership = BucketOwnerEnforced (ACLs off); (3) default encryption = SSE-KMS with the bucket key on; (4) bucket policy denies aws:SecureTransport=false; (5) versioning on (+ Object Lock for immutable data); (6) IAM Access Analyzer External access summary = 0 public; (7) Macie shows no PII sitting in a shared bucket. If all seven pass, that bucket will not be the next headline.
For the cert: this maps straight onto AWS Certified Security – Specialty (SCS-C02). Domain 2 (Security Logging and Monitoring) is your CloudTrail data events and Macie; Domain 4 (Identity and Access Management) is the bucket-policy / BPA / cross-account evaluation; Domain 5 (Data Protection) is SSE-S3/KMS/C, the bucket key, TLS enforcement and Object Lock. S3 questions show up across all three — get this lesson solid and you've covered a meaningful slice of the blueprint.
An interviewer at ICICI asks Arjun: "A bucket is NOT public, but how do you find out whether it contains customer Aadhaar/PAN data you didn't know about?" Best answer?
🤖 Ask the AI Tutor
Tap any question — instant, scoped to this lesson. No login, no waiting.
Pre-curated from AWS docs + community Q&A, scoped to this lesson. For a live prod issue, paste your export into chat.techclick.in.
📝 Wrap-up assessment — six more
You've answered 4 inline. Six left. 70% (7 of 10) marks the lesson complete on your profile. Tap Submit all answers at the end.
🧠 In your own words
Type one line: In one line: why does turning ON account-level Block Public Access protect you even from a bucket policy that says "Principal": "*"? Then compare to the expert version.
🗣 Teach a friend
Best way to lock it in — explain it in one line to a teammate. Tap to generate a paste-ready summary.
📖 Glossary
- S3 bucket
- A storage container in Amazon S3 that holds objects (files). Globally named and private by default.
- Block Public Access (BPA)
- Account- and bucket-level settings (4 toggles) that override any policy or ACL granting public access. The master safety switch.
- BlockPublicAcls / IgnorePublicAcls
- BPA toggles for ACLs: reject NEW public ACLs, and ignore ALL existing public ACLs, respectively.
- BlockPublicPolicy / RestrictPublicBuckets
- BPA toggles for policies: reject a policy that would grant public access, and restrict a bucket that already has a public policy to AWS services + the owner account.
- Object Ownership (bucket-owner-enforced)
- Bucket setting that disables ACLs entirely; the owner controls every object via policies. Default on new buckets since April 2023.
- ACL
- Access Control List — the legacy, coarse S3 permission model (grant to AllUsers/AuthenticatedUsers). Discouraged; disabled by default.
- Bucket policy
- A resource-based JSON policy on the bucket stating which principals may perform which actions. Where 'Principal: *' leaks happen.
- SSE-S3 / SSE-KMS / SSE-C
- Server-side encryption with: an AWS-managed AES-256 key (free, default), a KMS key you control + audit, or a customer-supplied key AWS never stores.
- S3 Bucket Key
- Bucket setting that uses one bucket-level data key to cut SSE-KMS API calls — reducing KMS cost by up to 99%.
- aws:SecureTransport
- Policy condition key; Deny when it is false to force TLS (HTTPS-only) and block plain-HTTP requests.
- Versioning / MFA Delete
- Keep every object version (delete adds a marker); MFA Delete requires a second factor to permanently remove versions or change versioning.
- Object Lock (WORM)
- Write-Once-Read-Many protection. Governance mode = override with a permission; Compliance mode = nobody (even root) can delete until retention ends.
- IAM Access Analyzer for S3
- Reports buckets reachable by the public internet or other accounts, shown as the 'External access summary' in the S3 console.
- Amazon Macie
- Scans S3 object content for sensitive data (PII, credentials, financial info) via automated discovery or targeted jobs.
📚 Sources
- AWS Documentation — Amazon S3 User Guide, "Blocking public access to your Amazon S3 storage" (the four BPA settings BlockPublicAcls/IgnorePublicAcls/BlockPublicPolicy/RestrictPublicBuckets, account vs bucket level, the meaning of "public" for ACLs and policies, most-restrictive evaluation). docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html
- AWS Documentation — Amazon S3 User Guide, "Controlling ownership of objects and disabling ACLs" (bucket-owner-enforced disables ACLs; default on new buckets; AccessControlListNotSupported error) + "Setting default server-side encryption behavior" (SSE-S3 base, bucket keys). docs.aws.amazon.com/AmazonS3/latest/userguide/about-object-ownership.html · docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html
- AWS News — "Amazon S3 now automatically encrypts all new objects" (SSE-S3 base encryption on every bucket at no cost, effective 5 January 2023). aws.amazon.com/about-aws/whats-new/2023/01/amazon-s3-encrypts-new-objects-default/
- AWS Documentation — "Reviewing bucket access using IAM Access Analyzer for S3" + Amazon Macie User Guide "Discovering sensitive data with Macie" / "Creating a sensitive data discovery job" (External access summary console path; managed data identifiers; discovery jobs). docs.aws.amazon.com/AmazonS3/latest/userguide/access-analyzer.html · docs.aws.amazon.com/macie/latest/user/discovery-jobs-create.html
- Halcyon Threat Research — "Abusing AWS Native Services: Ransomware Encrypting S3 Buckets with SSE-C" (Codefinger campaign, disclosed 13 Jan 2025: leaked keys with s3:GetObject/s3:PutObject + SSE-C to encrypt objects AWS can't recover; only an HMAC is logged) + Amazon S3 Object Lock feature page (WORM, Governance/Compliance, Cohasset SEC 17a-4 assessment). halcyon.ai/blog/abusing-aws-native-services-ransomware-encrypting-s3-buckets-with-sse-c · aws.amazon.com/s3/features/object-lock/
- AWS Certified Security – Specialty (SCS-C02) exam guide — Domain 2 (Logging & Monitoring: CloudTrail data events, Macie), Domain 4 (IAM: bucket policy / BPA / cross-account), Domain 5 (Data Protection: SSE-S3/KMS/C, bucket key, aws:SecureTransport, Object Lock). aws.amazon.com/certification/certified-security-specialty/
What's next?
You've locked S3 down and proved it isn't public. But who's poking at your account RIGHT NOW — the leaked-key reads, the recon API calls, the impossible-travel logins? Next we turn on the threat-detection layer that watches all of it.