Concepts / Detection and response

Detection and response

DNS sinkhole

Also called sinkhole, blocklist, blackhole dns, nxdomain block

A resolver that answers known-bad domains with a harmless address or NXDOMAIN, blocking them for every client.

Because a company's machines all resolve through one resolver, it is the natural place to enforce a blocklist. A sinkhole makes the resolver hand back a controlled address for a bad domain — often a web server that logs each arrival, giving a second list of infected hosts — or refuse the name with NXDOMAIN, a harder stop that keeps no arrival log. Either way the resolver's query log already names which internal host asked, even in logging-only mode.

How a DNS sinkhole works, step by step

In the DNS sinkhole lab two PCs, infectedPc (10.0.1.10) and cleanPc (10.0.1.11), resolve through one company resolver (10.0.1.12, unbound). The malware on infectedPc calls home to c2.evil, served by badNs (10.0.2.11). The sinkhole web server is 10.0.2.13.

  1. Every lookup passes the resolver. DHCP or static configuration puts nameserver 10.0.1.12 in each machine's /etc/resolv.conf, and the firewall should stop machines from asking anyone else on port 53.
  2. The resolver logs each question with the client address, name, type and, with replies logged, the response code. In logging-only mode nothing is blocked yet, but the log already names the infected machine.
  3. A policy takes over a domain. The resolver gets a local zone for evil.. With the redirect type, every name under evil (c2.evil, panel.evil, any name the malware generates) gets the answer you set: evil. A 10.0.2.13. The question never reaches the internet.
  4. Old answers are flushed. A cached c2.evil A 10.0.2.11 would live on until its TTL ran out, so the policy is followed by a flush of the zone.
  5. The malware connects to the sinkhole. Its beacon, an HTTP request to c2.evil, now lands on your own server, which answers a harmless page and logs the source address: a second list of infected machines, confirmed by traffic.
  6. Or the name is refused. The always_nxdomain type answers "no such name". The beacon cannot even start, and the arrival log is lost.

Production resolvers load these rules in bulk from a response policy zone, fed by threat intelligence lists, rather than one command at a time. Public filtering resolvers do the same for everyone who uses them: see filtering resolver.

What it looks like on a Linux machine

unbound writes one line per query and one per reply to /var/log/unbound.log. The client address comes first:

$ grep evil /var/log/unbound.log | tail -2
Oct 10 18:19:55 unbound[111:0] info: 10.0.1.10 c2.evil. A IN
Oct 10 18:19:55 unbound[111:0] info: 10.0.1.10 c2.evil. A IN NOERROR 0.000453 0 52

The reply line adds the response code, the time taken, whether it came from the cache (1) or not (0), and the size. The sinkhole is three commands, applied while unbound runs:

$ unbound-control local_zone evil. redirect
ok
$ unbound-control local_data "evil. A 10.0.2.13"
ok
$ unbound-control flush_zone evil.
ok removed 1 rrsets, 1 messages and 0 key entries

On the infected PC both names now resolve to the sinkhole, and the beacon gets the blocked page:

$ dig +short c2.evil; dig +short panel.evil
10.0.2.13
10.0.2.13
$ curl -s -m3 http://c2.evil/
blocked by security policy

The hard block instead:

$ unbound-control local_zone evil. always_nxdomain
ok
$ dig c2.evil | grep -E 'status|ANSWER:'
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 60070
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
$ curl -s -m3 http://c2.evil/; echo rc=$?
rc=6

Exit code 6 is curl's "could not resolve host". The NXDOMAIN carries no SOA record, which tells a local block from a name that really does not exist. Rules added with unbound-control vanish on restart; permanent ones go in unbound.conf as local-zone: and local-data: lines.

What goes wrong, and how you see it

The block does not take. The policy is in, but dig +short c2.evil still prints 10.0.2.11: the old answer is cached. unbound-control flush_zone evil. clears it.

Clients go around the resolver. Malware that asks 8.8.8.8 directly, or uses DNS over HTTPS, never meets the policy, and the query log goes quiet while the beacon still works. Block outbound port 53 except from the resolver, and watch for DoH endpoints.

The rule is too wide. A local zone covers every name under it, so blocking a shared parent domain cuts off legitimate sites too. dig +short www.site from a clean PC is the check that only the bad domain is gone.

The log names the resolver, not the PC. If another server forwards to yours, every query arrives from the forwarder's address. Read the log on the server the clients ask.

Questions people ask

Sinkhole or NXDOMAIN? A sinkhole address keeps a record of every machine that still tries to connect, and can show users a notice. NXDOMAIN is simpler and stops the connection earlier, but leaves only the query log.

Is a DNS sinkhole the same as Pi-hole? Pi-hole is a DNS sinkhole for ads and trackers on a home network: it answers blocked names with 0.0.0.0 or NXDOMAIN. The mechanism is the same; the list is different.

Can a sinkhole stop data theft over DNS? Only for domains it knows. A tunnel needs its queries to reach the attacker's name server; a policy on that domain, or a drop rule for that server as in the DNS exfiltration lab, ends it. See DNS tunneling.

See it on real machines

Learn it on real machines.

Every lab is a small network of real Linux machines in your browser. Start with a free account: Google or email, no card.

Open this lab