Build1 publisher2 min readPublished
A CNAME left pointing at a deleted S3 bucket hands the subdomain to whoever registers the name first
In HackerOne 121461 a researcher created the missing bucket in their own AWS account and served files on a2.bime.io. The state that allowed it is a name sitting in the DNS zone with no matching bucket in the account.
The Engineer · Build desk

What happened
- A researcher found in 2016 that a2.bime.io pointed by CNAME to bimeio.s3.amazonaws.com, a bucket no AWS account owned, and created it in their own account so the subdomain served their files.
- S3 bucket names are globally unique and a deleted name is available to any account immediately, so whoever registers it first decides what the surviving CNAME resolves to.
- The takeover ran on three commands and no credentials: dig the record, aws s3 ls to confirm NoSuchBucket, then aws s3 mb to register the name.
- The report is HackerOne 121461, and the fix was either claiming the bucket name or deleting the CNAME, each of which takes under a minute.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure The writeup says the files came up under Bime's domain with Bime's SSL certificate, so a browser gave a stranger's content the same standing as the company's own pages.
- decision Anyone adopting this control has to pick a max-unsafe window shorter than the time an attacker needs to register a name, and that choice is really a decision about how fast the finding queue gets worked.
- cost Enforcing the invariant means building a join between every zone's CNAMEs and your own account inventory, and that pipeline, not the one-minute remediation, is what the control costs to adopt.
- capability Because the mismatch shows up in a snapshot, an auditor can report the exposure without registering the bucket or touching production.
The record gets left behind for administrative reasons. A team ships a feature on S3, sets the CNAME, and later deprecates the feature. Deleting the bucket happens in the AWS console. Removing the CNAME happens at the DNS provider, a different system and often a different team, and that task does not block the deployment, so it gets forgotten [13].
After the delete, nothing in the account looks wrong, because the bucket that would have been scanned is gone from the inventory [9]. The DNS record is not an AWS resource, so it does not appear in AWS Config or Security Hub [10]. The endpoint keeps answering: NoSuchBucket is a valid HTTP response, and monitoring does not alert on it [11]. According to the dev.to writeup, the organisation has no tool that cross-references DNS records against S3 bucket ownership [12].
Stave's design makes the reference itself the asset. The type is s3_bucket_reference, and its properties are the endpoint, the bucket name, bucket_exists and bucket_owned; in the Bime case both booleans are false [16]. The control, CTL.S3.BUCKET.TAKEOVER.001, evaluates that reference instead of the bucket, and its unsafe_predicate is an any over two fields: bucket_exists eq false, bucket_owned eq false [17]. Either condition alone fires. A bucket that exists in an account you do not own is therefore also non-compliant, which covers the state after a takeover and not only the window before one. The invariant being enforced is that every CNAME pointing at an S3 endpoint references a bucket that exists and belongs to the same organisation [14].
The numbers come from the end-to-end test. It feeds two snapshots spanning 8 days, runs stave apply with --max-unsafe 168h and --eval-time 2016-03-18T00:00:00Z, and compares the output byte-for-byte against a golden file [18]. The expected line is "Unsafe for 192 hours (threshold: 168 hours)" [19]. That is eight days measured against a seven-day tolerance, so the finding clears the bar with 24 hours to spare [1]. The writeup puts the gap between the bucket being deleted and the researcher claiming it at days [21].
What the test demonstrates is that the engine emits the expected finding for a reconstructed 2016 configuration, one of 28 end-to-end cases in the suite [20]. For that to say anything about your zones, the two booleans have to be populated from your zones. The writeup describes the deploy-and-forget sequence as common and does not count how often it happens [13].
What to watch
- Whether Stave ships a collector that fills bucket_exists and bucket_owned from live zones, or whether the fixture stays hand-built.
- Whether takeover-class controls ship with a max-unsafe default below 168 hours.
- Whether Route53 or Cloudflare begin flagging zone records that resolve to unclaimed S3 endpoints.