Preventing DNS rebinding attacks using dnscrypt-proxy

Preventing DNS rebinding attacks using dnscrypt-proxy

dns resolution is an attack vector that can be leveraged to bypass the Same-origin policy enforced by all browsers.

The most insidious is DNS Rebinding, which this article very briefly covers before explaining a suggested mitigation based on dnscrypt-proxy ip blacklist capability.

The initiation of the attack requires a webpage (or iframe ) containing malicious javascript that performs repeated requests to hostnames under a subdomain of the malicious script’s origin domain.

Example (using evil.domain as origin):

var req = new XMLHttpRequest();
req.addEventListener("load", liveHost);
req.addEventListener("error", deadHost);

// for each ip:port of interest
req.open("GET", "http://<host name resolving to private ip>.evil.domain:<port>");
req.send();

The browser-run malicious script iterates over hostnames using a naming scheme which could look like this:

GET http://1-1-168-192.evil.domain/
GET http://2-1-168-192.evil.domain/
...

#####Edit 2018-07-29: Same-origin policy prevents this It can also iterate over ports (like a tool similar to netcat would):

GET http://1-1-168-192.evil.domain:80/
GET http://1-1-168-192.evil.domain:8080/

Or it could combine both to scan common ips and ports.

The authoritative nameserver for evil.domain is under the control of the attacker and configured in the example to resolve n4-n3-n2-n1 host names to ip address n1.n2.n3.n4 for each private ip in the private network range.

As far as the browser is concerned, all these requests fulfill the same origin policy, since the code is coming from (*.)evil.domain and only requesting things at (*.)evil.domain So the malicious javascript can now connect to anything in the private network(s) that the browser has a line-of-sight to.

The malicious javascript iterates over all ip:port combinations until one of those requests returns without error. Requests that succeed, or indicate that a port is open, enable further attacks. For a complete example of such malicious javascript, refer to this showcase.

As it turns out, there are potentially many such unprotected targets. As it also turns out, most public nameservers do not prevent name resolution from returning private ips.

If you want to prevent public nameserver responses that are in the private range, here is one way you could do it:

  • Install and use dnscrypt-proxy
  • Open the dnscrypt-proxy configuration (usually in /etc/dnscrypt-proxy/dnscrypt-proxy.toml)
  • Add an ip_blacklist section
[ip_blacklist]
  ## Path to the file of blocking rules (relative to the same directory)
  blacklist_file = 'ip-blacklist.txt'
  • Add an ip-blacklist.txt file in the same directory as the dnscrypt-proxy.toml file, and containing:
10.*
172.16.*
172.17.*
172.18.*
172.19.*
172.20.*
172.21.*
172.22.*
172.23.*
172.24.*
172.25.*
172.26.*
172.27.*
172.28.*
172.29.*
172.30.*
172.31.*
192.168.*
  • Restart dnscrypt-proxy
$ systemctl restart dnscrypt-proxy
  • Test that you can no longer resolve to private ip addresses, using Steve Gibson’s GRC rebindtest.com test domain.
$ nslookup net192.rebindtest.com
...
** server can't find net192.rebindtest.com: REFUSED

If you want to be reminded when your nameserver is no longer preventing local resolutions, a trick i use is to add a fragment in my shell initialization file (direnv, .textrc, …):

fatal() {
    echo '!!!' "$@" '!!!' 1>&2
    exit 1
}

if nslookup -type=txt debug.opendns.com. 2>&1 | grep -o "dnscrypt enabled" > /dev/null; then
    echo "> dnscrypt check: OK"
else
    fatal "dnscrypt not enabled"
fi

if nslookup net192.rebindtest.com | grep -q "REFUSED"; then
    echo "> dns rebinding check: OK"
else
    fatal "vulnerable to dns rebinding"
fi

Everytime i open a terminal, the dns status is checked.

> dnscrypt check: OK
> dns rebinding check: OK

Now you may think that this is superfluous, but the resolv.conf file can sometimes be replaced from under you. At least now, you’ll know when something’s wrong.