]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Test that spoofed DNAME is not accepted via spoofable transport
authorPetr Špaček <pspacek@isc.org>
Mon, 28 Jul 2025 09:33:14 +0000 (11:33 +0200)
committerMichał Kępień <michal@isc.org>
Mon, 22 Dec 2025 10:58:39 +0000 (11:58 +0100)
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>
bin/tests/system/bailiwick/ans2/ans.py
bin/tests/system/bailiwick/tests_bailiwick.py

index d1627fd5bd621891f648202ab8eae75d299754a6..1a9be3d931ed2f98fb4c81eedcfb833f1ecb54bb 100644 (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()
 
index 2e1c845a2202caf4cecf85ec9aad20dff19bf875..7245c71bce2b0bf140c9c36acbefba3580687ff6 100644 (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)