Dangling S3 Bucket References: Reclaiming Hijackable Cloud Storage Names
You deleted an S3 bucket six months ago when a project wound down. The name is gone from your AWS console, but it still lives in a CloudFront distribution, a CNAME record, and three lines of JavaScript your frontend ships to every user. An attacker can register that exact bucket name right now, make it public, and start serving whatever they want under your domain.
This is not a theoretical edge case. Bucket name hijacking follows the same playbook as subdomain takeover via dangling DNS records β and it is just as exploitable. The fix is equally straightforward once you know where to look.
What Is a Dangling S3 Bucket Reference?
A dangling S3 bucket reference is any pointer in your infrastructure, code, or DNS that still resolves to an S3 bucket name that no longer exists under your AWS account. The pointer itself keeps working at the network level: AWS S3 bucket names are globally unique, so the hostname my-deleted-bucket.s3.amazonaws.com still resolves to a valid S3 endpoint. If someone else claims that name, they own the destination of your pointer.
Unlike most misconfigurations, this one requires no privileged access to your account. The attacker just needs to create a bucket with the freed-up name.
What you'll learn
- Why S3 bucket deletion leaves exploitable residue in your DNS, CDN, and code
- The exact paths attackers use to discover and claim freed bucket names
- How to audit your infrastructure systematically for every dangling reference
- How to reclaim a name and lock it down so it cannot be reused against you
- Preventive controls to keep new dangling references from accumulating
Why Deleted Buckets Stay Dangerous
When you delete an S3 bucket, AWS releases the name back into the global pool immediately. There is no holding period. Any AWS account β yours, a competitor's, or a threat actor's β can register that name the moment the API call completes.
Your infrastructure doesn't know the bucket is gone. A CloudFront distribution with an S3 origin pointing to a deleted bucket will happily forward requests to whichever account now owns that name. A CNAME record pointing assets.example.com to example-assets.s3-website-us-east-1.amazonaws.com will resolve to the attacker's bucket. Static scripts loaded by your app from a hardcoded S3 URL will execute from the attacker's origin.
The blast radius depends on what your original bucket served. If it hosted JavaScript, an attacker can run arbitrary code in your users' browsers. If it hosted images or documents used in rendered HTML, they can replace those with phishing content. If it was a software distribution bucket, they can substitute malicious binaries. The common thread is that your users see traffic appearing to originate from infrastructure they already trust.
How Attackers Exploit Dangling Bucket Names
Discovery is the first step. Attackers enumerate candidate bucket names by scanning publicly accessible sources: JavaScript files served by your site, archived pages in the Wayback Machine, Certificate Transparency logs, leaked Terraform state files, and public GitHub repositories. Any hardcoded S3 URL or bucket name in your codebase is a lead.
Verification is cheap. A simple HTTP request to https://<bucket-name>.s3.amazonaws.com returns NoSuchBucket if the name is unclaimed. That is the green light. The attacker creates an AWS free-tier account, registers the bucket name in the same region your original bucket used (region matters for S3 website endpoints), enables static website hosting, and pushes their payload. The whole process takes under five minutes.
Exploitation varies by use case. The three most common outcomes are:
- Script injection: A bucket that hosted a JavaScript asset is reclaimed, and the attacker serves a modified version that exfiltrates session cookies or form data.
- Content spoofing: A bucket that served images or HTML fragments under a trusted domain now serves phishing content in that same visual context.
- Malware distribution: A bucket used as a software update or installer endpoint now delivers tampered binaries to users who trust your signing certificate.
The CORS implications are also worth noting. A reclaimed bucket can set permissive CORS headers that allow cross-origin reads from your domain, enabling data exfiltration attacks that look like they originate from your own infrastructure. This shares structural DNA with CORS misconfigurations that expose APIs to any origin β the trust boundary breaks in the same direction.
Where Dangling References Hide in Your Infrastructure
Before you can fix anything, you need a complete list of every place a bucket name appears. Dangling references cluster in predictable locations.
DNS records
CNAME records pointing to S3 website endpoints are the most dangerous. Check Route 53 (or your external DNS provider) for any CNAME whose value contains .s3.amazonaws.com, .s3-website, or .s3-accelerate. Each one is a potential takeover point if the target bucket no longer exists.
CloudFront distributions
CloudFront origins can reference S3 bucket domain names directly. An origin set to my-old-bucket.s3.amazonaws.com will route all matching requests to whichever account owns that bucket. Pull the list of all your CloudFront distributions and check each origin's domain name against your actual bucket inventory.
Application code and configuration files
Hardcoded S3 URLs in JavaScript, environment variable files, build configs, and server-side templates accumulate over years. A quick grep across your repositories is worth the five minutes it takes.
Infrastructure-as-code state and history
Terraform state files record every resource that was ever managed, including destroyed ones. A terraform state list against old workspaces or S3-backed state files can surface bucket names that were deleted but never cleaned out of other configurations. Similarly, CloudFormation stack exports can reference deleted bucket names if stacks were partially torn down.
Third-party and CDN configurations
If you use an external CDN, WAF, or static site service that proxies to S3, check those dashboards too. Origin configurations there are easy to forget after a migration.
Auditing Your Codebase and Infrastructure for Stale Bucket Names
Start with a grep pattern that catches common S3 URL shapes across your entire codebase and infrastructure repository:
grep -rE 's3(\.amazonaws\.com|://|\-website)' . \
--include='*.tf' \
--include='*.yaml' \
--include='*.yml' \
--include='*.json' \
--include='*.js' \
--include='*.ts' \
--include='*.env*' \
-l
That gives you a list of files to inspect. From there, extract the bucket names and check each one against your AWS account:
# Check if a bucket exists in your account
aws s3api head-bucket --bucket my-suspect-bucket 2>&1
# A 404 means it does not exist anywhere
# A 403 means it exists but belongs to another account β high risk
# No error means it is yours
A 403 Forbidden response to head-bucket is the most alarming result. It confirms the bucket name is registered but not in your account. That is an active takeover or a bucket owned by an unrelated party sitting in the path of your reference.
For DNS, query your Route 53 hosted zones programmatically:
aws route53 list-resource-record-sets \
--hosted-zone-id YOUR_ZONE_ID \
--query "ResourceRecordSets[?Type=='CNAME'].{Name:Name,Value:ResourceRecords[0].Value}" \
--output table | grep s3
For each CNAME pointing to an S3 endpoint, verify the bucket exists in your account before moving on.
Reclaiming the Name: Re-registering the Bucket
If you have confirmed a bucket name is unclaimed (the head-bucket call returns a 404), re-register it immediately in your own account, even if you have no plans to put content there. An empty, private bucket you own is infinitely better than a void that anyone can fill.
# Re-create the bucket in the same region it originally lived in
aws s3api create-bucket \
--bucket my-reclaimed-bucket \
--region us-east-1
# For regions other than us-east-1, you must specify a location constraint
aws s3api create-bucket \
--bucket my-reclaimed-bucket \
--region eu-west-1 \
--create-bucket-configuration LocationConstraint=eu-west-1
Region matters because S3 website endpoints are region-specific. If your original DNS CNAME points to my-bucket.s3-website-eu-west-1.amazonaws.com, an attacker creating the bucket in us-east-1 will not satisfy that hostname. But do not rely on this as a defense β create the bucket in the original region.
If the head-bucket call returns a 403, the name is already taken by another account. You cannot reclaim it directly. Your only recourse is to remove all references to it from your infrastructure immediately and rotate any content that was served from it, since you must treat that content as potentially compromised.
Locking Down the Reclaimed Bucket
Creating the bucket is only step one. You need to ensure it cannot be used against you even if someone manages to write to it or if your own access controls slip.
First, block all public access at the bucket level:
aws s3api put-public-access-block \
--bucket my-reclaimed-bucket \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Next, attach a restrictive bucket policy that denies all principals except your known roles:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllExceptTrustedRoles",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-reclaimed-bucket",
"arn:aws:s3:::my-reclaimed-bucket/*"
],
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": "arn:aws:iam::123456789012:role/AllowedDeployRole"
}
}
}
]
}
Enable versioning and server-side encryption as standard hygiene, even for a placeholder bucket. If the bucket ever needs to serve real content again, you want those controls already in place.
If the bucket previously served static website content and you need to keep serving it, consider migrating to CloudFront with an Origin Access Control policy instead of using an S3 website endpoint with a CNAME. This removes the public S3 hostname from the critical path entirely, eliminating the CNAME-based takeover surface.
Preventing Dangling References Going Forward
Audit and remediation address the debt you already have. Prevention stops new debt from accumulating.
Never delete a bucket without deleting its references first
Make it a hard policy: before running aws s3 rb or removing an S3 resource from Terraform, search for every reference to that bucket name across your DNS, CDN config, and codebase. A pre-destruction checklist in your runbook is the cheapest control you can add.
Use placeholder buckets for decommissioned names
When a project ends and a bucket's content is no longer needed, empty it and keep the bucket registered. The cost of an empty S3 bucket is effectively zero. The cost of losing control of its name can be substantial. This is especially important for bucket names that appeared in public documentation, blog posts, or open-source repositories.
Automate reference scanning in CI/CD
Add a step to your CI pipeline that extracts all S3 bucket names referenced in your codebase and infrastructure definitions, then checks each one with aws s3api head-bucket. Fail the build if any referenced bucket returns a 404 or belongs to a different account. This catches drift before it reaches production.
#!/usr/bin/env bash
# Minimal CI check β expand as needed
set -euo pipefail
BUCKETS=$(grep -rohE '[a-z0-9][a-z0-9\-]{2,62}(?=\.s3\.amazonaws\.com)' . | sort -u)
for bucket in $BUCKETS; do
result=$(aws s3api head-bucket --bucket "$bucket" 2>&1 || true)
if echo "$result" | grep -q '404'; then
echo "FAIL: Bucket '$bucket' does not exist β dangling reference detected"
exit 1
elif echo "$result" | grep -q '403'; then
echo "FAIL: Bucket '$bucket' is owned by another account β potential takeover"
exit 1
fi
done
echo "All referenced buckets verified."
Treat bucket names as secrets in your threat model
Bucket names that appear in public sources are attack surface. Prefer bucket names that are not guessable or discoverable from your public-facing application. This does not help with names already in the wild, but it limits future exposure. The same discipline applies to avoiding enumerable names in software distribution pipelines β a pattern directly relevant to supply chain attacks via compromised maintainers.
Common Pitfalls
Assuming the problem only affects old buckets. Buckets deleted during active development cycles are just as vulnerable. The bucket that served your staging environment's assets last quarter is fair game the moment it's deleted.
Only checking DNS and forgetting CloudFront origins. CloudFront origins are a separate attack surface. A distribution pointing to a deleted bucket will proxy all requests β including those made over HTTPS with your own certificate β to whoever owns the bucket name. Check both.
Relying on NoSuchBucket errors as a user-visible safeguard. The error is only user-visible if there is no CDN layer in between. If your CloudFront distribution caches responses, the attacker's content may propagate to your edge even if users never see a direct S3 URL.
Forgetting third-party integrations. If you ever gave a SaaS product an S3 bucket to write into or read from, and you later deleted that bucket, that product's configuration may now point at a name you no longer control. Review any integration that was ever granted cross-account access to your buckets.
Not checking git history. Bucket names that have been removed from your current codebase may still appear in git history or old branches. Run your grep against git log -p output if you suspect names have been scrubbed but are still discoverable from public repositories. This mirrors the discipline required when auditing file handling paths for hidden attack surfaces.
Wrapping Up: Next Steps
Dangling S3 bucket references are a quiet threat because the infrastructure appears to keep working even after the bucket is gone. Nothing breaks visibly until someone claims the name. Here is what to do right now:
- Run the grep audit against your codebase and infrastructure definitions today. Extract every bucket name and verify each with
head-bucket. - Check your DNS and CloudFront for CNAME records and origins pointing to S3 hostnames, and cross-reference against your live bucket inventory.
- Re-register any unclaimed names immediately, applying the public-access block and a deny-all policy before adding them to any other configuration.
- Add a CI/CD gate that fails on 404 or 403 responses for any S3 bucket name referenced in your repository.
- Update your decommission runbook to require reference cleanup and placeholder bucket retention as steps that must happen before a bucket is deleted.
The window between bucket deletion and bucket takeover can be seconds. The controls above collapse that window to zero by eliminating the name vacancy in the first place.
Frequently Asked Questions
Can an attacker actually take over my S3 bucket name after I delete it?
Yes. AWS releases a deleted bucket name back into the global pool immediately, and any AWS account can register it without restriction. If your infrastructure still references that name β through DNS, CloudFront, or hardcoded URLs β requests will be routed to whoever claims it.
How do I check whether a bucket name I reference is still owned by my account?
Run `aws s3api head-bucket --bucket your-bucket-name`. A 404 response means the bucket does not exist and the name is unclaimed. A 403 means the bucket exists but belongs to a different account, which is the most dangerous outcome.
What happens if the dangling bucket name is already registered by someone else?
You cannot reclaim it through normal means. Your immediate actions are to remove all references to that bucket from your DNS, CDN, and application code, and to treat any content previously served from it as potentially compromised until you can verify otherwise.
Do I need to worry about S3 bucket references in old git history?
Yes, if your repository is public or was ever public. Attackers routinely mine git history and archived web pages for S3 bucket names. Run your audit against git log output as well as your current working tree.
Is keeping an empty placeholder bucket a realistic long-term strategy?
For most organizations it is the simplest and cheapest defense. An empty, fully private S3 bucket costs essentially nothing to maintain, and it permanently closes the name-reclaim attack surface for that identifier.
π€ Share this article
Sign in to saveRelated Articles
Comments (0)
No comments yet. Be the first!