Fun with NMAP NSE Scripts and DOH (DNS over HTTPS)

Published: 2021-01-25
Last Updated: 2021-01-25 17:49:00 UTC
by Rob VandenBrink (Version: 1)
0 comment(s)

DOH (DNS over HTTPS) has been implemented into the various browsers over the last year or so, and there's a fair amount of support for it on public DNS services.  Because it's encrypted and over TCP, the mantra of "because privacy" has carried the day it looks like.  But why do network and system admins hate it so?

First of all, any name resolution that goes outside the organization, especially if it's encrypted, can't be easily logged.  I get that this is the entire point, but there are several attacks that can be prevented with simple DNS monitoring and sink-holing (blocking known malicious domains), and several attacks that can be mounted using just DNS (delivering malware via DNS TXT records for instance).   

What about DNS Tunneling you ask?  DNS tunnelling over DOH seems like a bit of a silly exercise - unless you're decrypting at your perimeter, DNS tunnelling over DOH is just going to look like HTTPS - you might as well just use HTTPS.

Why do privacy advocates tend to lose this debate at work?

For starters, the expecation of 100% privacy, but then the desire to hold IT and Security folks accountable for any breach or security incident, while you've got their hands tied doesn't hold water.  Especially for decryption, most organizations have broad exceptions by category - for instance, most organizations will not decrypt or inspect banking or financial information, interaction with government sites (taxes and so on), or healthcare sites of any kind.  Believe me, we don't want your banking password any more than we want your AD password!  So out of the gate, both the written and technical policies around decryption for most organizations focus on the individual's privacy, the goal is normally to protect against malware and attacks, HR violations (adult sites for instance), and illegal activity that could put the organization in jeopardy.

Also, the phrase "epxectation of privacy" is key here.  If you are at work, you don't usually have that - you're using the organizations systems and resources, and going about the business of the organization, and you've likely signed an Acceptable Use Policy (or something that covers that same ground) to that effect.  This protects you in that it defines what monitoring the company has, and protects the company in case any of it's employees do anything illegal while at work.  Note that I am not a Lawyer, nor do I play one on TV .. but I have been involved in more than a few "illegal stuff at work" cases over the years (thankfully not as a direct participant) - this stuff is important for both the company and the individuals!

So, with all the politics done, what does a DOH request look like?  The simple approach is to use the dns-json method, as outlined below - it'll save you base64 encoding the requests.  Let's start with a raw request in curl, then refine it a bit:

json formatted data:

curl -s -H 'accept: application/dns-json' 'https://1.1.1.1/dns-query?name=www.cisco.com&type=AAAA'
{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"www.cisco.com","type":28}],"Answer":[{"name":"www.cisco.com","type":5,"TTL":3600,"data":"www.cisco.com.akadns.net."},{"name":"www.cisco.com.akadns.net","type":5,"TTL":300,"data":"wwwds.cisco.com.edgekey.net."},{"name":"wwwds.cisco.com.edgekey.net","type":5,"TTL":21600,"data":"wwwds.cisco.com.edgekey.net.globalredir.akadns.net."},{"name":"wwwds.cisco.com.edgekey.net.globalredir.akadns.net","type":5,"TTL":3600,"data":"e2867.dsca.akamaiedge.net."},{"name":"e2867.dsca.akamaiedge.net","type":28,"TTL":20,"data":"2600:1408:5c00:3bc::b33"},{"name":"e2867.dsca.akamaiedge.net","type":28,"TTL":20,"data":"2600:1408:5c00:388::b33"}]}

Looks pretty straightforward - very much like any API that you might be used to.  DOH is an HTTPS request like any other, but with a specific user-agent string and a specific path on the target server (dns-query).  This raw output is great if you're a python script, but let's fix up the formatting a bit so it's a bit more "human readable"

curl -s -H 'accept: application/dns-json' 'https://1.1.1.1/dns-query?name=www.cisco.com&type=AAAA' | jq
{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": false,
  "CD": false,
  "Question": [
    {
      "name": "www.cisco.com",
      "type": 28
    }
  ],
  "Answer": [
    {
      "name": "www.cisco.com",
      "type": 5,
      "TTL": 3597,
      "data": "www.cisco.com.akadns.net."
    },
    {
      "name": "www.cisco.com.akadns.net",
      "type": 5,
      "TTL": 297,
      "data": "wwwds.cisco.com.edgekey.net."
    },
    {
      "name": "wwwds.cisco.com.edgekey.net",
      "type": 5,
      "TTL": 21597,
      "data": "wwwds.cisco.com.edgekey.net.globalredir.akadns.net."
    },
    {
      "name": "wwwds.cisco.com.edgekey.net.globalredir.akadns.net",
      "type": 5,
      "TTL": 3597,
      "data": "e2867.dsca.akamaiedge.net."
    },
    {
      "name": "e2867.dsca.akamaiedge.net",
      "type": 28,
      "TTL": 17,
      "data": "2600:1408:5c00:388::b33"
    },
    {
      "name": "e2867.dsca.akamaiedge.net",
      "type": 28,
      "TTL": 17,
      "data": "2600:1408:5c00:3bc::b33"
    }
  ]
}


now with just the data values parsed out:

curl -s -H 'accept: application/dns-json' 'https://1.1.1.1/dns-query?name=www.cisco.com&type=AAAA' | jq | grep data | tr -s " " | cut -d " " -f 3 | tr -d \"

www.cisco.com.akadns.net.
wwwds.cisco.com.edgekey.net.
wwwds.cisco.com.edgekey.net.globalredir.akadns.net.
e2867.dsca.akamaiedge.net.
2600:1408:5c00:3bc::b33
2600:1408:5c00:388::b33

This is all well and good for a shell script, but if you need to test more servers, what other tools can you use?  With the emphasis on script and multiple, I wrote a short NSE script for NMAP that will make arbitrary DOH requests:

First of all, the syntax is:

nmap -p433 <target> --script=dns-doh <DNS server> --script-args query=<query type>,target=<DNS lookup value>

>nmap -p 443 --script=dns-doh 1.1.1.1 --script-args query=A,target=isc.sans.edu

Starting Nmap 7.80 ( https://nmap.org ) at 2021-01-25 12:04 Eastern Standard Time

Nmap scan report for one.one.one.one (1.1.1.1)

Host is up (0.027s latency).

 

PORT    STATE SERVICE

443/tcp open  https

| dns-doh:

|   Answer:

|

|       type: 1

|       name: isc.sans.edu

|       TTL: 7

|       data: 45.60.103.34

|

|       type: 1

|       name: isc.sans.edu

|       TTL: 7

|       data: 45.60.31.34

|   AD: false

|   Status: 0

|   RA: true

|   Question:

|

|       type: 1

|       name: isc.sans.edu
|   CD: false
|   RD: true
|_  TC: false

Nmap done: 1 IP address (1 host up) scanned in 9.08 seconds

Looking at the code (comments are in-line), after all the setup and syntax checking, this is essentially a 3 line script:

local nmap = require "nmap"

local shortport = require "shortport"

local http = require "http"

local stdnse = require "stdnse"

local string = require "string"

local table = require "table"

local json = require "json"

local strbuf = require "strbuf"

 

description = [[

Performs a DOH lookup against the target site

variables: t = <target of dns query>

           q = <dns query type>

]]

---

-- @usage

-- nmap <target> --script=doh <DNS server> --script-args query=<query type>,target=<DNS lookup value>

--

-- @output

-- 443/tcp open   https

-- | results of query

--

---

author = {"Rob VandenBrink","rob@coherentsecurity.com"}

license = "Creative Commons https://creativecommons.org/licenses/by-nc-sa/4.0/"

categories = { "discovery" }

portrule = shortport.http

action = function(host,port)

     -- collect the command line arguments

     local query = stdnse.get_script_args('query')

     local target = stdnse.get_script_args('target')

     -- input checking - check that both arg values are present and non-zero

     if(query==nil or query == '') then

         return "DNS query operation is not defined (A,AAAA,MX,PTR,TXT etc)"

     end

     if(target==nil or target=='') then

         return "DNS target is not defined (host, domain, IP address etc)"

     end

     -- construct the query string, the path in the DOH HTTPS GET

     local qstring = '/dns-query?name='..target..'&type='..query

     -- define the header value (which defines the output type)

     local options = {header={}}

     options['header']['accept'] = 'application/dns-json'

     -- Get some DOH answers!

     local response = http.get(host.ip, port.number, qstring, options)

     -- convert results to JSON for more legible output

     local stat, resp =json.parse(response.body)

     return resp

end

 

The dns-doh.nse script is available and is maintained at: https://github.com/robvandenbrink/dns-doh.nse

If you find any issues with this code, by all means use our comment section to report them, or ping me via git

===============
Rob VandenBrink
rob@coherentsecurity.com

0 comment(s)
ISC Stormcast For Monday, January 25th, 2021 https://isc.sans.edu/podcastdetail.html?id=7342

Comments


Diary Archives