Two days ago, I wrote about how to profile traffic to recognize DNS over HTTPS. This is kind of a problem for DNS over HTTPS. If you can see it, you may be able to block it. On Twitter, a few chimed in to provide feedback about recognizing DNS over HTTPS. I checked a couple of other clients, and well, didn't have a ton of time so this is still very preliminary:
But to come back to the initial observation: The DoH traffic had specific packet sizes it preferred. So I was looking at this since it didn't seem random, meaning it leaked information.
I found a couple of interesting issues. First of all, the DNS query and response are sent as two packets. The first packet contains the HTTP2 headers. The second packet, the payload, which is our DNS payload. For requests and responses, the HTTP2 headers are always the same. The AES encryption process, as it is performed by TLS 1.3 does not change the length of the data. It just adds headers and integrity checks, but the size of the encrypted data matches the size of the decrypted data. This makes it easy to identify the packets that contain the HTTP2 headers for requests and responses:
The two most common packet sizes correspond to the responses (smaller packet, 38 Bytes ) and requests (larger packets, 45-50 bytes in this example). The HTTP2 HEADER record for requests is slightly larger than the response as it includes the URL, the "authority" (hostname), and other details. The size of these requests can be used to deduct which DoH provider is being used.
The second packet, the content of the query or the response, is interesting as the packet length is related to the size of the query. The diagram below illustrates that all other fields in the TCP payload have a fixed size, and only the query string is variable.
Which adds 65 Bytes of fixed-length data to the TCP length. The rest is our DNS query string.
So what is the solution? There is a solution that was defined specifically for this reason. At first, I considered this a bug in Firefox and reported it, but I was pointed to an already open bug about this issue . One of our readers left a detailed comment pointing out this issue as well. The solution is, as so often with crypto: Padding. DNS has a special option for it, and this option was specifically introduced for this use case . The idea is that the client just appends some data to the end to obscure the size of the query. RFCs suggest to always pad to a specific size. But well, random padding would probably work too. The server will also respond with padding to avoid the same attack on the response. At least that is what the RFC suggests.
I used a sample of DoH providers , and crafted an HTTP2 request with and without padding, to see how they respond. To craft the DNS payload, I used scapy. I could probably use scapy for the HTTP2 part as well but found it simpler to just send the DNS payload over HTTP2 using curl. Scapy does have a contributed HTTP2 extension if anybody wants to make this prettier. The reader commenting to the earlier post used sdig which also looks interesting to do these tests. It works like 'dig', but supports DoH. sdig is part of the PowerDNS project 
Here is the scapy part without padding:
For the "no padding" test, I still included the standard/default EDNS0 option.
and with padding:
The difference is just the final 6 bytes:
Padding Option: 000C , Padding Length: 4 Bytes, and 4 0 bytes to pad. I picked a short padding length to save myself some typing.
To verify that I got a reasonable valid packet, I used Wireshark from scapy:
Next, using scapy to extract the DNS payload in hexadecimal:
And sending the string with curl (simple copy-paste for the string and using xxd to convert it to binary for curl):
Xxd at the end is used to inspect the result. Here are some of the result I found using a sample of public DoH endpoints:
The fact that I got responses from Cleanbrowsing and Quad9 should indicate that my request was valid. But these are the only two providers that responded. Some sent errors back, so I want to go back and double-check my work above.
Lots of work left. I will publish pcaps shortly (but it is always a pain to get clean pcaps without much additional stuff.. give me a few hours).
Dec 19th 2019
Dec 19th 2019
10 months ago