wiki:doc/PreventingDnsLeaksInTor

  • Copyright (c) 2005 tyranix
  • Distributed under the X11 license
  • See LegalStuff for a full text

Overview

Why is this necessary?

When you use Tor, it's setup as a SOCKS4 or 5 proxy but not SOCKS4a. This means DNS requests are resolved using the normal method of /etc/resolv.conf. This exposes you because your ISP (or whoever is in /etc/resolv.conf) can view your plaintext DNS queries.

Example scenario

In the case of using Firefox through Tor, it means all of the actual transfers (pages, images, video, etc) are anonymized through Tor but your ISP can see every site you visit by looking at what DNS names you request.

While they can only assume you are visiting these sites because the actual traffic is encrypted, it's best to not let anyone see what you are doing for privacy concerns.

For instance, if you request some site google.com without dsocks but with Tor, you will get the HTML back encrypted but to lookup the IP of google.com, programs use /etc/resolv.conf to ask someone what the IP is. Using logs of google.com and your ISP's DNS server and traffic logs, it may be possible for them to determine whether you accessed that site or just looked the name up (not 100% but possibly a correlation).

This tutorial is designed to prevent such information leaks.

Purpose of tutorial

This tutorial will setup dsocks to answer DNS queries. This means you will no longer have DNS leaks from Tor. Dsocks is also setup with systrace so dsocks itself runs as nobody.

Systrace allows it to bind to port 53 by allowing that call as root. Or you can setup your firewall to redirect requests to port 53 to a different port above 1023 so you don't have to run any of it as root.

This way, you do not have to trust dsocks any more than necessary. If there is malicious code in dsocks, it is only being executed as the nobody user. And your ISP doesn't see which sites you are looking up because all DNS requests go through Tor.

For the best protection, I would setup your firewall to deny all connections to external DNS when you have this setup properly.

Note: This works through ssh port forwards. In my case, I run a browser in GNU/Linux that connects via ssh port forwards to an OpenBSD machine where Tor and dsocks run. I can firewall off port 53 for the GNU/Linux machine which shows the DNS requests are answered through the OpenBSD machine.

If you want the same setup, there's another tutorial I wrote on this site for that.

Assumptions

Dsocks assumes that Tor is listening on port 9050 which is the default port for Tor. Unless you have modified Tor's config, this is not a problem.

This tutorial is written and tested with OpenBSD. But it should work in GNU/Linux as long as you have a systrace patched kernel.

For GNU/Linux, you'll have to change most of the paths because OpenBSD uses /usr/local/lib/python2.3 while Debian for instance uses /usr/lib/python2.3.

Additionally, the user and group ID for running systrace will probably have to be changed.

Use root.root instead of root.wheel for chown commands.

And the package management commands would be different.

TODO

It would be great if someone could test this on GNU/Linux. Or follow everything from OpenBSD.

Thanks

Thanks to an undeadly.org article about the OpenBSD chrooted Tor where someone mentioned dsocks. I'm now using dsocks so I'll pass along the instructions.

Install python

Note: For OpenBSD 3.6, there are security updated packages that you should use. This means you should be running python 2.3.5.

You probably don't need to get all of these but I use python outside of this program so I like extra modules.

mkdir ~/packages
cd ~/packages
wget ftp://ftp.openbsd.org/pub/OpenBSD/3.6/packages/i386/python{,-idle,-mpz,-tests,-tkinter,-tools}-2.3.5.
tgz
pkg_add *.tgz

and now you can remove the directory.

Get the source for dsocks and dpkt

Dsocks and dpkt are both written in Python. Dsocks uses dpkt so you'll need to get both.

Create a directory for storing them

mkdir ~/dsocks
cd ~/dsocks

Get dsocks and dpkt

wget http://www.monkey.org/~dugsong/{dsocks/dsocks-1.2,dpkt/dpkt-1.1}.tar.gz

Extract them both and move dpkt to dsocks directory

You can either install these using the provided setup.py (which will install them in some python location) or you can just use the directory as is.

I prefer to use it as is because I'm going to be moving it to a different location than the normal python libraries.

tar -zxvf dsocks-1.2.tar.gz
tar -zxvf dpkt-1.1.tar.gz

Now move the dpkt source code into dsocks. If you don't do this, you'll need to move dpkt directory to somewhere the python interpreter will find.

mv dpkt-1.1/dpkt dsocks-1.2/dpkt

Byte compile the source

Then compile them because you want to make this read only. Normally python does this when you execute the files. However, when you call compileall, you only compile it to byte-code instead of executing the code.

This will recursively compile all python code it finds in that directory.

/usr/local/bin/python /usr/local/lib/python2.3/compileall.py dsocks-1.2

Create a new location for the source

As root, create a location for the source.

This should also be made read only because we already byte-compiled the source. This means there are *.pyc files where ever *.py are found in dsocks-1.2.

This has to be accessible for everyone because we're going to run this program as the nobody user.

mkdir /opt
cp -Rp dsocks-1.2 /opt/

chown -R root.wheel /opt/dsocks-1.2
find /opt/dsocks-1.2 -type f -exec chmod 444 '{}' \;
find /opt/dsocks-1.2 -type d -exec chmod 555 '{}' \;

chmod 555 /opt

Option 1: Use systrace

This is my systrace file. Python has a lot of fsread calls so I used a regex to avoid naming all of those.

The file is called /root/.systrace/usr_local_bin_python2_3 and should be copied to /etc/systrace/ after you have tested it.

Notice that the bind call has as root which escalates permissions for that one call. All the others will be run as whatever user you specify in systrace (with the -c option).

This also means that you must execute systrace as root for this escalation to occur. The program itself will run the nobody user.

Policy: /usr/local/bin/python2.3, Emulation: native
        native-issetugid: permit
        native-__sysctl: permit
# Memory
        native-mprotect: permit
        native-mmap: permit
        native-mquery: permit
        native-munmap: permit
# Denied to avoid information leaks
        native-fsread: filename eq "/" then deny
        native-fsread: filename eq "/etc/." then deny
        native-fsread: filename eq "/home" then deny
# System files
        native-fsread: filename match "/<non-existent filename>: *" then permit
        native-fsread: filename eq "/etc/malloc.conf" then permit
        native-fsread: filename match "/usr/lib/libc.so.*" then permit
        native-fsread: filename match "/usr/lib/libm.so.*" then permit
        native-fsread: filename match "/usr/lib/libpthread.so.*" then permit
        native-fsread: filename match "/usr/lib/libutil.so.*" then permit
        native-fsread: filename match "/usr/lib/libssl.so.*" then permit
        native-fsread: filename match "/usr/lib/libcrypto.so.*" then permit
        native-fsread: filename eq "/usr/local/lib" then permit
        native-fsread: filename eq "/usr/local/bin/python" then permit
        native-fsread: filename match "/usr/local/bin/python2.?" then permit
        native-fsread: filename match "/usr/local/lib/python2.?" then permit
        native-fsread: filename match "/usr/local/lib/python2?.zip" then permit
        native-fsread: filename re "/usr/local/lib/python2\.[0-9]+/.*" then permit
        native-fsread: filename eq "/usr/local/lib/site-python" then permit
        native-fsread: filename re "/usr/local/lib/site-python/.*" then permit
        native-fsread: filename match "/usr/share/zoneinfo/*/*" then permit
        native-fsread: filename eq "/usr/share/nls/C/libc.cat" then permit
        native-fsread: filename eq "/var/run/ld.so.hints" then permit
# User files
        native-fsread: filename eq "/root/.systrace/." then permit
        native-fsread: filename match "/opt/dsocks-1.?/." then permit
        native-fsread: filename re "/opt/dsocks-1\.[0-9]+" then permit
        native-fsread: filename re "/opt/dsocks-1\.[0-9]+/.*" then permit
# File system
        native-chdir: filename match "/opt/dsocks-1.?" then permit
        native-fchdir: permit
        native-fcntl: permit
        native-fstat: permit
        native-fstatfs: permit
        native-getdirentries: permit
# Read/write
        native-close: permit
        native-ioctl: permit
        native-lseek: permit
        native-poll: permit
        native-read: permit
        native-write: permit
# Signals
        native-sigaction: permit
        native-sigprocmask: permit
        native-sigreturn: permit
# User information
        native-getpid: permit
        native-setgid: gid eq "32767" then permit
        native-setuid: uid eq "32767" and uname eq "nobody" then permit
# Process control
        native-exit: permit
        native-pipe: permit
# Time
        native-clock_gettime: permit
        native-gettimeofday: permit
        native-setitimer: permit
# Resources
        native-break: permit
        native-getrlimit: permit
# Networking
        native-bind: sockaddr eq "inet-[127.0.0.1]:53" then permit as root
        native-connect: sockaddr eq "inet-[127.0.0.1]:9050" then permit
        native-recvfrom: permit
        native-sendto: permit
        native-socket: sockdom eq "AF_INET" and socktype eq "SOCK_STREAM" then permit
        native-socket: sockdom eq "AF_INET" and socktype eq "SOCK_DGRAM" then permit
        native-socket: sockdom eq "AF_UNIX" and socktype eq "SOCK_DGRAM" then permit
        native-socket: sockdom eq "AF_UNIX" and socktype eq "SOCK_STREAM" then permit

Option 2: Use firewall redirection

Instead of using systrace to allow tor-dns-proxy.py to bind to port 53, you could setup your firewall to redirect requests to port 127.0.0.1:53 to 127.0.0.1:7777 where 7777 is some arbitrary port greater than 1023 so you don't have to run systrace as root.

If you do this, you can run everything as user nobody (or any other user) by removing the "as root" part to the above systrace policy and setting up a redirection.

Here's how you would do it with OpenBSD's pf. See this part of the FAQ for details: http://www.openbsd.org/faq/pf/rdr.html

# Redirect internal requests to port 53 to port 7777.
rdr on lo0 proto udp from 127.0.0.1 to 127.0.0.1 port 53 -> 127.0.0.1 port 7777

Note that there is a disadvantage to doing this. Because you are redirecting to port 7777, if another user can start up a program binding to port 7777 or crash yours and start theirs, now your requests are going through a malicious DNS server.

Using systrace's escalation, a normal user cannot do this same replacement without finding some systrace or root exploit to circumvent the protection.

Change /etc/resolv.conf

I have two /etc/resolv.conf: one for Tor and one for normal operation. I switch between them by copying over the existing /etc/resolv.conf with either /etc/resolv.conf.tor or /etc/resolv.conf.normal

This is what you want for resolving DNS through Tor.

lookup file bind
nameserver 127.0.0.1

Start up tor-dns-proxy

This will run as the user nobody. It only escalates the bind call so we can bind below port 1024 (53 in DNS case).

/bin/systrace -a -e -c 32767:32767 /usr/local/bin/python2.3 /opt/dsocks-1.2/tor-dns-proxy.py

Note: This does not background so you'll have to kill it with control-c to exit. When you run it as a daemon, you'll have to background the process (as shown below).

Try it out

You can use any application to test it out. Here, I'll use wget.

# If you use privoxy with tor (recommended)
export http_proxy=http://127.0.0.1:8118/
# Otherwise, plain tor
# export http_proxy=http://127.0.0.1:9050/
wget http://www.google.com

You can also create/modify ~/.wgetrc to set this permanently.

Make it permanent

Note: I haven't rebooted yet so this part hasn't been tested yet.

Add this to your /etc/rc.local.

# Tor-ized DNS to prevent DNS leaks. Runs as 'nobody' except for an escalated
# bind to port 53 by systrace.
if [ -f /opt/dsocks-1.2/tor-dns-proxy.py ]; then
        echo -n ' tor DNS':
        /bin/systrace -a -c 32767:32767 /usr/local/bin/python2.3 /opt/dsocks-1.2/tor-dns-proxy >/dev/null
2>&1 &
fi

Other examples from dsocks author

These are from his webpage, copied here for reference:

copy a file remotely thru a home SSH gateway, using an internal DNS name

ssh -D 1080 home_gw
dsocks.sh scp /etc/motd internal_host:/tmp

web surf anonymously thru Tor using Firefox (configured to use the Tor SOCKS proxy), with no DNS leaks (using the included Tor DNS proxy):

tor >/dev/null 2>&1 &
echo "nameserver 127.0.0.1" > /etc/resolv.conf
tor-dns-proxy.py >/dev/null 2>&1 &
firefox

SSH anonymously thru Tor:

tor >/dev/null 2>&1 &
dsocks-torify.sh ssh example.com
Last modified 3 years ago Last modified on Sep 20, 2011 4:38:01 PM