DNS över HTTPS (DoH) - Förklarat

Saturday, September 24, 2022

Privacydnsdohdomain name system

Christoffer Strömblad

Entusiastisk Jedi inom Cybersäkerhet
3 minuter att läsa.

En av de mest fundamentala komponenterna bakom ett användbart och fungerande Internet är DNS, eller Domain Name System som det egentligen heter. De flesta brukar känna till att ett domännamn översätts till en IP-adress, och att detta är den huvudsakliga uppgiften för DNS.

Detta är inte en artikel om hur DNS fungerar, det får lov att komma senare. Detta är istället en artikel om DNS över HTTPS (DoH). Jag tänkte förklara hur det fungerar, och även beskriva varför det finns. Det blir lite teknik, men inte så mycket.

Hur fungerar det?

DNS över HTTPS beskrivs i RFC8484 vilken publicerades 2018, men det är kanske egentligen de senaste året, eller åren, som det har börjat användas. Det är egentligen inte så komplicerat som vi kanske först kan tro. Du skickar en base64url-enkodad dns-query i wire-format till: https://<server>/dns-query?dns=<enkodad-sträng>

När du gjort det, får du tillbaka ett DNS-svar i wire-format, och kan du läsa DNS, har du således även svaret på din DNS-fråga.

Mycket svårare än så är det faktiskt inte. Visst, det förutsätter förståelse för DNS, men i övrigt är det inte mycket mer än så. DNS över HTTPS. Badabom badabipp.

Wire-format?

Men vänta, det här “wire”-formatet, vad är det för något? Det är DNS-frågan ordnad i network-byte order, även kallat Big Endian. Till skillnad från Little Endian som används av x86-CPU:er.

Så det du behöver göra är att packa ihop en DNS-fråga i en korrekt ordnad sträng av bytes, och så har du en wire-formaterad sträng du kan använda. Så här ser det ut i Python.

def construct_query(domain: str):
    """ Build and return very simple DNS-query packet in wire-format for <domain>. 

        This is not a useful function, it's for educational purposes only."""

    flags = 0x100 # RD-bit set

    id_ = random.randint(1, 65535)
    qdcount = 0x1 
    ancount = 0x0
    nscount = 0x0
    arcount = 0x0

    preamble = struct.pack(">HHHHHH", id_, flags, qdcount, ancount, nscount, arcount)
    ld = labeled_domain(domain)
    postamble = struct.pack(">HH", 0x1, 0x1)

    return preamble + ld + postamble

Network byte order är så som alla datapaketar skickas över internet. Du kan läsa mer om Endianness här .

Varför?

DoH försöker adressera i huvudsak två användningsfall:

  1. Förhindra enheter “på vägen” att påverka ett DNS-anrop.
  2. Möjliggöra för webb-applikationer att komma åt DNS via webbläsaren på ett bra sätt, utan att pajja Cross Origin Resource Sharing (CORS).

Privacy

Poängen med DoH är ju att hindra t.ex. en ISP, eller andra mellanliggande servrar, från att läsa din DNS-fråga. Ett vanligt DNS-anrop görs över port 53, okrypterat och fullt synligt. DNSSEC hjälper inte här eftersom det syftar till att endast påvisa autenticiteten på svaret.

DoH hjälper med detta genom att använda HTTPS som bärare istället för rakt över UDP/TCP.

Lite mer djup?

Först och främst behöver vi hitta en server som pratar DoH, där vi också kan experimentera med hur det fungerar.

Kolla här och välj en server, eller gå till följande adress i en ny tab i browsern:

https://doh.opendns.com/

För att kunna ställa en DNS-fråga över HTTPS behöver du göra följande:

  1. Sätta headern Content-type till application/dns-message. (Men åtminstone opendns skiter i vilket)
  2. Skicka en base64url-encodad query-sträng till /dns-query/dns=
  3. Profit!

Men, men… HTTPS-servern då, den måste jag väl ändå också slå upp?

Jo, du måste göra ett DNS-anrop på vanligt sätt för att kunna ansluta till en DoH-server, men det anropet är kanske inte jättekänsligt, det är ju bara en fråga om en DNS-server.

Base64URL encoding

En förändring av Base64-algoritmen för att kunna generera “säkra” strängar av tecken eftersom base64 inte kan göra det pga / och + t.ex.

Lite Python-kod

Jag har publicerat ett repository på Github om du är nyfiken på att se hur det hänger ihop kodmässigt.

Kärnan i ett DoH-anrop kan summeras enligt nedan:

# Send DNS-request to DoH-enabled server.
headers= {'Content-type': 'application/dns-message'}
response = requests.get(f"{OPENDNS_URL}/dns-query?dns={doh_request.decode()}", headers=headers)

Referenser

Privacydnsdohdomain name system

Christoffer Strömblad

Entusiastisk Jedi inom Cybersäkerhet

Effektiv cybersäkerhet genom underrättelser

Portable Network Graphics (PNG) och Steganografi