BIND’s Response Policy Zones (RPZ) can block malware domains, but most people think of it as a simple blocklist mechanism when it’s actually a powerful, context-aware traffic shaping tool.
Let’s see it in action. Imagine you have a named.conf file that looks something like this:
options {
directory "/var/cache/bind";
// ... other options
};
logging {
channel rpz_log {
file "/var/log/named/rpz.log" versions 3 size 5m;
severity info;
print-time yes;
};
category rpz { rpz_log; };
};
zone "malware.local" {
type master;
file "/etc/bind/db.malware.local";
};
rpz-zones {
malware.local QNAME-NXDOMAIN;
};
And your db.malware.local zone file contains:
$TTL 604800
@ IN SOA localhost. root.localhost. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS localhost.
@ IN A 127.0.0.1
malware-site.com. IN CNAME malware.local.
bad-actor.net. IN A 127.0.0.1
When a client queries for bad-actor.net, BIND checks the malware.local RPZ. Because bad-actor.net is listed in the zone file with an A record pointing to 127.0.0.1, BIND returns that IP address. The client thinks it’s talking to the malware site, but it’s actually hitting 127.0.0.1. If malware-site.com is queried, it resolves to malware.local, which also has an A record for 127.0.0.1.
If you wanted to completely prevent resolution, you’d configure the rpz-zones line like this:
rpz-zones {
malware.local NXDOMAIN;
};
Now, querying for bad-actor.net or malware-site.com would result in an NXDOMAIN (Non-Existent Domain) response, effectively killing the lookup before it even gets to a real IP.
The power of RPZ lies in its flexibility. You’re not just blocking domains; you’re rewriting DNS responses based on policy. The QNAME-NXDOMAIN directive tells BIND to resolve the query name to NXDOMAIN if it’s found in the RPZ. Other common actions include:
CNAMEto a specific domain: This is useful for redirecting users to a "block page" or a honeypot.Arecord to a specific IP: This is what we used with127.0.0.1to make the client think it’s connected to the malware server.NSto a specific name server: This can be used to redirect queries to a dedicated sinkhole or analysis server.DNAME(Delegation Name): This allows you to delegate a subtree of the RPZ zone to another zone, enabling more complex policy routing.
You can also apply RPZ policies based on the client’s IP address using rpz-client-ip. For example, to apply a policy only to clients in the 192.168.1.0/24 subnet:
rpz-zones {
malware.local QNAME-NXDOMAIN;
};
rpz-client-ip 192.168.1.0/24 {
malware.local QNAME-NXDOMAIN;
};
This allows for granular control, such as blocking malware for internal clients while allowing external researchers to resolve those same domains for analysis.
The rpz-log channel we configured is crucial for visibility. It will log every query that matches an RPZ rule, showing the client IP, the query name, the action taken (e.g., NXDOMAIN, 127.0.0.1), and the RPZ zone that triggered it. This log is invaluable for troubleshooting and understanding what’s being blocked.
The most surprising aspect of RPZ is its ability to perform "policy-based routing" at the DNS layer, influencing where traffic would go if the DNS resolution were allowed to proceed normally. It’s not just about denial; it’s about redirection and manipulation of the DNS response itself. This allows you to integrate DNS-based security policies directly into your recursive resolver without needing separate DNS firewall appliances or complex iptables rules for every blocked domain.
The next step is to explore how to dynamically update RPZ zones using tools like rpz-tool or by integrating with threat intelligence feeds.