Some Notes on DNS Tunnels
I’ve been spending a decent number of frustrating evenings fighting with DNS tunnels and wanted to write a quick post to capture my thoughts while they were fresh in my head.
DNS tunnels work through the magical properties of DNS recursion. Without getting too far into it, here’s how that works:
- You ask your local DNS server if it knows where you can find
foo.bar.mydomain.com. It has no idea. If it is not configured for recursion, and most of the ones I’ve seen are, it sends a big fat nope to you and that’s that. Otherwise, one of two things will occur.
- A) It will forward your request to another DNS server or B) It will forward it to the root DNS servers. Since option A) eventually ends up in step 1 or option B, we’ll move forward.
- The root servers contain a list of all top-level domains (.com, .net, .ninja, etc.). The root name servers will have no idea where
foo.bar.mydomain.comis, but they do know which server is responsible for
.com, so it helpfully forwards your request there
.comname servers receive the request and similarly have no idea where
foo.bar.mydomain.comis, but they do know of a NS record for
mydomain.com. It forwards your request there.
- The name server for
mydomain.comreceives the query and looks at the list of zones it is authoritative for and see that it, too, doesn’t know much about
foo.bar.mydomain.com, but it does know where to find the name server for
bar.mydomain.comand passes your query along.
- Finally your DNS query arrives at the name servers for
bar.mydomain.com. Since they’re authoritative for the zone, they know exactly where to find
foo.bar.mydomain.comand sends a response, allowing you to do whatever it is that you were doing to a hostname. Great.
Tunneling Through DNS
If we keep the above example in mind, let’s imagine that you were on a locked down network with a recursive DNS server and I was in possession of the
bar.mydomain.com DNS server.
If you wanted to send me a message, you could just do something like this:
and I would see a DNS query come in for hi-liam. I could then send a response using a TXT record that says something like ‘sup?’. You could then respond with a:
And we’d have a painfully slow but pretty stealthy conversation. DNS tunnelling works more or less the same way, but is automated.
So this seems pretty neat, right? We’ve got a covert comms channel baked into the architecture of the internet that will almost always be working.
The cake is a lie, man.
You see, not really, because DNS tunnels exist in this strange limbo where they’re feasible enough to exist, but not widely useful enough to actually have solid solutions in place.
You’re also swimming upstream against some decent technical restrictions imposed on us as a result of us co-opting the backbone of the internet for covert C2 traffic.
I tested out four DNS tunnels with varying degrees of success. I stuck my DNS server in Azure since I’ve got credits fo’ DAYS, and configured a name server for a $1 domain I’d purchased.
Here’s the DNS configuration I used for the
@ 10800 IN SOA ns1.gandi.net. hostmaster.gandi.net. 1496496973 10800 3600 604800 10800 ns1 300 IN A <DNS SERVER IP> tunnel 300 IN NS ns1.mydomain.com.
Let’s unpack what I’ve just done. The
ns1 A record is just a normal host record that tells us where to find a host. In my previous example, this would be
The next line is where the magic happens. What we’re doing here is defining name servers for the
tunnel.mydomain.com domain (or
bar.mydomain.com domain if you’re stuck on the previous example still). Our messages will be sent in the form
tunnel part names the subdomain, and the
ns1.mydomain.com tells us where to find the DNS servers for that domain. In practice, these are usually in different zones. Here’s what the NS records for smallsec.ca look like:
; <<>> DiG 9.8.3-P1 <<>> ns smallsec.ca ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46359 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;smallsec.ca. IN NS ;; ANSWER SECTION: smallsec.ca. 86277 IN NS dora.ns.cloudflare.com. smallsec.ca. 86277 IN NS sam.ns.cloudflare.com. ;; Query time: 48 msec ;; SERVER: 184.108.40.206#53(220.127.116.11) ;; WHEN: Thu Jun 8 06:14:48 2017 ;; MSG SIZE rcvd: 83
These NS records tell us where to send queries for the domain in question.
I got Iodine up and running with minimal fuss, but it was slow as fuck. One neat thing about it is that it tunnels IP, so you can route through it if you like, making this totally transparent to the tools you’re using.
First we start the server:
[email protected]:~$ sudo iodined -f -DD -c -4 -P secretpassword123 10.10.10.1 bar.mydomain.com Debug level 2 enabled, will stay in foreground. Add more -D switches to set higher debug level. Opened dns0 Setting IP of dns0 to 10.10.10.1 Setting MTU of dns0 to 1130 Opened IPv4 UDP socket Listening to dns for domain bar.mydomain.com
Then the client:
[email protected]:~$ sudo iodine -I 1 -L 1 -f -P secretpassword123 bar.mydomain.com
Here’s what the initial communication looks like:
RX: client 18.104.22.168, type 10, name yrb54k.bar.mydomain.com TX: client 22.214.171.124, type 10, name yrb54k.bar.mydomain.com, 48 bytes data <snip> RX: client 126.96.36.199, type 10, name paaap5za.bar.mydomain.com PING pkt from user 0, ack for downstream 0/0 RX: client 188.8.131.52, type 10, name paaap5zi.bar.mydomain.com PING pkt from user 0, ack for downstream 0/0
This shows the first lookup (foo.bar.mydomain.com from our first example) and then I’ve cut out the client/server negotiation. The last two RX/PING pairs are just keepalives. Neat!
This… didn’t work. A tunnel would start, then disconnect and refuse to connect again. I don’t know why, but I discovered dnscat2 before I found a solution and then I no longer cared.
I read about TUNS and got really excited, I think mainly because I’d been swearing at DNS2TCP for days. The author wrote a paper on the topic of DNS tunneling, which is definitely worth a read, that introduces the tool. Here’s the part that made me giddy:
an IP over DNS tunnel, focused on simplicity and protocol compliance. Comparison of TUNS and the other implementations showed that this approach is successful: TUNS works on all the networks we tested, and provides reasonable performance despite its use of less efficient encapsulation techniques, especially when facing degraded network conditions
I kept coming back to DNS2TCP for some reason and reached out to a colleague for help who sent me to dnscat2. I’ll let the author speak for himself:
dnscat2 strives to be different from other DNS tunneling protocols by being designed for a special purpose: command and control.
This isn’t designed to get you off a hotel network, or to get free Internet on a plane. And it doesn’t just tunnel TCP.
It can tunnel any data, with no protocol attached. Which means it can upload and download files, it can run a shell, and it can do those things well. It can also potentially tunnel TCP, but that’s only going to be added in the context of a pen-testing tool (that is, tunneling TCP into a network), not as a general purpose tunneling tool. That’s been done, it’s not interesting (to me).
It’s also encrypted by default. I don’t believe any other public DNS tunnel encrypts all traffic!
This one gets my vote. It’s also somehow faster than the others, which was a nice surprise.
There’s a lot to like about DNS tunnels. I love that they’re built into the infrastructure. I love that recursion is almost always enabled and provides a way for this traffic to sneakily exit the network. There are almost certainly ways to detect and block this traffic, but I think it’s unlikely most sites will implement them in the near future.