mirror of
https://github.com/isc-projects/bind9.git
synced 2026-07-15 09:00:53 -04:00
Add forward-first referral poisoning reproducer
Add a system test covering authority-section NS referrals returned by configured forwarders under forward first. The test verifies that a forwarder for fwd.hack cannot install the parent hack zone cut and redirect resolution for the sibling zone sibling.hack.
This commit is contained in:
parent
cef614cdf1
commit
ee34bbd208
9 changed files with 244 additions and 0 deletions
22
bin/tests/system/fwdfirst/ans1/ans.py
Normal file
22
bin/tests/system/fwdfirst/ans1/ans.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"""
|
||||
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
See the COPYRIGHT file distributed with this work for additional
|
||||
information regarding copyright ownership.
|
||||
"""
|
||||
|
||||
from isctest.asyncserver import AsyncDnsServer
|
||||
|
||||
|
||||
def main() -> None:
|
||||
AsyncDnsServer().run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
7
bin/tests/system/fwdfirst/ans1/hack.db
Normal file
7
bin/tests/system/fwdfirst/ans1/hack.db
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
$TTL 300
|
||||
@ SOA ns.hack. . 0 0 0 0 300
|
||||
@ NS ns
|
||||
ns A 10.53.0.1
|
||||
|
||||
sibling NS ns.sibling
|
||||
ns.sibling A 10.53.0.1
|
||||
8
bin/tests/system/fwdfirst/ans1/root.db
Normal file
8
bin/tests/system/fwdfirst/ans1/root.db
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
$ORIGIN .
|
||||
$TTL 300
|
||||
@ SOA a.root-servers.nil. . 0 0 0 0 300
|
||||
@ NS a.root-servers.nil.
|
||||
a.root-servers.nil. A 10.53.0.1
|
||||
|
||||
hack. NS ns.hack.
|
||||
ns.hack. A 10.53.0.1
|
||||
5
bin/tests/system/fwdfirst/ans1/sibling.hack.db
Normal file
5
bin/tests/system/fwdfirst/ans1/sibling.hack.db
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
$TTL 300
|
||||
@ SOA ns.sibling.hack. . 0 0 0 0 300
|
||||
@ NS ns
|
||||
ns A 10.53.0.1
|
||||
victim A 203.0.113.42
|
||||
45
bin/tests/system/fwdfirst/ans2/ans.py
Normal file
45
bin/tests/system/fwdfirst/ans2/ans.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""
|
||||
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
See the COPYRIGHT file distributed with this work for additional
|
||||
information regarding copyright ownership.
|
||||
"""
|
||||
|
||||
import dns.rcode
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
|
||||
from isctest.asyncserver import AsyncDnsServer, DomainHandler, StaticResponseHandler
|
||||
|
||||
|
||||
class PoisonReferralForwarder(DomainHandler, StaticResponseHandler):
|
||||
domains = ["fwd.hack."]
|
||||
authority = [
|
||||
dns.rrset.from_text(
|
||||
"hack.", 300, dns.rdataclass.IN, dns.rdatatype.NS, "ns.fwd.hack."
|
||||
)
|
||||
]
|
||||
additional = [
|
||||
dns.rrset.from_text(
|
||||
"ns.fwd.hack.", 300, dns.rdataclass.IN, dns.rdatatype.A, "10.53.0.3"
|
||||
)
|
||||
]
|
||||
rcode = dns.rcode.NOERROR
|
||||
authoritative = False
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer()
|
||||
server.install_response_handler(PoisonReferralForwarder())
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
50
bin/tests/system/fwdfirst/ans3/ans.py
Normal file
50
bin/tests/system/fwdfirst/ans3/ans.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""
|
||||
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
See the COPYRIGHT file distributed with this work for additional
|
||||
information regarding copyright ownership.
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
import dns.rcode
|
||||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
QueryContext,
|
||||
ResponseAction,
|
||||
ResponseHandler,
|
||||
)
|
||||
|
||||
|
||||
class AttackerAuthority(ResponseHandler):
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[ResponseAction, None]:
|
||||
if qctx.qtype == dns.rdatatype.A:
|
||||
qctx.response.answer.append(
|
||||
dns.rrset.from_text(
|
||||
qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "6.6.6.6"
|
||||
)
|
||||
)
|
||||
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handler(AttackerAuthority())
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
31
bin/tests/system/fwdfirst/ns4/named.conf.j2
Normal file
31
bin/tests/system/fwdfirst/ns4/named.conf.j2
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
options {
|
||||
query-source address 10.53.0.4;
|
||||
notify-source 10.53.0.4;
|
||||
transfer-source 10.53.0.4;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.4; };
|
||||
listen-on-v6 { none; };
|
||||
recursion yes;
|
||||
allow-query { any; };
|
||||
allow-recursion { any; };
|
||||
dnssec-validation no;
|
||||
qname-minimization off;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
include "../../_common/rndc.key";
|
||||
|
||||
zone "." {
|
||||
type hint;
|
||||
file "../../_common/root.hint";
|
||||
};
|
||||
|
||||
zone "fwd.hack" {
|
||||
type forward;
|
||||
forward first;
|
||||
forwarders { 10.53.0.2 port @PORT@; };
|
||||
};
|
||||
27
bin/tests/system/fwdfirst/ns5/named.conf.j2
Normal file
27
bin/tests/system/fwdfirst/ns5/named.conf.j2
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
options {
|
||||
query-source address 10.53.0.5;
|
||||
notify-source 10.53.0.5;
|
||||
transfer-source 10.53.0.5;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.5; };
|
||||
listen-on-v6 { none; };
|
||||
recursion yes;
|
||||
allow-query { any; };
|
||||
allow-recursion { any; };
|
||||
dnssec-validation no;
|
||||
qname-minimization off;
|
||||
forward first;
|
||||
forwarders { 10.53.0.2 port @PORT@; };
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
include "../../_common/rndc.key";
|
||||
|
||||
zone "." {
|
||||
type hint;
|
||||
file "../../_common/root.hint";
|
||||
};
|
||||
49
bin/tests/system/fwdfirst/tests_fwdfirst.py
Normal file
49
bin/tests/system/fwdfirst/tests_fwdfirst.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import dns.name
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import pytest
|
||||
|
||||
from isctest.instance import NamedInstance
|
||||
|
||||
import isctest
|
||||
|
||||
|
||||
def _a_records(response, qname: str) -> set[str]:
|
||||
rrset = response.get_rrset(
|
||||
response.answer,
|
||||
dns.name.from_text(qname),
|
||||
dns.rdataclass.IN,
|
||||
dns.rdatatype.A,
|
||||
)
|
||||
if rrset is None:
|
||||
return set()
|
||||
return {rdata.address for rdata in rrset}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resolver_name", ["ns4", "ns5"])
|
||||
def test_forward_first_referral_ns_above_forward_zone_not_cached(
|
||||
servers: dict[str, NamedInstance],
|
||||
resolver_name: str,
|
||||
) -> None:
|
||||
resolver = servers[resolver_name]
|
||||
resolver.rndc("flush")
|
||||
|
||||
trigger = isctest.query.create("trigger.fwd.hack.", "A", dnssec=False)
|
||||
isctest.query.tcp(trigger, resolver.ip)
|
||||
|
||||
sibling = isctest.query.create("victim.sibling.hack.", "A", dnssec=False)
|
||||
response = isctest.query.tcp(sibling, resolver.ip)
|
||||
isctest.check.noerror(response)
|
||||
|
||||
assert _a_records(response, "victim.sibling.hack.") == {"203.0.113.42"}
|
||||
Loading…
Reference in a new issue