Test that spoofed DNAME is not accepted via spoofable transport

A single spoofed DNAME answer can impact many names, and because of the
nature of DNAME, the attacker can use randomized query names to get
unlimited number of tries to spoof the answer.  To limit impact, we
should not be accepting DNAME over insecure transport, like UDP without
cookies etc.

In short, the attacker tries to spoof at least one answer that has the
following form:

    opcode QUERY
    rcode NOERROR
    flags QR AA
    ;QUESTION
    trigger$RANDOM.test. IN A
    ;ANSWER
    trigger$RANDOM.test. 3600 IN CNAME trigger$RANDOM.attacker.net.
    test. 3600 IN DNAME attacker.net.
    ;AUTHORITY
    ;ADDITIONAL

This has been discovered internally.

Co-authored-by: Michał Kępień <michal@isc.org>
This commit is contained in:
Petr Špaček 2025-07-28 11:33:14 +02:00 committed by Michał Kępień
parent b5dc46fe6e
commit e223ee7097
No known key found for this signature in database
2 changed files with 33 additions and 0 deletions

View file

@ -77,6 +77,31 @@ class ParentGlueSpoofer(ResponseSpoofer, mode="parent-glue"):
yield DnsResponseSend(response, authoritative=False)
class DnameSpoofer(ResponseSpoofer, mode="dname"):
qname = "trigger.victim."
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
response = qctx.prepare_new_response(with_zone_data=False)
cname_rrset = dns.rrset.from_text(
qctx.qname,
TTL,
qctx.qclass,
dns.rdatatype.CNAME,
"trigger.attacker.",
)
dname_rrset = dns.rrset.from_text(
"victim.", TTL, qctx.qclass, dns.rdatatype.DNAME, "attacker."
)
response.answer.append(cname_rrset)
response.answer.append(dname_rrset)
yield DnsResponseSend(response, authoritative=True)
def main() -> None:
spoofing_server().run()

View file

@ -109,3 +109,11 @@ def test_bailiwick_parent_glue(servers: Dict[str, NamedInstance]) -> None:
time.sleep(61)
check_domain_hijack(ns4)
def test_bailiwick_spoofed_dname(servers: Dict[str, NamedInstance]) -> None:
set_spoofing_mode(ans1="none", ans2="dname")
ns4 = servers["ns4"]
send_trigger_query(ns4, "trigger.victim.")
check_domain_hijack(ns4)