]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Test that positive answer cannot overwrite sibling NS RRs
authorPetr Špaček <pspacek@isc.org>
Fri, 11 Jul 2025 16:37:57 +0000 (18:37 +0200)
committerMichał Kępień <michal@isc.org>
Mon, 22 Dec 2025 10:58:39 +0000 (11:58 +0100)
Before the fixes for CVE-2025-40778, a positive answer was allowed to
overwrite sibling NS RRs.  The answer had to be a positive AA=1 answer
with a fake NS along with it.  This combination of conditions avoided
the code path with "unrelated <RRTYPE>" detection logic.

If it were some other answer, named from the main branch would detect
the attempt and log:

    DNS format error from 10.53.0.1#16386 resolving trigger/A for <unknown>: unrelated NS victim in trigger authority section

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. IN A
    ;ANSWER
    trigger$RANDOM. 3600 IN A 10.53.0.3
    ;AUTHORITY
    victim. 3600 IN NS ns.attacker.
    ;ADDITIONAL
    ns.attacker. 3600 IN A 10.53.0.3

This attack was originally reported as "test case 1c".

Co-authored-by: Michał Kępień <michal@isc.org>
bin/tests/system/bailiwick/ans1/ans.py
bin/tests/system/bailiwick/tests_bailiwick.py

index be072a39e163a1ef47b448fc491a26bb9cf7bc03..f0b152ac3d7931b7f4359d83253f0dcd58957aa7 100644 (file)
@@ -29,6 +29,37 @@ ATTACKER_IP = "10.53.0.3"
 TTL = 3600
 
 
+class SiblingNsSpoofer(ResponseSpoofer, mode="sibling-ns"):
+
+    qname = "trigger."
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[ResponseAction, None]:
+        response = qctx.prepare_new_response(with_zone_data=False)
+
+        txt_rrset = dns.rrset.from_text(
+            qctx.qname,
+            TTL,
+            qctx.qclass,
+            dns.rdatatype.TXT,
+            '"spoofed answer with extra NS"',
+        )
+        response.answer.append(txt_rrset)
+
+        ns_rrset = dns.rrset.from_text(
+            "victim.", TTL, qctx.qclass, dns.rdatatype.NS, "ns.attacker."
+        )
+        response.authority.append(ns_rrset)
+
+        a_rrset = dns.rrset.from_text(
+            "ns.attacker.", TTL, qctx.qclass, dns.rdatatype.A, ATTACKER_IP
+        )
+        response.additional.append(a_rrset)
+
+        yield DnsResponseSend(response, authoritative=True)
+
+
 def main() -> None:
     spoofing_server().run()
 
index 79ee8a4364c052111f9124e71c5f311f8f3eda66..b068180265c73e29cf85867aaa0db6620b2188ed 100644 (file)
@@ -77,3 +77,11 @@ def check_domain_hijack(ns4: NamedInstance) -> None:
         "TXT",
         '"correct answer from the domain under attack"',
     )
+
+
+def test_bailiwick_sibling_ns_referral(servers: Dict[str, NamedInstance]) -> None:
+    set_spoofing_mode(ans1="sibling-ns", ans2="none")
+
+    ns4 = servers["ns4"]
+    send_trigger_query(ns4, "trigger.")
+    check_domain_hijack(ns4)