Exemplo n.º 1
0
    def search_soa(self, zone: Zone, result: DNSSECScannerResult,
                   resolver: dns.resolver.Resolver) -> SoaState:
        response = utils.dns_query(self.domain, zone.ip, dns.rdatatype.SOA)

        rrsets = response.answer + response.authority

        # RCODE 3 (NXDomain) means the domain name does not exist
        # Source: https://tools.ietf.org/html/rfc6895#section-2.3
        if response.rcode() == 3:
            # Domain name does not exist. Validate with NSEC the integrity of the none-existence.
            result.note = "Domain name does not exist"
            zone.RR = rrsets
            nsec.proof_none_existence(zone, result, False)
            return SoaState.FOUND
        elif utils.get_rrs_by_type(rrsets, dns.rdatatype.CNAME):
            # We have found a CNAME RR set so we have to start from the top again
            zone.RR = rrsets
            validate_rrset(zone, result)  # validate CNAME entry

            self.domain = str(
                utils.get_rr_by_type(rrsets,
                                     dns.rdatatype.CNAME).items[0].target)
            result.domain = self.domain
            return SoaState.FOUND_CNAME
        elif utils.get_rr_by_type(rrsets, dns.rdatatype.SOA):
            # We are in the zone for the domain name
            rr_types = self.find_records(zone)
            zone.RR = self.get_records(zone, rr_types)

            validate_rrset(zone, result, True)
            return SoaState.FOUND

        return SoaState.NOT_FOUND
Exemplo n.º 2
0
    def get_dnskey(self, zone: Zone):
        response = utils.dns_query(zone.name, zone.ip, dns.rdatatype.DNSKEY)

        zone.DNSKEY = utils.get_rr_by_type(response.answer,
                                           dns.rdatatype.DNSKEY)
        zone.DNSKEY_RRSIG = utils.get_rr_by_type(response.answer,
                                                 dns.rdatatype.RRSIG)
Exemplo n.º 3
0
    def get_ns(self, zone: Zone, resolver: dns.resolver.Resolver) -> Zone:
        response = utils.dns_query(self.domain, zone.ip, dns.rdatatype.NS)

        response = utils.get_rr_by_type(response.authority, dns.rdatatype.NS)
        next_zone_name = str(response.name)
        zone.child_name = next_zone_name

        next_zone_domain = response.items[0].to_text()
        next_zone_ip = resolver.query(next_zone_domain,
                                      "A").rrset.items[0].address

        return Zone(next_zone_name, next_zone_ip, next_zone_domain, zone)
Exemplo n.º 4
0
    def find_records_from_nsec(self, zone: Zone) -> Set[int]:
        response = utils.dns_query(self.domain, zone.ip, dns.rdatatype.NSEC)
        rrsets = response.answer + response.authority

        nsec = utils.get_rr_by_type(rrsets, dns.rdatatype.NSEC)
        if nsec:
            return nsec_utils.nsec_window_to_array(nsec.items[0])

        nsec3 = utils.get_rr_by_type(rrsets, dns.rdatatype.NSEC3)
        if nsec3:
            return nsec_utils.nsec_window_to_array(nsec3.items[0])

        return set()
Exemplo n.º 5
0
    def get_records(self, zone: Zone, rrs: Set[int]) -> List[dns.rrset.RRset]:
        output = []
        for rr in rrs:
            response = utils.dns_query(self.domain, zone.ip, rr)
            # check if RR exists
            rrsets = utils.get_rrs_by_type(response.answer, rr)
            if rrsets:
                output.extend(response.answer)

            for name, rrset in rrsets:
                # only for pretty printing
                for entry in rrset.to_text().split("\n"):
                    log.info(f"Found DNS entry: {entry}")

        return output
Exemplo n.º 6
0
    def get_ds(self, zone: Zone, next_zone: Zone,
               result: DNSSECScannerResult) -> bool:
        response = utils.dns_query(next_zone.name, zone.ip, dns.rdatatype.DS)

        zone.RR = response.answer
        if not utils.get_rr_by_type(zone.RR, dns.rdatatype.DS):
            zone.RR = response.authority
            nsec.proof_none_existence(zone, result, True)
            msg = Message(zone.name, zone.child_name, dns.rdatatype.DS)
            msg.set_not_found(Msg.NOT_FOUND)
            result.errors.append(str(msg))
            result.change_state(False)
            return False

        return True
Exemplo n.º 7
0
    def find_records(self, zone: Zone) -> Set[int]:
        # define a default list of records in case ANY does not return anything
        rr_types = [
            dns.rdatatype.SOA,
            dns.rdatatype.NS,
            dns.rdatatype.A,
            dns.rdatatype.AAAA,
            dns.rdatatype.MX,
            dns.rdatatype.CNAME,
            dns.rdatatype.PTR,
            dns.rdatatype.WKS,
            dns.rdatatype.HINFO,
            dns.rdatatype.MINFO,
            dns.rdatatype.TXT,
        ]

        if self.requested_type:
            rr_types.append(self.requested_type)

        # ask with ANY for all existing records
        response = utils.dns_query(self.domain, zone.ip, dns.rdatatype.ANY)

        for rr in response.answer:
            if rr.rdtype != dns.rdatatype.DNSKEY and rr.rdtype != dns.rdatatype.RRSIG:
                rr_types.append(rr.rdtype)

        # Try to query a NSEC or NSEC3 record. This would tell
        # us all available record types.
        rr_types.extend(self.find_records_from_nsec(zone))

        # remove duplicates
        rr_types = set(rr_types)
        # remove NSEC and NSEC3 record types
        if dns.rdatatype.NSEC in rr_types:
            rr_types.remove(dns.rdatatype.NSEC)
        if dns.rdatatype.NSEC3 in rr_types:
            rr_types.remove(dns.rdatatype.NSEC3)
        return rr_types
Exemplo n.º 8
0
def nsec3_proof_of_none_existence(
    nsec3s: List[dns.rdtypes.ANY.NSEC3],
    zone: Zone,
    result: DNSSECScannerResult,
    check_ds: Optional[bool] = False,
) -> bool:
    """
    This method does the closest encloser proof after: https://tools.ietf.org/html/rfc7129#section-5.5
    :param nsec3s:
    :param zone:
    :param result:
    :param check_ds:
    :return:
    """
    success = True

    qname = dns.name.from_text(result.domain)

    # query NSEC3 paramter from zone and validate them
    response = utils.dns_query(zone.name, zone.ip, dns.rdatatype.NSEC3PARAM)
    zone.RR = utils.remove_duplicates(response.answer)
    success &= validate_rrset(zone, result)
    nsec3param = utils.get_rr_by_type(zone.RR, dns.rdatatype.NSEC3PARAM)

    if check_ds:
        status = check_nsec_bitmap(nsec3s, nsec3param.items[0],
                                   zone.child_name)
        # If we want to show that there is no DS record for the QNAME
        # and we have found an NSEC3 record for the QNAME without the
        # DS record in its bitmap field we are already done.
        if status:
            return status

    # search for closest enclosure
    status, closest_encloser, next_closer_name = find_closest_encloser(
        nsec3s, nsec3param.items[0], qname)
    if status:
        msg = f"{zone.name} zone: Found closest encloser {closest_encloser}"
        result.logs.append(msg)
    else:
        msg = f"{zone.name} zone: Could not find closest encloser for {qname.to_text()}"
        result.errors.append(msg)
    success &= status

    # check if the next closer name is covered by an NSEC3 record
    status = check_name_cover(nsec3s, nsec3param.items[0], next_closer_name)
    if status:
        msg = f"{zone.name} zone: Found NSEC3 that covers the next closer name {next_closer_name}"
        result.logs.append(msg)
    else:
        msg = f"{zone.name} zone: Could not find a NSEC3 record that covers the next closer name {next_closer_name}"
        result.errors.append(msg)
    success &= status

    if check_ds:
        # If we want to show an insecure delegation at this point,
        # every NSEC3 record must have the Opt-Out flag set.
        success &= check_opt_out(nsec3s)
    else:
        # Proof "Three to Tango" to show that the domain name does not exist.
        # The only missing part is to show that no wildcard expansion could be used.
        # Source: https://tools.ietf.org/html/rfc7129#section-5.6
        success = check_name_cover(nsec3s, nsec3param.items[0],
                                   f"*.{closest_encloser}")
        if success:
            msg = f"{zone.name} zone: Found NSEC3 that covers the wildcard *.{closest_encloser}"
            result.logs.append(msg)
        else:
            msg = f"{zone.name} zone: Could not find a NSEC3 record that covers the  wildcard *.{closest_encloser}"
            result.errors.append(msg)

    return success