GHSA-63cw-r7xf-jmwr: CoreDNS DoH GET oversized dns= query parameter causes pre-validation CPU and memory amplification
SummaryCoreDNS's DNS-over-HTTPS (DoH) GET path accepts oversized `dns=` query values and performs substantial request parsing, query unescaping, base64 decoding, and message unpacking work before returning `400 Bad Request`.A remote, unauthenticated attacker can repeatedly send oversized DoH GET requests to `/dns-query?dns=...` and force high CPU usage, large transient allocations, elevated garbage-collection pressure, and increased resident memory consumption even though the requests are ultimately rejected.This is a denial-of-service issue caused by expensive pre-validation processing on the DoH GET path.DetailsThe vulnerable flow is in `plugin/pkg/doh/doh.go`:`RequestToMsg()` dispatches GET requests to `requestToMsgGet()`:`plugin/pkg/doh/doh.go:79-89``requestToMsgGet()` calls `req.URL.Query()`, extracts `dns`, and passes it directly to `base64ToMsg()`:`plugin/pkg/doh/doh.go:99-108``base64ToMsg()` decodes the full attacker-controlled value via `b64Enc.DecodeString()` and only then attempts to unpack it into a DNS message:`plugin/pkg/doh/doh.go:121-130`Relevant snippet:By contrast, the POST path applies a bounded read before unpacking:So, POST is explicitly size-bounded, while GET is not equivalently bounded before expensive parsing and decoding work occurs.In addition, the HTTPS server is created in `core/dnsserver/server_https.go:87-92` without an explicit early GET-path size guard in this path:As a result, oversized DoH GET request targets are processed through:HTTP request-line parsingURL query parsing / unescapingDoH GET extractionbase64 decodingDNS message unpackingbefore the request is rejected.Root causeThe root cause is missing early size validation on the DoH GET path.More specifically:`requestToMsgGet()` performs `req.URL.Query()` on attacker-controlled oversized request targets.The extracted `dns` value is passed to `base64ToMsg()` without an encoded-length or decoded-length bound.`base64ToMsg()` fully decodes the attacker-controlled string before any DNS-size rejection.The POST path already has an explicit bounded read, but GET does not have an equivalent pre-decode bound.This creates a pre-validation resource-amplification path for DoH GET.PoCLocal test setupI reproduced this locally against CoreDNS 1.14.2 over HTTPS with `pprof` enabled.Create a self-signed certificate:Create this `Corefile`:Run CoreDNS:Proof-of-concept scriptRun the PoC:Profiling commands used during reproductionCPU profile:Heap / allocation profiles:Reproduction resultsI confirmed the issue on:CoreDNS 1.14.2linux/amd64go1.26.1PoC payload characteristics:decoded payload size: `737,280 bytes`base64url-encoded `dns` length: `983,040`request-target length: `983,055`Observed request outcome:`5000 / 5000` requests returned `400 Bad Request`total runtime for the 5000-request run: `18.22s`The important point is that the requests are rejected only after expensive processing has already happened.CPU profile highlightsThe CPU profile captured during the attack showed significant time in:`net/http.readRequest``net/url.ParseQuery` / `net/url.QueryUnescape` / `net/url.unescape``github.com/coredns/coredns/plugin/pkg/doh.requestToMsgGet``github.com/coredns/coredns/plugin/pkg/doh.base64ToMsg``encoding/base64.(*Encoding).DecodeString`Go GC worker pathsRepresentative cumulative values from the captured profile included:`github.com/coredns/coredns/core/dnsserver.(*ServerHTTPS).ServeHTTP` → `10.91s``github.com/coredns/coredns/plugin/pkg/doh.RequestToMsg` → `10.88s``github.com/coredns/coredns/plugin/pkg/doh.requestToMsgGet` → `10.88s``github.com/coredns/coredns/plugin/pkg/doh.base64ToMsg` → `3.50s``encoding/base64.(*Encoding).DecodeString` → `3.46s``net/http.readRequest` → `10.57s``net/url.(*URL).Query` / `ParseQuery` / `QueryUnescape` → `7.38s``runtime.gcBgMarkWorker` and related GC paths were also heavily activeThis demonstrates that the issue is not limited to final DNS unpacking. The oversized GET request forces meaningful work in HTTP parsing, URL handling, base64 decoding, and garbage collection before rejection.Allocation profile highlightsAllocation profiling showed very large transient allocation volume caused by the rejected requests:total `alloc_space`: `26,756.48 MB`Top contributors included:`net/textproto.(*Reader).readLineSlice` → `19,668.19 MB``net/textproto.(*Reader).ReadLine` → `3,738.84 MB``encoding/base64.(*Encoding).DecodeString` → `2,766.16 MB`Within the CoreDNS DoH GET path specifically:`github.com/coredns/coredns/plugin/pkg/doh.RequestToMsg` → `2,775.67 MB``github.com/coredns/coredns/plugin/pkg/doh.requestToMsgGet` → `2,775.67 MB``github.com/coredns/coredns/plugin/pkg/doh.base64ToMsg` → `2,773.67 MB`Heap delta (`inuse_space`) also showed live growth attributable to this path, including:`encoding/base64.(*Encoding).DecodeString` → `7,629.75 kB`Memory observationsRuntime memory monitoring showed a clear increase in peak resident usage during the attack:baseline `VmHWM / VmRSS` before load was approximately `55,864 kB`observed `VmHWM` during testing reached approximately `146,100 kB`So even though requests returned `400`, the server still experienced substantial transient memory growth and allocator / GC pressure before rejection.ImpactA remote, unauthenticated attacker can repeatedly send oversized DoH GET requests to the HTTPS endpoint and force significant pre-rejection work.Impact includes:elevated CPU consumptionlarge transient allocationsincreased garbage-collection pressurehigher peak resident memory usagedegraded throughput and responsivenessdenial of service risk on memory-constrained or heavily loaded deploymentsThis is especially relevant for internet-facing DoH deployments, where an attacker can repeatedly trigger the GET parsing path without authentication.The fact that the final HTTP status is `400 Bad Request` does not mitigate the issue, because the expensive processing has already occurred before the rejection is generated.Suggested remediationA robust fix should address both stages of the problem:Apply an early bound on the DoH GET request target / raw query length before expensive query parsing.Enforce an encoded-length and decoded-length limit for the `dns` parameter before calling `DecodeString()`.Preserve equivalent size constraints across GET and POST paths.A minimal hardening direction would be:reject oversized GET requests before `req.URL.Query()` on the DoH pathreject `dns` values whose encoded length exceeds the maximum valid DNS message encodingreject any decoded payload larger than the supported DNS message size before unpacking**Credit request: When referencing, republishing, or issuing downstream advisories for this vulnerability, please preserve the original researcher credit as Ali Firas (thesmartshadow).**
CVSS v4.0: 8.7
CVSS v3.1: 7.5