paulc / dnslib

A Python library to encode/decode DNS wire-format packets
https://github.com/paulc/dnslib
BSD 2-Clause "Simplified" License
295 stars 84 forks source link

Dnslib fails to handle unknown RR types in NSEC RD type bitmap #45

Closed robinlandstrom closed 1 year ago

robinlandstrom commented 1 year ago

I parsed a bunch of DNS traffic with dnslib and noticed that some NSEC response records contains a TYPE65534 record that causes dnslib to fail.

Example:

;; Sending:
;; QUERY: 822601000001000000000000066f696c70726f02636800002f0001
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33318
;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;oilpro.ch.                     IN      NSEC

;; Got answer:
;; RESPONSE: 822681800001000100000000066f696c70726f02636800002f0001c00c002f000100000e100036066f696c70726f02636800000722000000000380ff200000000000000000000000000000000000000000000000000000000000000002
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33318
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;oilpro.ch.                     IN      NSEC
;; ANSWER SECTION:
oilpro.ch.              3600    IN      NSEC    oilpro.ch. NS SOA RRSIG NSEC DNSKEY TYPE65534

TYPE65534 is an internal type used by BIND to keep track of signing, not sure why it shows up in the NSEC data, but it does. https://www.dns.cam.ac.uk/news/2020-04-27-nsdiff.html https://ftp.iij.ad.jp/pub/network/isc/bind9/9.14.6/doc/arm/Bv9ARM.ch04.html

Also possible to query for the TYPE65534 record on the domain

$ dig oilpro.ch TYPE65534

; <<>> DiG 9.18.6 <<>> oilpro.ch TYPE65534
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16416
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; MBZ: 0x0005, udp: 512
;; QUESTION SECTION:
;oilpro.ch.                     IN      TYPE65534

;; ANSWER SECTION:
oilpro.ch.              5       IN      TYPE65534 \# 5 0D2A680001
oilpro.ch.              5       IN      TYPE65534 \# 5 0DD5C60001

;; Query time: 63 msec
;; SERVER: 192.168.42.2#53(192.168.42.2) (UDP)
;; WHEN: Mon Oct 24 13:50:23 CEST 2022
;; MSG SIZE  rcvd: 72

Quick and dirty fix for my needs for decoding the NSEC part

--- dnslib/dns.py
+++ dnslib/dns.py
@@ -1634,7 +1636,7 @@ def decode_type_bitmap(type_bitmap):
             for i in range(8):
                 if (value << i) & 0x80:
                     bitpos = (256*winnum) + (8*pos) + i
-                    rrlist.append(QTYPE[bitpos])
+                    rrlist.append(QTYPE.get(bitpos, "TYPE" + str(bitpos)))

This only helps for unknown types in the type bitmap thou, and only for decoding. Dnslib still fails to parse unknown RR types in general

$ ./test_decode.py --new oilpro.ch TYPE65534
Traceback (most recent call last):
  File "/home/lanrob1702/development/dnslib/dnslib/bimap.py", line 75, in __getattr__
    return self.reverse[k]
KeyError: 'TYPE65534'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/lanrob1702/development/dnslib/dnslib/./test_decode.py", line 264, in <module>
    new_test(*args.new,nodig=args.nodig,dnssec=args.dnssec)
  File "/home/lanrob1702/development/dnslib/dnslib/./test_decode.py", line 72, in new_test
    q = DNSRecord.question(domain,qtype)
  File "/home/lanrob1702/development/dnslib/dnslib/dns.py", line 140, in question
    return DNSRecord(q=DNSQuestion(qname,getattr(QTYPE,qtype),
  File "/home/lanrob1702/development/dnslib/dnslib/bimap.py", line 77, in __getattr__
    raise self.error("%s: Invalid reverse lookup: [%s]" % (self.name,k))
dnslib.dns.DNSError: QTYPE: Invalid reverse lookup: [TYPE65534]

A better solution would probably be to implement support for RFC3597: Handling of Unknown DNS Resource Record (RR) Types

[5](https://datatracker.ietf.org/doc/html/rfc3597#section-5).  Text Representation

   In the "type" field of a master file line, an unknown RR type is
   represented by the word "TYPE" immediately followed by the decimal RR
   type number, with no intervening whitespace.  In the "class" field,
   an unknown class is similarly represented as the word "CLASS"
   immediately followed by the decimal class number.

   This convention allows types and classes to be distinguished from
   each other and from TTL values, allowing the "[<TTL>] [<class>]
   <type> <RDATA>" and "[<class>] [<TTL>] <type> <RDATA>" forms of
   [[RFC1035](https://datatracker.ietf.org/doc/html/rfc1035)] to both be unambiguously parsed.

   The RDATA section of an RR of unknown type is represented as a
   sequence of white space separated words as follows:

      The special token \# (a backslash immediately followed by a hash
      sign), which identifies the RDATA as having the generic encoding
      defined herein rather than a traditional type-specific encoding.

      An unsigned decimal integer specifying the RDATA length in octets.

      Zero or more words of hexadecimal data encoding the actual RDATA
      field, each containing an even number of hexadecimal digits.

   If the RDATA is of zero length, the text representation contains only
   the \# token and the single zero representing the length.

Most of the parts needed seems to already be available in the RR and RD base classes.

@paulc Would a PR with necessary updates to RR, RD and the Bimap be interesting if I decide to take a look at it?

paulc commented 1 year ago

Robin - thanks for pointing this out - I hadn't spotted rfc3597. I didn't have a lot of time today but I have pushed initial support for this. Need to tidy this up a bit and add some tests but just wanted to check whether this works for you (I have tested against the 'oilpro.ch TYPE65534' query which now works but haven't tested any further)

# python3 -mdnslib.client oilpro.ch TYPE65534
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64272
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;oilpro.ch.                     IN      TYPE65534
;; ANSWER SECTION:
oilpro.ch.              0       IN      TYPE65534 \# 5 0DD5C60001
oilpro.ch.              0       IN      TYPE65534 \# 5 0D2A680001
robinlandstrom commented 1 year ago

Thanks Paul, absolutely works for me. The changes look good.

Last version on master (a99a458453d52f416300492b551a086f2027a61b) passes the tests I have. When I run a bunch of random traffic through it I no longer see any parse errors related to unsupported RR types 😃