Safeguard
Cloud Security

AWS secure REST API vs. S3: where shared responsibility actually splits

S3 buckets are private by default — every public leak is a customer misconfiguration. Capital One's 2019 breach of 106 million records proves the boundary.

Safeguard Research Team
Research
Updated 7 min read

On March 22–23, 2019, an attacker exploited a misconfigured web application firewall running on EC2 to perform a server-side request forgery (SSRF) against the instance metadata service, retrieved temporary IAM credentials scoped far more broadly than the WAF needed, and used those credentials to call the S3 API directly — exfiltrating roughly 106 million Capital One customer records before the breach was discovered on July 19, 2019 and disclosed ten days later. Nothing about that chain involved a flaw in AWS's infrastructure. S3 buckets are private by default; API Gateway ships with no implicit trust for anonymous callers; IAM roles are opt-in and scoped by the customer who writes the policy. Every step Capital One's attacker took was possible because of decisions made on the customer side of AWS's shared responsibility model — the line separating what AWS secures "of the cloud" from what the account owner must secure "in the cloud." Six years later, the same boundary still trips up teams building REST APIs on API Gateway backed by S3 storage. This post maps that boundary precisely, section by section, and ends with the audit checklist that would have caught Capital One's misconfiguration before an attacker did.

What does AWS actually secure on S3 and API Gateway?

AWS's documented shared responsibility model draws the line at the infrastructure layer: AWS is responsible for the physical data centers, host operating systems, hypervisor layer, and the durability and availability of the managed service itself. For S3, that means AWS guarantees the storage layer's 99.999999999% durability design and patches the underlying host software; it does not decide who can read your bucket. For API Gateway, AWS operates the endpoint infrastructure, handles TLS termination, and scales the service to handle traffic — but it does not choose your authorization model. AWS's own S3 security documentation is explicit that bucket policies, ACLs, Block Public Access settings, and object-level permissions are entirely customer-configured. There is no AWS default that opens a bucket to the internet; every public S3 exposure incident starts with a customer action, not a platform gap.

Why are S3 data leaks always a customer-side failure?

S3 buckets are private by default at creation, and AWS has spent years making that default harder to override by accident — Block Public Access, introduced in 2018, sits as an account-level and bucket-level circuit breaker that overrides even an explicit public bucket policy unless someone deliberately disables it. That means every "S3 bucket left open to the internet" headline requires at least two separate customer actions: turning off Block Public Access, and then attaching a policy or ACL that grants public read or write. AWS is not silently defaulting anything to public; the exposure is a deliberate, if often careless, configuration change. This is precisely why the shared responsibility model matters as a diagnostic tool, not just a compliance checkbox — when a bucket leaks, the question is never "did AWS's storage layer fail," it's "which customer-managed control was misconfigured or missing," whether that's a wildcard resource in a bucket policy, a forgotten public ACL from a migration script, or an IAM role with s3:* attached for convenience during a hackathon that never got scoped back down.

What does the customer own on the API Gateway side?

API Gateway gives you the scaffolding for a REST or HTTP API — routes, stages, deployment, throttling defaults — but authorization is opt-in and the customer's design choice. Out of the box, a newly created API Gateway resource with no authorizer attached and a permissive resource policy is reachable by anyone who finds the endpoint URL. Securing it means the customer must explicitly configure one of IAM authorization, a Lambda authorizer, or a Cognito user pool authorizer; attach resource policies that restrict source VPCs or IP ranges where appropriate; set usage plans and throttling to blunt abuse; and turn on request validation so malformed payloads never reach backend Lambda or S3 integrations. None of this is defaulted to "secure" by AWS because API Gateway has no way to know your intended trust boundary — a public marketing API and an internal admin API look identical to the service until the customer tells it otherwise through configuration.

How exactly did the Capital One breach cross the shared responsibility line?

The root cause chain, as reconstructed by security researchers and reported by journalist Brian Krebs shortly after disclosure, ran through three customer-side decisions rather than an AWS platform vulnerability. First, an EC2-hosted WAF was configured with an IAM role carrying broader S3 permissions than the WAF's function required. Second, that WAF had an SSRF vulnerability that let a crafted request coerce it into querying the EC2 instance metadata service (IMDS) and returning the role's temporary security credentials as plaintext output. Third, IMDSv1 — the version in use at the time — had no built-in defense against SSRF reaching it, because it never required a session token to answer a metadata request. With valid temporary credentials in hand, the attacker simply called the S3 API like any authorized client and listed, then copied, the contents of over 700 buckets. AWS's infrastructure performed exactly as designed at every step; the failure was excessive IAM scope plus an unpatched SSRF plus a metadata service that trusted any caller on the box.

What closes the exact gap Capital One's attacker used?

IMDSv2, which AWS introduced in late 2019 and can now be enforced account-wide, requires a session-oriented PUT request with a custom header to obtain a token before any metadata GET request succeeds — a design that specifically defeats simple SSRF-based credential theft, since most SSRF primitives only support basic GET requests through a vulnerable proxy. Enforcing IMDSv2 (setting the metadata hop limit and requiring tokens) is now a customer-configurable, off-by-default setting on every EC2 instance and Auto Scaling launch template. Pairing that with least-privilege IAM roles — scoping policies to specific bucket ARNs and actions instead of s3:* on Resource: "*" — means that even a successful SSRF against a compromised instance returns credentials that can't reach unrelated buckets. Both controls are entirely customer-side to configure and enforce: AWS has since made IMDSv2-only the default for newly released instance types and added an account-level setting to default all new launches to IMDSv2, but neither change retroactively enforces IMDSv2 on instances or launch templates that existed before those defaults shipped — enforcing it there still requires the customer to act.

Misconfiguration audit checklist for S3-backed REST APIs

Run this against every account before you assume S3 and API Gateway are locked down:

  • S3 Block Public Access enabled at both the account level and every individual bucket — no exceptions without a documented business reason.
  • Bucket policy least-privilege review: no "Principal": "*" combined with "Action": "s3:*"; scope to specific roles and specific object prefixes.
  • Default encryption enabled on every bucket (SSE-S3 or SSE-KMS), so any object written without explicit encryption headers is still encrypted at rest.
  • Versioning + MFA delete on buckets holding data you can't afford to lose to accidental or malicious deletion.
  • Access logging and CloudTrail S3 data events turned on, so a GetObject spike from an unfamiliar principal is visible before 106 million records leave.
  • IAM role scoping: audit every role attached to compute or Lambda functions for s3:* on Resource: "*"; replace with named bucket ARNs and the minimum action set.
  • API Gateway authorizers and resource policies: confirm every route requires IAM auth, a Lambda authorizer, or Cognito — never ship a route with NONE in production.
  • IMDSv2 enforcement: set HttpTokens: required on every EC2 launch template and Auto Scaling group, closing the exact SSRF-to-credential path Capital One's attacker used.

Each of these sits squarely on the customer side of the line. AWS will keep the lights on and the durability guarantees intact; whether the data behind that API is actually private is a decision your team makes, one policy statement at a time.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.