Linux - DNS Leak Script

We can use services like https://www.dnsleaktest.com/ to probe our network for DNS leaks, but it is not all the times we can access a web browser and most of the times we system administrators will be using the shell and when that's the case we can use this script, which can be found below as a backup if the original goes down.

Make sure to have python3 installed before executing the script.

tiago@desktop-linux:~/dnsleak$ sudo apt install python3
#!/usr/bin/env python3
# dnsleaktest v0.7 python CLI Created by Tugzrida(https://gist.github.com/Tugzrida)
from __future__ import print_function

version = "0.7"

import sys, socket, getopt, os.path
from uuid import uuid4
from json import loads, dumps
try:
    from urllib.request import urlopen, Request
    from urllib.error import HTTPError, URLError
except ImportError:
    from urllib2 import urlopen, Request, HTTPError, URLError

def formatIPorName(address):
    # Accepts an ip address or hostname, returns a formatted output (where the
    # resolved part is in parentheses), and the ip address
    try:
        ip = socket.getaddrinfo(address, None)[0][4][0]
    except (socket.herror, socket.gaierror):
        sys.exit("DNS server address is invalid or does not resolve!")

    if ip == address:
        try:
            return "{} ({})".format(address, socket.gethostbyaddr(address)[0]), address
        except (socket.herror, socket.gaierror):
            return "{} (No PTR)".format(address), address
    else:
        return "{} ({})".format(address, ip), ip

def dns_req(uuid, server, port):
    # Accepts a 36-character uuid, DNS server and port through which to lookup {uuid}.test.dnsleaktest.com

    req = b'\x42\x42\x01\x10\x00\x01\x00\x00\x00\x00\x00\x00\x24' + uuid.encode("utf-8") + b'\x04test\x0bdnsleaktest\x03com\x00\x00\x01\x00\x01'

    try:
        try:
            skt = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            skt.sendto(req, (server, port))
        except socket.gaierror:
            skt = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
            skt.sendto(req, (server, port))
        skt.settimeout(5)
        skt.recv(512)
    except (socket.timeout, socket.error) as e:
        sys.exit("Error connecting to DNS server: " + str(e))


testIDs = []

server = None
extended = False
port = 53
json = False

# Handle opts
try:
    opts, args = getopt.getopt(sys.argv[1:],"ejs:p:")
except getopt.GetoptError:
    sys.exit('''Usage: {} [-e] [-j] [-s server] [-p port]
    -e:         extended test, 36 lookups rather than the default 6
    -j:         json output
    -s server:  trace lookups through a specific DNS server, rather than the system defaults.
                In this mode, the behaviour is less like a leak test and more like a tracer.
    -p port:    specify a non-standard port for the DNS server
v{} Created by Tugzrida(https://gist.github.com/Tugzrida)'''.format(os.path.basename(__file__), version))
for opt, arg in opts:
    if opt == "-j": json = True
    if opt == "-e": extended = True
    if opt == "-s":
        if arg:
            formattedServer, server = formatIPorName(arg)
        else:
            sys.exit("DNS server address not provided!")
    if opt == "-p":
        if not any(i[0] == "-s" for i in opts): sys.exit("Must specify a server for port number to work!")
        try:
            if 0 <= int(arg) <= 65535:
                port = int(arg)
            else:
                sys.exit("DNS server port number must be in the range 0-65535!")
        except ValueError:
            sys.exit("DNS server port number must be an integer!")

# Begin the tests
if not json: print("Starting {}DNS leak test via {}{}...".format("extended " if extended else "", formattedServer if server else "system resolver", " port " + str(port) if port != 53 else ""))

for _ in range(36 if extended else 6):
    testIDs.append(str(uuid4()))

try:
    urlopen(Request("https://www.dnsleaktest.com/api/v1/identifiers", headers={"User-Agent": "dnsleaktestcli/{} (https://gist.github.com/Tugzrida)".format(version), "Content-Type": "application/json;charset=UTF-8"}), dumps({"identifiers": testIDs}).encode("utf-8"), timeout=5)
except HTTPError:
    pass # Endpoint always returns 400 Bad Request
except URLError:
    sys.exit("Unable to reach dnsleaktest.com!")

for testCount in range(36 if extended else 6):
    if server:
        dns_req(testIDs[testCount], server, port)
    else:
        socket.gethostbyname("{}.test.dnsleaktest.com".format(testIDs[testCount]))
    if not json: print("\rTest {}/{}".format(testCount+1, 36 if extended else 6), end="")
    sys.stdout.flush()

# Get the results
res = loads(urlopen(Request("https://www.dnsleaktest.com/api/v1/servers-for-result", headers={"User-Agent": "dnsleaktestcli/{} (https://gist.github.com/Tugzrida)".format(version), "Content-Type": "application/json;charset=UTF-8"}), dumps({"queries": testIDs}).encode("utf-8"), timeout=5).read().decode("utf-8"))

if json:
    print(dumps(res))
else:
    print("\rDiscovered DNS recursors are:")
    max_ip_len = max(len(x["ip_address"]) for x in res)
    if max_ip_len == 0: sys.exit('  No recursors found!')
    for server in res:
        if server["hostname"] == "None": server["hostname"] = "No PTR"
        print("  {srv[ip_address]:>{pad}} ({srv[hostname]}) hosted by {srv[isp]} in {srv[city]}, {srv[country]}".format(srv=server, pad=max_ip_len))

Let's create an alias to make life easier adding the below to our user bash profile :

tiago@desktop-linux:~/dnsleak$ vim ~/.bashrc
## DNS - dnsleak test
alias dns-leak="python3 /home/tiago/Desktop/servers/software/dnsleak/dnsleaktest.py"
#####################
~/.bashrc

And fininally let's source the file :

tiago@desktop-linux:~/dnsleak$ source ~/.bashrc

We can now use the command dns-leak to probe our DNS servers :

tiago@desktop-linux:~/dnsleak$ dns-leak 
Starting DNS leak test via system resolver...
Discovered DNS recursors are:
  141.101.106.30 (No PTR) hosted by Cloudflare in London, United Kingdom
  172.217.41.196 (No PTR) hosted by Google in Groningen, Netherlands
  172.217.41.207 (No PTR) hosted by Google in Groningen, Netherlands
    74.125.47.10 (No PTR) hosted by Google in Brussels, Belgium
   74.125.47.154 (No PTR) hosted by Google in Brussels, Belgium

Usage:
-e:         extended test, 36 lookups rather than the default 6
-j:         json output
-s server:  trace lookups through a specific DNS server, rather than the system defaults.
In this mode, the behaviour is less like a leak test and more like a tracer.
-p port:    specify a non-standard port for the DNS server
Created by Tugzrida(https://gist.github.com/Tugzrida)