The government domain pointing at a name up for grabs
Metadata
- Title: The government domain pointing at a name up for grabs
- Source of the case: HackerOne report — DoD #918946
- AWS service(s): S3, DNS (CNAME)
- Risk archetype: Ghost reference under a high-trust domain
- One-line hook: Can you prove this
.govsubdomain cannot be served by an attacker?
0. The challenge (what the reader does first)
Scenario given to the reader:
A government program hosted a data portal subdomain on S3. The bucket behind data.defense.gov was decommissioned, but the DNS record stayed in the zone file. The page now returns an error. The zone file has dozens of records like it, accumulated over years of projects starting and ending.
Evidence they're handed (and nothing else):
{
"dns_record": {"name": "data.defense.gov", "type": "CNAME", "value": "data.defense.gov.s3.amazonaws.com"},
"s3_bucket_exists": false,
"http_response": {"status": 404, "body": "NoSuchBucket"}
}
- The DNS record, the fact that the bucket no longer exists, and the HTTP response above.
- No AWS credentials. No live account. No scripts.
The questions they must answer from the evidence alone:
- What is the state now — what does a visitor to
data.defense.govsee, and is it currently harmful? - This is a harmless 404 today — so what is the latent risk the moment anyone creates an S3 bucket named
data.defense.gov, and why is the blast radius larger under a.govthan a commercial domain? - Which exposure comes from the DNS path — the CNAME still resolving to S3?
- Which exposure comes from the storage path — the bucket name being unowned and globally claimable?
- What single rule would have prevented this — a constraint binding DNS records to resources the account owns?
1. The manual problem
The page is a 404. On its face there is nothing to fix. But this is a .gov domain, and the trust attached to it is the whole problem. Browsers, mail filters, link previewers, and humans treat .gov as authoritative; many allowlists explicitly trust it.
To see the risk you must reason about a state that does not exist yet. The CNAME still resolves to the S3 service. The named bucket is gone, so the name is now free for anyone in any AWS account to create. The moment an attacker creates data.defense.gov as their own bucket, the live record resolves to their content — served under a government domain that downstream systems trust by default. Malware and phishing hosted there sail past filters that would block an unknown domain.
Doing this by hand means joining a DNS fact (record resolves) to an S3 fact (bucket gone) across two systems, then reasoning forward to an attacker action — and then weighting it by the trust the domain carries. None of that is visible in the current state.
2. The reasoning wall (capture, don't invent)
| What they hit | What they said / would say |
|---|---|
| The current response is a benign 404 | "It returns NoSuchBucket. There's nothing live there to exploit." |
The risk is a future claim, amplified by .gov trust | "If someone grabs that bucket name, it's serving from a government domain — filters won't stop it." |
| DNS and S3 checks each report clean | "DNS says it resolves fine; the S3 scan has no bucket to inspect. Both pass." |
The insight the reader should reach on their own:
A dangling record under a high-trust domain is not a dead link — it is a pre-authorized phishing channel waiting for the first attacker to claim the name.
3. Why scanners miss or flatten it
A DNS scanner confirms data.defense.gov resolves and the CNAME is well-formed — healthy. An S3 scanner has no bucket to enumerate, so it emits nothing. Each tool, looking at one node, reports a clean state.
What neither sees is the combination: a live, resolving record whose target is a globally claimable name, under a domain whose trust increases the impact of takeover. The dangerous fact — record points to name + name is unowned + namespace is open + domain is high-trust — exists only as the edge between the records, and as context the scanner has no field for. A per-setting tool cannot represent "this reference can be hijacked by a third party, and the hijack inherits government trust," so it flattens the worst exposure in the zone to two passing checks.
Pivot point. Everything above is the gap. Everything below is Stave filling it. The reader has now done the work and hit the wall. Only now does the tool appear.
4. The evidence Stave consumes
The same static facts the reader had — no live cloud, no credentials:
{
"dns_record": {"name": "data.defense.gov", "type": "CNAME", "value": "data.defense.gov.s3.amazonaws.com"},
"s3_bucket_exists": false,
"http_response": {"status": 404, "body": "NoSuchBucket"}
}
- Normalized into an
obs.v0.1snapshot: the DNS record is an asset whose CNAME target is correlated against the S3 bucket inventory, where the referenced bucket is absent.
5. The reasoning Stave performs
- Control / invariant:
CTL.DNS.DANGLING.001— a DNS record targeting an AWS resource must point at a resource that exists and is owned by the account. Paired withCTL.S3.BUCKET.TAKEOVER.001— a referenced S3 bucket must exist. - What it evaluates: the predicate fails when a CNAME resolves to an S3 endpoint (DNS path) while the named bucket does not exist (storage path), leaving the name claimable by a third party — both paths from section 0 in one verdict.
- Verdict produced: NON_COMPLIANT. The record resolves but the target name is unowned and claimable; under a
.govdomain the takeover inherits government trust.
control: CTL.DNS.DANGLING.001
asset: data.defense.gov (CNAME -> data.defense.gov.s3.amazonaws.com)
evidence: CNAME live; s3_bucket_exists=false; bucket name globally claimable
verdict: NON_COMPLIANT — government subdomain points to an unclaimed bucket
control: CTL.S3.BUCKET.TAKEOVER.001
asset: data.defense.gov.s3.amazonaws.com
evidence: referenced bucket does not exist (http 404 NoSuchBucket)
verdict: NON_COMPLIANT — referenced bucket missing; name available to attacker
6. The prevention artifact Stave produces
- Artifact: a guardrail / SCP that requires every DNS record targeting an S3 bucket to reference a bucket owned by the account, and refuses CNAME creation toward an unowned bucket name.
- What it forecloses: the latent state from question 2 — no dangling record can sit in the
.govzone waiting to be hijacked, because a record pointing at an unowned bucket name is rejected (and the existing one is surfaced for removal or re-claim).
# Guardrail: DNS records to S3 must target a bucket this account owns.
rule require_owned_bucket_for_s3_cname:
for each dns_record where target matches "*.s3*.amazonaws.com":
assert s3_bucket(target).exists AND s3_bucket(target).owner == self.account
else: BLOCK "CNAME targets an S3 bucket not owned by this account"
# SCP companion: require bucket ownership before the name is wired into DNS.
{
"Sid": "NoCnameToUnownedBucket",
"Effect": "Deny",
"Action": "route53:ChangeResourceRecordSets",
"Resource": "*",
"Condition": { "Null": { "aws:ResourceTag/s3-bucket-owned": "true" } }
}
# Manual fix for the record in this case (do one, not neither):
# - Delete the dangling CNAME data.defense.gov, OR
# - Re-create the S3 bucket data.defense.gov in this account to re-claim the name.
7. What the team no longer does manually
| Before | After Stave |
|---|---|
Decide whether a 404 .gov subdomain is cosmetic or a high-trust takeover risk | A control proves the reference is claimable and emits NON_COMPLIANT |
| Audit a sprawling zone file against the live S3 inventory by hand | The correlation runs deterministically from a snapshot |
Hope no future bucket-name claim turns a dead .gov link into a phishing host | A guardrail rejects records pointing at unowned bucket names |
Positioning line for this case
Stave proves that a live
.govCNAME pointing at a deleted, globally claimable S3 bucket is a high-trust takeover primitive — not a broken link — and emits the guardrail that forbids records targeting names you do not own.
Reuse checklist
- A reader could attempt section 0 with zero Stave knowledge
- Stave is not named or shown before the pivot point
- Section 2 quotes are real (or honestly plausible), not slogans
- Section 3 names the specific thing per-setting tools can't see
- Section 6 closes the exact latent state raised in section 0, question 2
- The title names the failure, not the product