# Blocky DNSSEC validation bypass and validation-cache scope pollution

Blocky's DNSSEC validator treats unsigned upstream answers and unsigned NSEC/NSEC3 DS proofs as Insecure, letting a malicious upstream poison the DNS cache for signed domains.

- Payout: Research
- Severity: high
- Program: Blocky
- Found by: RealHurrison
- Disclosed: 2026-06-19
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-x845-2f78-7v36
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-x845-2f78-7v36
- Tags: area:devices domain:network impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:go technique:chained technique:source-review vuln:auth-bypass vuln:cache-poisoning vuln:misconfiguration

**Target:** DNS resolver (Blocky DNSSEC validator and caching resolver)
**Root cause:** ValidateResponse classifies any response lacking RRSIG as Insecure without first proving the name sits under an insecure delegation; handleNoDSRecords trusts mere presence of unsigned NSEC/NSEC3 as authenticated DS absence. Validation cache is keyed only by bare domain name and response cache writes occur before outer DNSSEC validation.
**Steps:**
1. Configure Blocky with `dnssec.validate: true` and a positive caching maxTime, pointing upstream at attacker-controlled resolver.
2. Query `cloudflare.com. A`; upstream returns unsigned `cloudflare.com. 120 IN A 203.0.113.77` with no RRSIG.
3. Blocky sees no RRSIG, returns ValidationResultInsecure without issuing DS/DNSKEY queries, returns and caches forged answer.
4. Shut down malicious upstream; second query still returns forged answer from cache.
5. For path 2: return poisoned A RR plus unrelated decoy RRSIG (type-covered=TXT) to force RRset validation.
6. Answer DS query with no DS records plus unsigned NSEC in authority section; Blocky caches Insecure for bare domain and accepts unsigned A RRset.
7. Replay poisoned answer from cache after upstream shutdown.
**How found:** Code review of resolver/dnssec/validator.go dispatch: noticed `!v.hasAnySignatures(response)` short-circuits to Insecure before chain status is checked, and handleNoDSRecords only checks for NSEC/NSEC3 presence rather than validating the denial proof. Also noticed resolver chain ordering means CachingResolver writes before DNSSEC validation returns, and validation cache key omits qtype/view/upstream context.
**Exploitation:** Attacker controls or tampers with the recursive upstream (or a conditional-forwarding/split-horizon view). Forged unsigned positive answers or forged DS/NSEC denial proofs are accepted as Insecure, cached, and replayed to later clients for the cache TTL, redirecting traffic to attacker infrastructure.
**Escalation:** Validation-cache scope pollution lets one response path or proof purpose seed Insecure state reused across views, upstream groups, and conditional-forwarding branches; also can induce false Bogus/Indeterminate states causing targeted SERVFAIL or AD-bit stripping.
**Insight:** In validating resolvers, 'no RRSIG' must never imply insecure — always walk DS/DNSKEY to the trust anchor first; and denial-of-existence proofs (NSEC/NSEC3) must be signature-validated, not just detected. Check resolver chain ordering: inner caching layers that write before outer validation returns defeat the validator entirely.

## Proof of concept (copied exactly from the original report)

**PoC 1 run command for basic bypass**

```
cd /home/hurrison/workspace/dnssec/repos/blocky
go run ./exp/external-dnssec-basic-bypass
```

**PoC 2 run command for cache-scope pollution**

```
cd /home/hurrison/workspace/dnssec/repos/blocky
go run ./exp/external-dnssec-cache-scope-pollution
```

**Forged unsigned A answer for signed domain**

```
cloudflare.com. 120 IN A 203.0.113.77
```

