From e223ee709765e6eff2bb6ae980ec5eec83d64390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pa=C4=8Dek?= Date: Mon, 28 Jul 2025 11:33:14 +0200 Subject: [PATCH] Test that spoofed DNAME is not accepted via spoofable transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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ń --- bin/tests/system/bailiwick/ans2/ans.py | 25 +++++++++++++++++++ bin/tests/system/bailiwick/tests_bailiwick.py | 8 ++++++ 2 files changed, 33 insertions(+) diff --git a/bin/tests/system/bailiwick/ans2/ans.py b/bin/tests/system/bailiwick/ans2/ans.py index d1627fd5bd..1a9be3d931 100644 --- a/bin/tests/system/bailiwick/ans2/ans.py +++ b/bin/tests/system/bailiwick/ans2/ans.py @@ -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() diff --git a/bin/tests/system/bailiwick/tests_bailiwick.py b/bin/tests/system/bailiwick/tests_bailiwick.py index 2e1c845a22..7245c71bce 100644 --- a/bin/tests/system/bailiwick/tests_bailiwick.py +++ b/bin/tests/system/bailiwick/tests_bailiwick.py @@ -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)