+---------------------------------------------------------------+ | Quick Tunneling IP over DNS guide Alejandro Ramos | | 29/Mar/2005 v0.2 aramosf@unsec.net | | http://www.digitalsec.es | +---------------------------------------------------------------+ / 1.- Introduction / +--------------------+ The aim of this guide is to show how to create an IP tunnel over the DNS protocol to obtain Internet access in a restricted environment where the only available outbound service is DNS for resolving Internet domains. This technique is very useful for wireless lans that are protected by captive portal authentication. You'll recogize this situation, you can join the WLAN only when you have been authenticated via HTTP, usually after paying for example at airports, cafes and hotels. These places usually have full DNS available to resolve Internet domains, which is an essential requirement to the network architecture. The following text will attempt to show a practical example the aforemenationed protocol encapsulation technique using a demostration WLAN architecture. The operation of our encapulation is simple. First we'll have a DNS server that has been modified to answer fixed queries encoded in Base32 if the query is done by means of CNAME or Base64 if they are TXT records. The client will be decode the query and generate the correct IP packet. Currently there are two applications that are able to build a tunnel over DNS: nstx (http://nstx.dereference.de/nstx/) and ozymandns (http://www.doxpara.com/). Lets do a quick comparison between the two. ozymandns ========= Advantages: * It's written in perl, so it can be ported to any OS with this interpreter available. * It's easy and quick to configure. Disadvantages: * It only allows you to perform an SSH tunnel. * The software is unstable. nstx ========= Advantages: * It allows encapsulation of any kind of application layer protocol. * The software is older and more stable. * There are already binary packages for some distributions (debian sid). Disadvantages: * Server and client *must* be linux systems. * It's necessary to create a tunnel with tun/tap. Apart from these applications there are others that allow us to send and recive files over DNS or even use it to encapsulate other protocols such as VoIP. DANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGER A D N Neither of the systems described use authentication, so A G somebody that knew the configuration would be able to easily N E tunnel DNS. G R E DANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGERDANGER / 2.- Requirements / +-------------------+ I'll start out assuming you have basic knowledge about the DNS protocol and its specific record types: A, NS, TXT... (see link in reference section for relevent RFCs) You can see an example of a typical configuration below, where the hosts involved are marked between asterisks: [*PC client*] ~~ wireless ~~ [ AP ] | [ switch ] <- ( outbound DNS allowed ) | [ router ] | /\/\/\/\/\ [*DNS server* ]-------|Internet|-----[*tunnel server *] \/\/\/\/\/ Our players will then be: - A client from which we will perform the tunnel. - A DNS server that we have full control over (i.e. we own/0wn it) - Tunnel server, where the application server will run. - Client access to a DNS server. See the following definitions as a reference for the example in this text: - DNS Server (that we control): 10.1.2.2 will be the DNS with access as a client and the one that should be able to resolve our domain. - TunnelServer : this is one of our systems and we'll install the query server part, you'll need to be able to answer a DNS request therefore port 53/udp should be open to the Internet. - DNS Server : with an arbitrary IP, any one will do. / 3.- DNS configuration / +-------------------------+ If you understand the concept of recursive DNS there should be no problem in configuring the domain to be able to raise the tunnel. In our example the domain 'digitalsec.es' is going to be used as a reference. The zone file of digitalsec.es is modified, in this case (fedora core 3 with named chrooted): /var/named/chroot/var/named/db.digitalsec.es, and the next records are added: t.digitalsec.es. IN NS tun.digitalsec.es. tun IN A 65.73.147.191 With this configuration we are sending to the host tun.digitalsec.es (65.73.147.191) all queries that arrive to t.digitalsec.es. That is, if somebody asks for 'foo.t.digitalsec.es' the DNS server listening in t.digitalsec.es (65.73.147.191) will be the one handling the query. So the host found at 65.73.147.191 should have the DNS tunnel server. This configuration is commonly used for nstx and ozymandns. The tests performed were done with bind and everything worked properly, they were setup as per the debian package documentation. If you are using djbdns, it is necessary to patch first before using nstx. So just take the the easier way and use bind. / 4.- ozymandns configuration / +--------------------------------+ The software can be downloaded from its official site: http://www.doxpara.com/, currently the most recent version is http://www.doxpara.com/ozymandns_src_0.1.tgz. For ozymandns to work some CPAN modules are needed, detailed information can be found about their installation in: http://www.cpan.org/modules/INSTALL.html. Many distributions have some of these modules as packages, so you probably won't even need to manually build/install them. You can also use perl -MCPAN -e shell if you'd like. The scripts to enable the tunnel are: nomde.pl (server) and droute.pl (client), the rest of applications included in the tar are for other purposes. Once everything is properly installed, the scripts should show the help when executed: # perl nomde.pl nomde 0.1: Experimental DNS Server Component of: OzymanDNS Dan Kaminsky(dan@doxpara.com) Usage: nomde -l 10.0.1.11 servername.foo.com Options: -i [ip address]: IP address to host for all A requests -f [filename] : Filename to host in TXT records [b64] -p [name] : Name/IP to return for reverse lookups[ptr] -L [name:host:port]: Forward function to address, port (Default: sshdns:127.0.0.1:22) Bear in mind this application is a beta one, and there are still some failures; the help doesn't completely fit the actual functionality and with "nomde.pl" specifically, the -L parameter doesn't work as expected. This will be clarified below, when the server is configured. - SERVER SIDE - : On this host we'll setup the tunnel server, on the host tun.digitalsec.es (65.73.147.191) to be precise. The command below will let this side ready: # ./nomde.pl -i 127.0.0.1 t.digitalsec.es creating TCP socket...done. creating UDP socket...done. waiting for connections... By default this allows doing ssh to 127.0.0.1 (that is to the server that performs the tunnel or tun.digitalsec.es) to port 22, this is theoretically modifiable with the -L option, but this doesn't work and if you need this option to change (other IP and port) you need to hardcode it in the source code: By changing line 32 in nomde.pl: "Localforward"=> \$opts{forward} to this : "Localforward=s"=> \$opts{forward} The problem will be solved. Now, this command will let us connect by ssh to port 2222 of 82.165.25.126: # ./nomde.pl -i 127.0.0.1 -L sshdns:82.165.25.126:2222 t.digitalsec.es creating TCP socket...done. creating UDP socket...done. waiting for connections... Another problem in the script makes the connection to close from time to time, we can use a ghetto solution to avoid the problem. This will restores the connection as needed: # while true; do ./nomde.pl -i 127.0.0.1 t.digitalsec.es; done - CLIENT SIDE - : We'll follow the same steps than in the server by first installing the CPAN modules required for the script to work properly: # ssh -p2222 -o ProxyCommand="droute.pl sshdns.t.digitalsec.es" \ aramosf@82.165.25.126 With this, a SSH connection will be established to the destination system shown in the server side part (option -L). You should notice the "sshdns" label before t.digitalsec.es. Another thing to be aware of is that user and IP address will only be used when verifying if there is a private key or if the remote system is a known host. If you wished to change the SSH target address you should do it in the tunnel server (nomde.pl) with -L option. The droute.pl script, can be used on windows by using perl and ssh from cygwin, using the CPAN modules. It might work with ActiveState perl and and another SSH client if the "ProxyCommand" (from openssh client) option is supported. / 5.- nstx configuration / +--------------------------+ For nstx to work you should have built the kernel with ethertap/tun support, both client and server. Device Drivers ---> Networking support ---> Universal TUN/TAP device driver support Sometimes it is necessary to create the device by hand: # mkdir /dev/net # mknod /dev/net/tun c 10 200 NOTE: Configuration of tun0, both in the server and client, is performed AFTER starting the applications. - SERVER SIDE - : The installation example will be done, like with ozymandns in tun.digitalsec.es (65.73.147.191). Download the software from: http://nstx.dereference.de/nstx/. Currently, the last version is: nstx-1.1-beta6.tgz. As explained previously, it is possible to find a package with the needed binaries. In debian's sid distribution, the package name is "nstx", and intalling it with "apt-get install nstx" should be enough, you just need to configure it in the file: /etc/default/nstx. Once uncompressed and built: # tar -zxvf nstx-1.1-beta6.tgz # cd nstx-1.1-beta6 # make Now, you'll have the needed binaries for execution in the server side, that will be executed with the option to change UID to nobody and leave the server in background: # ./nstxd -u nobody -D t.digitalsec.es. Opening tun/tap-device... using device tun0 Please configure this device appropriately (IP, routes, etc.) Opening nameserver-socket... listening on 53/UDP Load the TUN module: # modprobe -a tun At last, the interface is configured with an internal arbitrary IP: # ifconfig tun0 172.26.0.2 netmask 255.255.255.0 - CLIENT SIDE - : Download and built of the client side of nstx: Load the TUN module: # modprobe -a tun Execute the client pointing to t.digitalsec.es and to the DNS with client access, in the example 10.1.2.2: # ./nxstcd t.digitalsec.es 10.1.2.2 Finally, configure the device tun0 with an interface in the same network than the server: # ifconfig tun0 172.26.0.1 netmask 255.255.255.0 Now we should have connectivity with the client, and the remote server ip: 172.26.0.2 / 6.- Countermeasures / +----------------------+ One possible option to avoid CNAME queries is to use iptables in the router/firewall, with a rule such as this: # iptables -t filter -A INPUT -p udp --dport 53 \ -m string --string "CNAME" -j DROP To use "string" in iptables, it is necessary to apply the "patch-o-matich" patch. The problem using this method is the performance degradation in the system, there are a great number of packets ruled out that are false positives, and using same method for TXT would lead to a network with too many packets dropped. An alternative is using a DNS server that doesn't accept TXT or CNAME queries, or one that verifies the responses size to be able to detect possible wrapped queries. The following example shows a quite simple perl script that does this: -------------------------------------------------------------------------------- #!/usr/bin/perl # Mon May 16 00:00:44 CEST 2005 # Last version at: http://www.unsec.net # # proxy-dns, check lengh of respones and deny TXT records # # BUGS: All Net::DNS bugs -> SLOW! # use Net::DNS::Nameserver; use Net::DNS::Resolver; use Net::DNS; use Getopt::Long; use POSIX qw(strftime); use strict; my ($daemon, $log, $verbose, $ns, $help, $length); my $length = 100; my $log = "/dev/stdout"; GetOptions( "daemon" => \$daemon, "log=s" => \$log, "ns=s" => \$ns, "length=s" => \$length, "verbose" => \$verbose, "help" => \$help); if(defined($help)) { print STDERR <<"EOD"; Syntax: $0 [--daemon] [--log ] [--verbose] [--ns ] [--help] Options: --daemon : run script as daemon --log : log all querys to (default: STDOUT) --ns : use instead /etc/resolv.conf --length : use of maximum length of reply (default: 100) --verbose : verbose output --help : this help EOD exit 1; } if (defined($daemon)) { defined(my $pid = fork) or die "Error: $!"; exit if $pid; } open LOG, ">>$log"; print LOG scalar(localtime) . " [$0] Starting service\n"; sub reply_handler { my ($qname, $qclass, $qtype, $peerhost) = @_; my ($rcode, @ans, @auth, @add, $ret, $r1, $r2); my $res = Net::DNS::Resolver->new; $res->nameservers("$ns") if defined $ns; my $query = $res->query($qname, $qtype); if ($query) { foreach my $rr ($query->answer) { next unless $rr->type eq "$qtype"; $ret = $rr->address if $qtype eq "A"; $ret = $rr->ptrdname if $qtype eq "PTR"; $ret = $rr->nsdname if $qtype eq "NS"; if ($qtype eq "MX") { $ret = $rr->preference . " " . $rr->exchange; } if (($qtype ne "TXT") || length($ret) gt $length) { my ($ttl, $rdata) = (3600, "$ret"); push @ans, Net::DNS::RR->new("$qname $ttl $qclass $qtype $rdata"); print LOG scalar(localtime) . " [$0] $qname -> $qclass $qtype " . "from: $peerhost reply: $rdata\n"; $rcode = "NOERROR"; } else { print LOG scalar(localtime) . " [$0] $qname -> $qclass $qtype " . "from: $peerhost reply: REFUSED(".length($ret).")\n"; $rcode = "REFUSED"; } } } else { print LOG scalar(localtime) . " [$0] $qname -> $qclass $qtype " . "from: $peerhost reply: NO ANSWER\n"; $rcode = "NOTAUTH"; } return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); } my $ns = Net::DNS::Nameserver->new( LocalPort => 53, ReplyHandler => \&reply_handler, Verbose => $verbose, ) || die "couldn't create nameserver\n"; $ns->main_loop; close LOG; -------------------------------------------------------------------------------- / 7.- References / +------------------+ http://www.dns.net/dnsrd/rfc/ http://www.doxpara.com/Black_Ops_DNS_BH.ppt [don't forget to ask Dan K. for his "todo" list ;) ] http://www.aripollak.com/wiki/Main/SSHOverDNS http://slashdot.org/articles/00/09/10/2230242.shtml / 8.- Change log / +-----------------+ lun may 16 00:53:54 CEST 2005 + Countermesures point added. + English version. + Changes control added. mar june 8 00:69:00 CEST 2005 + Some crazy American fuck named feeble made some changes.