🚀 Executive Summary
TL;DR: S3-hosted static websites are vulnerable to costly DDoS attacks when attackers bypass CloudFront and hit the public S3 bucket directly, leading to massive billing spikes. The definitive fix involves implementing AWS Origin Access Control (OAC) to restrict S3 bucket access exclusively to CloudFront, ensuring all traffic is filtered and cached at the edge.
🎯 Key Takeaways
- Direct public access to S3 buckets, even when fronted by CloudFront, creates a critical vulnerability allowing DDoS attacks to bypass edge protection and incur significant data transfer costs.
- Origin Access Control (OAC) is the recommended, permanent solution to secure S3 static sites by making the bucket private and granting access only to a specific CloudFront distribution.
- AWS WAF IP sets can provide a temporary, reactive defense against active DDoS attacks by blocking malicious IPs at the CloudFront edge, but they do not prevent direct S3 bucket access.
- Implementing custom headers or functions for S3 access control is generally an over-engineered solution for static sites, as OAC offers a simpler, more effective, and less costly approach.
Protect your S3-hosted static website from costly DDoS attacks by locking down direct bucket access and forcing traffic through CloudFront with AWS WAF and Origin Access Control (OAC).
So, Your S3 Bill Just Exploded. Let’s Talk About DDoS on Static Sites.
I still remember the PagerDuty alert. It was 3:17 AM on a Tuesday. The alert wasn’t for a downed server or a failed deployment—it was from our billing alarm. The “Estimated S3 Data Transfer” cost had shot up by something like 800% in a few hours. My first thought was a bug, maybe a runaway process in our `analytics-prod-pipeline` spitting out terabytes of logs. But no. The culprit was our marketing team’s “simple” static website. A site hosted in S3, fronted by CloudFront, that was now the target of a massive, albeit unsophisticated, DDoS attack. The attackers weren’t even hitting our CloudFront distribution; they were hammering the S3 bucket’s public endpoint directly, bypassing all our expensive WAF rules and caching. It was a painful, expensive, and frankly, embarrassing lesson in cloud security fundamentals.
The “Why”: The Original Sin of Public S3 Buckets
We’ve all done it. You spin up a static site, you need it to be public, so you go into the S3 console, click through the permissions, and hit that big orange button that says “Block all public access (off)”. You hook it up to CloudFront for a CDN and think you’re safe. But here’s the rub: if your bucket is public, it’s public. The CloudFront distribution is just one way to get to your files. The direct S3 URL (e.g., my-awesome-site.s3.us-east-1.amazonaws.com) is still wide open for anyone—or any botnet—to hit directly. Every single one of those direct requests is a cache miss and a billable S3 `GET` operation. That’s how a “cheap” static site can suddenly cost you a fortune.
So, how do we fix it? We need to make sure the only entity that can access our S3 bucket is our CloudFront distribution itself. Here are a few ways to tackle it, from a quick patch to the architecturally sound solution.
Solution 1: The Quick Fix (AWS WAF IP Set)
Let’s say you’re in the middle of an attack like I was. You need to stop the bleeding, fast. The quickest, dirtiest way is to use AWS WAF to block the offending IP addresses. You can create an IP set in WAF and associate it with your CloudFront distribution. This stops the traffic at the edge, which is good.
Warning: This is a temporary solution, a digital band-aid. Attackers can easily switch IP addresses, and you’ll be stuck playing a very stressful game of whack-a-mole. This also does nothing to block direct S3 access, it only protects your CloudFront endpoint.
If you need to do this, here’s what a basic WAF rule looks like in JSON. You’d create an IP set called `MaliciousIPSet` and then reference it.
{
"Name": "BlockMaliciousIPs",
"Priority": 1,
"Action": {
"Block": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "BlockMaliciousIPs"
},
"Statement": {
"IPSetReferenceStatement": {
"ARN": "arn:aws:wafv2:us-east-1:123456789012:global/ipset/MaliciousIPSet/a1b2c3d4-e5f6-7890-1234-567890abcdef"
}
}
}
Again, use this to stop an active incident, but immediately move on to a real fix.
Solution 2: The Permanent Fix (Origin Access Control)
This is the way. This is what we should have done from the start. AWS created Origin Access Control (OAC) to solve this exact problem, replacing the older and clunkier Origin Access Identity (OAI). The concept is simple: you make your S3 bucket private and then create a special policy that says, “Only this specific CloudFront distribution is allowed to access my objects.”
Here’s the playbook:
- Turn off public access: Go to your S3 bucket’s “Permissions” tab and turn “Block all public access” ON. All green checks. Your site will immediately break. Don’t panic.
- Create an Origin Access Control setting: In the CloudFront console, navigate to “Origin access” and create a new control setting. The defaults are usually fine.
- Update your CloudFront Origin: Edit the S3 origin in your distribution. Instead of “Public,” select your newly created OAC setting. CloudFront will give you a bucket policy to copy.
- Apply the S3 Bucket Policy: Go back to your S3 bucket’s permissions and paste the policy CloudFront gave you. It will look something like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-awesome-site-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E123ABCDEF4567"
}
}
}
]
}
Once you save this policy, the magic happens. Your S3 bucket is now locked down from the entire world… except for your CloudFront distribution. Problem solved. The attackers can hit your old S3 URL all day long and all they’ll get is a 403 Access Denied error, which costs you nothing.
Solution 3: The ‘Nuclear’ Option (Custom Headers & Functions)
Okay, sometimes you have a more complex setup. Maybe you’re using a third-party CDN or have some other reason you can’t use OAC directly. In this case, you can roll your own authentication mechanism using a secret custom header.
The idea is to configure your CloudFront distribution to add a custom header to all requests it sends to the S3 origin. Then, you can use a Lambda@Edge or a CloudFront Function to check for the presence and value of this header. However, S3 bucket policies can’t inspect headers. So, you’d have to put your S3 bucket behind an API Gateway endpoint or an Application Load Balancer with a Lambda authorizer. This is getting complicated, fast.
Pro Tip: I’m mentioning this for completeness, but for a standard static site, this is massive overkill. 99% of the time, OAC is the answer. Going down this path adds complexity, latency, and cost. Only consider it if you have a truly bizarre edge case that OAC doesn’t solve.
This approach transforms your simple S3 origin into a complex, multi-service architecture just to validate a header. Stick with OAC unless your job title is “Principal Over-Engineer”.
Choosing Your Weapon
To make it simple, here’s how I see the options:
| Solution | Complexity | Effectiveness | When to Use It |
| AWS WAF IP Set | Low | Low (Temporary) | You are under attack RIGHT NOW and need to stop the bleeding. |
| Origin Access Control (OAC) | Medium | High (Permanent) | This is the default, correct solution for ALL S3 static sites. |
| Custom Headers / Functions | Very High | High (But complex) | You have a specific, non-standard architecture that prevents OAC usage. |
Don’t wait for that 3 AM billing alert. If you have a static site on S3, go check its permissions right now. Lock it down with OAC, force all traffic through CloudFront, and sleep soundly knowing you’ve closed a simple but potentially very expensive security hole.
🤖 Frequently Asked Questions
âť“ What is the primary security flaw in S3-hosted static websites that leads to DDoS attacks?
The ‘Original Sin of Public S3 Buckets’ allows direct access to the S3 bucket’s public endpoint, bypassing CloudFront’s WAF and caching, making it susceptible to direct DDoS attacks and inflated S3 `GET` operation bills.
âť“ How does Origin Access Control (OAC) secure an S3-hosted static website?
OAC secures the site by making the S3 bucket private (‘Block all public access’ ON) and then creating a specific bucket policy that permits access only from a designated CloudFront distribution, effectively blocking all direct S3 requests.
âť“ When should AWS WAF IP sets be used for S3 static site protection, and what are their limitations?
AWS WAF IP sets are a quick, temporary fix for active DDoS incidents to block known malicious IPs at the CloudFront edge. Their limitation is that they do not prevent direct S3 bucket access and require constant updates as attacker IPs change.
Leave a Reply