Skip to main content
VulnerabilityPublished

CVE-2026-83813: DNS Response Spoofing in twisted.names Due to Missing Source Address Validation

Affects alotSource GCVE Database

0
Compare
Authoritative record:CVE-2026-83813· Low 3.1

Not known to be exploited · CVSS 3.1 Low.

Data as of 30 Oct 2026, 05:15 UTC

CVSS 3.1 base score
3.1Low
How severe the flaw is on the 0–10 CVSS scale.

Thin evidence

One scorer or none
CVSS scores by source
CNA (GitHub_M)CVSS 3.13.1
CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 23 Oct 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponized: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 29 Oct 2026.

What to do

Affected software

ProductAffectedFixed in
Homebrewalotpkg:brew/alotOther threatsAffected
  • >=0.3.7
Fixed in–

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

### Summary `twisted.names.dns.DNSDatagramProtocol`, the UDP transport used by twisted's DNS client (`twisted.names.client.Resolver`, `createResolver`, and anything built on it, including MX lookups in `twisted.mail`), accepts a DNS response for a pending query solely on the basis of the 16-bit transaction ID matching. It never checks that the response actually originated from the server the query was sent to. Any host that can deliver a UDP packet to the resolver's listening port with the correct transaction ID can inject a forged answer, independent of the real server's address. This is a materially weaker defense than standard DNS resolver practice (transaction ID plus source-address validation, typically also source-port rotation), and enables off-path DNS response spoofing / cache poisoning. ### Details `DNSDatagramProtocol.datagramReceived` (`src/twisted/names/dns.py`, line 3350) is: ```python def datagramReceived(self, data, addr): """ Read a datagram, extract the message in it and trigger the associated Deferred. """ m = Message() try: m.fromStr(data) except EOFError: ... except ValueError as ex: ... except BaseException: ... if m.id in self.liveMessages: d, canceller = self.liveMessages[m.id] del self.liveMessages[m.id] canceller.cancel() try: d.callback(m) except BaseException: log.err() else: if m.id not in self.resends: self.controller.messageReceived(m, self, addr) ``` The `addr` parameter, the UDP source address of the datagram, is accepted but never compared against the address the corresponding query was sent to. The only check is `m.id in self.liveMessages`, and `liveMessages` is keyed purely by the transaction ID chosen in `DNSMixin.pickID()`. The listening socket is a plain `listenUDP(0, self, maxPacketSize=512)` (line 3348), not connected to the queried server, so the kernel does not filter by source address either; any host can deliver a UDP datagram to this socket. The transaction ID itself is generated with `secureRandom` (`randomSource()`, line 174), so it is not predictable, but at 16 bits it is a 1-in-65536 space per attempt. `twisted.names.client.Resolver`, the standard, documented DNS client, mitigates brute-force guessing of that space by creating a brand-new `DNSDatagramProtocol` bound to a fresh, independently random source port (`_connectedProtocol()`, `src/twisted/names/client.py` line 206, using `dns.randomSource()`) for every individual query, and tearing the socket down once the query completes (`_query()`, line 253). This gives an off-path attacker roughly 32 bits of combined entropy (port and transaction ID) to guess per query issued through the standard client, not just the 16 bits of the transaction ID alone. The missing source-address check is still a real gap for two reasons. First, it removes a defense-in-depth backstop that DNS resolver best practice (RFC 5452) treats as independent from port and ID randomization; an attacker who is on-path, shares a broadcast domain or NAT with the resolver or the real DNS server, or is otherwise able to observe or narrow the ephemeral port faces no additional barrier at all, since source-address validation is exactly the check that would stop them. Second, `DNSDatagramProtocol` is itself a public, documented class; code that constructs one directly and issues more than one query against the same instance, rather than going through `client.Resolver`'s per-query protocol and port cycling, reuses the same source port for every query after the first, so guessing the 16-bit transaction ID alone is sufficient from that point on. `twisted.names.client.Resolver` uses this same `DNSDatagramProtocol` class for every UDP query it issues, so every consumer of the standard twisted DNS client, including `twisted.mail`'s MX resolution, is subject to the same unauthenticated accept logic in `datagramReceived`, moderated by the per-query port rotation described above. ### PoC ```python import socket from twisted.internet import reactor from twisted.names import dns class CapturingController: def messageReceived(self, message, protocol, address): pass def main(): resolverProto = dns.DNSDatagramProtocol(CapturingController()) resolverProto.startListening() real_server = ("203.0.113.1", 53) # RFC 5737 TEST-NET-1, never answers query = dns.Query(b"victim.example.com", dns.A, dns.IN) d = resolverProto.query(real_server, [query], timeout=5) stolen_id = next(iter(resolverProto.liveMessages)) resolver_port = resolverProto.transport.getHost().port forged = dns.Message(stolen_id, answer=1) forged.queries = [query] forged.answers = [ dns.RRHeader(name=b"victim.example.com", type=dns.A, cls=dns.IN, ttl=60, payload=dns.Record_A(address="6.6.6.6")) ] attacker_sock = socket.socket(socket.AF_INET, s

References

Community reviews

Loading reviews…