Amazon S3 buckets keep leaking data because access is decided by several overlapping policy layers, and one permissive layer is often enough. This guide sets out a configuration baseline for S3 general purpose buckets: how authorisation is evaluated, which settings to lock at organisation level, how to control the machine identities that read and write objects, what to log and alert on, and how to make data survive deletion or malicious encryption. It is written for cloud security engineers, platform teams and auditors who own S3 at scale. Incident response for an exposed bucket is covered separately in the Cloud Storage Exposure Playbook; this page is about preventing the exposure in the first place.
Key takeaways
- Turn on all four Block Public Access settings at organisation level and treat any exception as a reviewed, documented decision, because S3 enforces the most restrictive combination of organisation, account, bucket and access point settings.
- Disable ACLs with Object Ownership set to Bucket owner enforced so that access is decided only by policies (IAM, bucket, VPC endpoint, SCP and RCP), which are far easier to review than per-object ACLs.
- Most S3 incidents are identity incidents: a leaked access key or an over-scoped workload role reads or rewrites data with valid credentials, so bucket hardening must be paired with tight, short-lived machine credentials.
- Versioning plus Object Lock is the ransomware control for S3: it stops overwrites and permanent deletes from destroying the only copy, including attacks that abuse SSE-C or lifecycle rules.
- Management events are not enough: enable CloudTrail data events or GuardDuty S3 Protection on sensitive buckets, otherwise object reads and bulk deletes are invisible.
How identity and access work in Amazon S3
An S3 request is allowed only when the policy layers that apply to it allow it and none of them denies it. For a request inside one account, either an identity-based policy or the bucket policy can grant access. For a cross-account request, both the caller's identity policy and the bucket policy (or access point policy) must allow it. Guardrail layers such as SCPs, RCPs and VPC endpoint policies never grant anything; they only cap what the other layers can grant. Block Public Access sits across all of them and overrides public grants regardless of what a policy says.
| Layer | Attached to | Can grant? | Typical use |
|---|---|---|---|
| Identity-based policy | IAM role, user or group | Yes | What a workload or person may do on which buckets |
| Bucket policy | The bucket | Yes | Cross-account access, deny insecure transport, require encryption, restrict to a VPC endpoint |
| Access point policy | An S3 access point | Yes (within the bucket policy delegation) | Per-application views of a shared bucket, VPC-only network origin |
| ACLs | Bucket and objects | Yes, if ACLs are enabled | Legacy only; disable with Bucket owner enforced |
| VPC endpoint policy | Gateway or interface endpoint | No (limit only) | Stop workloads in a VPC writing to buckets outside your organisation |
| Service control policy (SCP) | Organisation root, OU or account | No (limit only) | Cap what your own principals can do, for example forbid disabling Block Public Access |
| Resource control policy (RCP) | Organisation root, OU or account | No (limit only) | Cap what any principal, including external ones, can do to your buckets |
| Block Public Access | Organisation, account, bucket, access point | No (overrides public grants) | Neutralise public ACLs and public bucket policies |
What S3 treats as public
- ACLs: an ACL is public if it grants any permission to the
AllUsersorAuthenticatedUsersgroups. "Authenticated users" means any AWS account in the world, not your organisation. - Bucket policies: S3 assumes a policy is public unless it grants access only to fixed values, without wildcards or policy variables, of keys such as a named principal,
aws:PrincipalOrgID,aws:SourceVpc,aws:SourceVpce,aws:SourceAccount,aws:SourceArnor anaws:SourceIprange. IPv4 ranges wider than /8 (outside RFC 1918 space) are treated as public. - Not covered: Block Public Access does not evaluate identity-based policies or the policies on other resources, such as the KMS key that encrypts the bucket. Those need their own review.
The four Block Public Access settings
| Setting | Effect when true | What it does not do |
|---|---|---|
BlockPublicAcls | Rejects PutBucketAcl, PutObjectAcl and uploads that carry a public ACL | Does not remove existing public ACLs |
IgnorePublicAcls | S3 ignores every public ACL on the bucket and its objects | Does not stop new public ACLs being written |
BlockPublicPolicy | Rejects PutBucketPolicy (and same-account access point policies) that would allow public access | Does not change an existing public policy |
RestrictPublicBuckets | Limits a bucket with a public policy to AWS service principals and authorised users in the owner's account, blocking cross-account access derived from that policy | Does not rewrite the policy itself |
Because none of the settings rewrites an existing ACL or policy, removing a setting immediately makes any old public grant effective again. Block Public Access can now be set as an AWS Organizations policy attached to the root or an OU, where all four settings apply together and override account settings in member accounts. Access point settings can only be chosen when the access point is created. Block Public Access is not available per object.
Admin roles and privileged access
A small set of S3 actions changes who can reach data. They deserve the same treatment as IAM administration: few holders, protected by SCPs, and alerted on every use.
| Action | Why it is privileged | Suggested guardrail |
|---|---|---|
s3:PutBucketPolicy, s3:DeleteBucketPolicy | Can open a bucket to another account or remove deny statements | Restrict to a pipeline role; alert on every call outside it |
s3:PutBucketPublicAccessBlock, s3:PutAccountPublicAccessBlock | Removing a block re-enables old public grants at once | Deny via SCP for all but a break-glass role, or manage BPA as an organisation policy |
s3:PutBucketAcl, s3:PutObjectAcl, s3:PutBucketOwnershipControls | Re-enabling ACLs restores any pre-existing ACL permissions | Deny via SCP once ACLs are disabled estate-wide |
s3:PutLifecycleConfiguration | A lifecycle rule can expire every object; Codefinger used one to pressure victims | Alert on changes; restrict to storage owners |
s3:PutReplicationConfiguration | Can copy every new object to a bucket in another account | Alert; constrain destinations with an RCP or SCP |
s3:BypassGovernanceRetention | Overrides governance-mode Object Lock retention | Grant to no standing role; use compliance mode where retention must be absolute |
s3:PutBucketEncryption | Can switch default encryption or enable SSE-C on a bucket | Alert; deny SSE-C where not needed |
- Root and break-glass: a bucket policy that denies everyone can only be repaired by the account root user or a principal it still allows. Document that recovery path before rolling out deny-by-default policies, and keep root credentials offline with MFA.
- Human access: engineers should reach S3 through federated, time-limited roles, not IAM users with access keys. See the AWS IAM Identity Center Guide.
- Separation of duties: the team that can change a bucket policy should not be the same team that can delete the CloudTrail and access-log buckets that would record the change.
Integrations, API tokens and service accounts
The majority of principals that touch S3 are machines: applications on EC2 and Lambda, CI/CD pipelines, data platforms, backup tools and SaaS vendors. They are where S3 breaches usually start. In the Capital One breach, a firewall's IAM role, reached through the instance metadata service, could list and read customer-data buckets it had no need for. In the Codefinger S3 ransomware campaign, long-term access keys with read and write rights were enough to re-encrypt objects with SSE-C and set expiry rules, with no AWS vulnerability involved.
- Use roles, not access keys: attach IAM roles to EC2, ECS, EKS and Lambda so workloads receive temporary credentials. Outside AWS, use OIDC federation for CI/CD or IAM Roles Anywhere for on-premises servers. See the AWS STS and AssumeRole Guide.
- Scope to bucket and prefix: grant
s3:GetObjectonarn:aws:s3:::bucket/prefix/*, nots3:*on*. List and delete rights are separate decisions; most readers need neither. - Protect the credential source: require IMDSv2 on instances whose roles can read sensitive buckets. See the EC2 Instance Metadata Service Hardening Guide.
- Third parties: grant SaaS vendors a role they assume with an external ID and a policy limited to the prefixes they process, never an IAM user key pasted into their console. Restrict cross-account access to your own organisation with
aws:PrincipalOrgIDin bucket policies or, estate-wide, with an RCP. See the AWS Resource Control Policies and Data Perimeter Guide. - Presigned URLs: a presigned URL carries the permissions of the principal that signed it for its lifetime. Sign with a narrowly scoped role and keep expiry short; a URL signed with temporary credentials stops working when those credentials expire.
- Network path: for workloads inside a VPC, use a gateway endpoint for S3 and a bucket policy with
aws:SourceVpceoraws:SourceVpc. Pair it with an endpoint policy that only allows your organisation's buckets, which closes an easy exfiltration path. Test carefully: a wrong endpoint ID denies all access, and console users outside the VPC will lose access too. - Leaked key hygiene: find and remove IAM user keys with S3 rights, and rotate any key that has appeared in code or logs. See the Cloud Access Key Leak Playbook.
Logging and detection
S3 has three distinct log sources and they answer different questions. CloudTrail management events record configuration changes and are on by default in event history. CloudTrail data events record object-level calls such as GetObject, PutObject and DeleteObject, and must be enabled on a trail or event data store. Server access logs record every request to a bucket, including anonymous ones, in a delivered log file. Destination buckets for server access logs must use SSE-S3, not SSE-KMS.
| Signal | Source | Why it matters |
|---|---|---|
PutBucketPolicy, DeleteBucketPolicy, PutBucketAcl, PutObjectAcl | CloudTrail management events | Access being widened |
PutBucketPublicAccessBlock, DeletePublicAccessBlock | CloudTrail management events | A guardrail being removed |
PutBucketLifecycle, PutBucketReplication, PutBucketEncryption | CloudTrail management events | Staging for destruction, exfiltration or SSE-C ransom |
Spike in GetObject or ListObjects by one principal | CloudTrail data events, GuardDuty | Bulk download (ATT&CK T1530 Data from Cloud Storage) |
Bulk PutObject or CopyObject with SSE-C headers, or bulk deletes | CloudTrail data events | Malicious re-encryption or destruction (T1486, T1485) |
Requests without tlsDetails.tlsVersion | CloudTrail | Clients using plain HTTP |
GuardDuty S3 Protection analyses S3 data events across all buckets in the Region without you configuring data event logging yourself, though it skips unauthenticated requests to public objects. Its finding types include Exfiltration:S3/AnomalousBehavior, Impact:S3/AnomalousBehavior.Delete, Impact:S3/AnomalousBehavior.Write, Impact:S3/AnomalousBehavior.Permission, Policy:S3/BucketBlockPublicAccessDisabled, Policy:S3/BucketAnonymousAccessGranted and Stealth:S3/ServerAccessLoggingDisabled. See the Amazon GuardDuty Guide. IAM Access Analyzer flags buckets shared outside your account or organisation, and Amazon Macie finds sensitive data so you know which buckets deserve the strictest controls.
Metrics that show the baseline is working
- Public exposure: number of buckets with a public or cross-account finding in IAM Access Analyzer that is not on the approved exceptions list (target zero).
- ACL debt: percentage of buckets with Object Ownership set to Bucket owner enforced (target 100%).
- Visibility: percentage of buckets classified as sensitive that are covered by data events or S3 Protection.
- Machine credential risk: count of IAM user access keys with S3 write or delete rights, and their age.
- Recoverability: percentage of critical buckets with versioning, Object Lock or replication to a separately controlled account, plus the last tested restore date.
- Response: median time from a policy or Block Public Access change outside the pipeline to triage.
Hardening baseline
| Control | Setting or mechanism | Check |
|---|---|---|
| Block public access | All four settings, set as an organisation policy or at every account | Config rules s3-bucket-public-read-prohibited, s3-bucket-public-write-prohibited |
| Disable ACLs | Object Ownership: Bucket owner enforced (default for new buckets) | get-bucket-ownership-controls per bucket |
| Encryption in transit | Bucket policy deny where aws:SecureTransport is false | Config rule s3-bucket-ssl-requests-only |
| Encryption at rest | SSE-S3 is the default; SSE-KMS with a customer managed key for sensitive data, with S3 Bucket Keys to cut KMS request volume | S3 Inventory encryption status |
| SSE-C | Keep disabled unless a documented need exists; AWS states SSE-C has been disabled by default on new general purpose buckets since April 2026 | Bucket encryption configuration |
| Versioning and Object Lock | Versioning on; Object Lock (compliance or governance mode) on logs and backups | Config rule s3-bucket-versioning-enabled |
| Replication | Cross-Region or cross-account replication for critical data; both buckets need versioning | Config rule s3-bucket-replication-enabled |
| Logging | Server access logs or CloudTrail data events on sensitive buckets | Config rules s3-bucket-logging-enabled, cloudtrail-s3-dataevents-enabled |
| Name squatting | Create buckets in the account regional namespace; do not delete global-namespace buckets that clients may still call | Bucket inventory review |
Choosing between options
- SSE-S3 or SSE-KMS: SSE-S3 protects against lost disks and costs nothing extra. Choose SSE-KMS with a customer managed key when you need a second authorisation check (the reader also needs
kms:Decrypt), key-level CloudTrail evidence, or the ability to cut off access by disabling the key. The AWS managed keyaws/s3cannot be shared across accounts and its key policy cannot be edited. Use a full key ARN, not an alias, in cross-account writes. See the AWS KMS Security Guide. - Governance or compliance mode: governance mode lets holders of
s3:BypassGovernanceRetentionoverride retention, which suits testing and operational data. Compliance mode cannot be shortened or removed by anyone, including root, until it expires, so use it for regulated records and backup copies, and pilot retention periods in governance mode first. - Small or large estate: a single account can set account-level Block Public Access and a handful of Config rules. A multi-account organisation should manage Block Public Access, SCPs and RCPs centrally and send findings to a delegated security account. See the AWS Multi-Account Security Reference Architecture Guide.
Rollout in phases
- Inventory: list every bucket per account and Region with its Block Public Access state, Object Ownership, policy, encryption and logging. Use IAM Access Analyzer to list external and public sharing, and Macie to rank buckets by data sensitivity.
- Agree exceptions: the few buckets that are intentionally public (static sites are better served through CloudFront with origin access control) get an owner, a reason and a review date.
- Disable ACLs: for buckets still on Object writer, review bucket and object ACLs, move needed grants into the bucket policy, then switch to Bucket owner enforced. Watch for
AccessControlListNotSupportederrors from clients that still send ACLs. Rollback is switching the setting back, which restores the earlier ACLs. - Enforce Block Public Access: apply at account level in a pilot OU first, watch for broken access over one or two weeks, then attach the organisation policy to the root and add an SCP that denies changes to it.
- Add deny statements: TLS-only, required encryption and, where relevant, VPC endpoint restrictions. Deploy through infrastructure as code with a tested recovery path for lockouts.
- Turn on detection and resilience: GuardDuty S3 Protection, data events on sensitive buckets, versioning everywhere critical, and Object Lock on log and backup buckets.
Framework mapping
| Control area | NIST CSF 2.0 | NIST SP 800-53 Rev. 5 | CIS Controls v8.1 | ISO/IEC 27001:2022 Annex A |
|---|---|---|---|---|
| Bucket policies, BPA, least privilege | PR.AA-05 | AC-3, AC-6 | 3.3 | A.8.3 |
| Encryption at rest and in transit | PR.DS-01, PR.DS-02 | SC-28, SC-8, SC-12 | 3.10, 3.11 | A.8.24 |
| Logging and monitoring | PR.PS-04, DE.CM-09 | AU-2, AU-12, SI-4 | 8.2 | A.8.15, A.8.16 |
| Versioning, Object Lock, replication | PR.DS-11 | CP-9 | 11.3 | A.8.13 |
| Configuration baseline | PR.PS-01 | CM-2, CM-6 | 4.1 | A.8.9 |
The ATT&CK techniques most relevant to S3 are T1530 Data from Cloud Storage, T1619 Cloud Storage Object Discovery, T1537 Transfer Data to Cloud Account, T1485 Data Destruction and T1486 Data Encrypted for Impact, usually preceded by T1078.004 Cloud Accounts or T1552.005 Cloud Instance Metadata API.
Common misconfigurations
- Turning off Block Public Access "temporarily": every old public ACL and policy becomes live again the moment the setting is removed. Make the organisation policy the only control point.
- "Authenticated users" grants: legacy ACLs granting
AuthenticatedUsersare open to any AWS customer, not to your staff. - Wildcard principals with a weak condition:
"Principal": "*"with a broadaws:SourceIprange, a policy variable or a wildcard in the condition is still public by S3's definition. - Over-scoped workload roles: roles with
s3:*on every bucket turn a single SSRF or leaked key into a full data breach, as Capital One showed. - Versioning without lock, or no versioning at all: an attacker with delete rights can remove noncurrent versions too; only Object Lock or a replica in a separately controlled account withstands that.
- Logs in the same blast radius: CloudTrail and access logs stored in the production account, writable by production administrators, can be deleted by the same compromised identity. Send them to a log archive account with Object Lock. See the AWS CloudTrail Guide.
Practitioner checklist
- Enable all four Block Public Access settings as an organisation policy or on every account, and deny changes to them with an SCP.
- Set Object Ownership to Bucket owner enforced on every bucket after migrating any needed ACL grants.
- Add TLS-only and required-encryption deny statements to every bucket policy through infrastructure as code.
- Use SSE-KMS with customer managed keys for sensitive buckets and keep SSE-C disabled unless documented.
- Replace IAM user access keys with roles, OIDC federation or IAM Roles Anywhere, and scope each role to bucket and prefix.
- Limit cross-account access with
aws:PrincipalOrgIDor an RCP, and restrict VPC endpoints to your organisation's buckets. - Enable GuardDuty S3 Protection, and CloudTrail data events or server access logs on sensitive buckets.
- Alert on bucket policy, ACL, Block Public Access, lifecycle, replication and encryption changes made outside the pipeline.
- Turn on versioning for critical buckets and Object Lock for logs and backups, and test a restore each quarter.
- Review IAM Access Analyzer external access findings weekly and record every intentional public or shared bucket with an owner.
Standards and references
- AWS: Security best practices for Amazon S3
- AWS: Blocking public access to your Amazon S3 storage
- AWS: Controlling ownership of objects and disabling ACLs for your bucket
- AWS: Locking objects with Object Lock
- AWS: Using server-side encryption with AWS KMS keys (SSE-KMS)
- AWS: Controlling access from VPC endpoints with bucket policies
- AWS: Amazon GuardDuty S3 Protection
- MITRE ATT&CK: Data from Cloud Storage
Related NHI Mgmt Group resources: AWS Security Guide · Cloud Storage Exposure Playbook · AWS IAM Policies and Roles Guide · Cloud Ransomware Guide