Implementing DNS-Based Security with BIND Response Policy Zones
Response Policy Zones (RPZ) provide DNS-level security by allowing you to override DNS responses for specific domains. This enables blocking malicious domains, implementing content filtering, and protecting your network from phishing, malware, and other threats at the DNS layer - before connections are even established.
How RPZ Works
RPZ intercepts DNS queries and modifies responses based on policy rules. When a client queries a domain that matches an RPZ rule, BIND returns a modified response instead of the actual DNS record. This can be:
- NXDOMAIN: Domain doesn't exist
- NODATA: Domain exists but has no records of the requested type
- Custom IP: Redirect to a walled garden or block page
- CNAME: Redirect to another domain
- Passthru: Allow the query (override more general rules)
- Drop: Silently drop the query
Enabling RPZ in BIND
Basic Configuration
Add the response-policy statement to your options:
options {
directory "/var/named";
recursion yes;
allow-recursion { trusted; };
response-policy {
zone "rpz.local";
};
};
zone "rpz.local" {
type primary;
file "/var/named/rpz/rpz.local.zone";
allow-query { none; };
allow-transfer { none; };
};
Creating the RPZ Zone File
; /var/named/rpz/rpz.local.zone
$TTL 300
@ IN SOA localhost. admin.localhost. (
2024010101 ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
300 ) ; Minimum TTL
IN NS localhost.
; Block malicious-site.com (return NXDOMAIN)
malicious-site.com CNAME .
*.malicious-site.com CNAME .
; Block tracking domain (return NODATA)
tracker.example.com CNAME *.
; Redirect phishing site to block page
phishing-site.com A 192.168.1.100
*.phishing-site.com A 192.168.1.100
; Allow specific subdomain (passthru)
legit.blocked-domain.com CNAME rpz-passthru.
; Block entire domain but redirect to info page
bad-domain.com CNAME blocked.example.com.
RPZ Policy Actions
NXDOMAIN (Block Domain)
Return "domain not found":
; CNAME to root (.) returns NXDOMAIN
badsite.com CNAME .
*.badsite.com CNAME .
NODATA (Block Record Type)
Return "no records of requested type":
; CNAME to wildcard (*.) returns NODATA
tracking.example.com CNAME *.
Custom Response
Return specific IP addresses:
; Redirect to block page
blocked-site.com A 192.168.1.100
blocked-site.com AAAA fd00::100
; Redirect to walled garden
malware.com CNAME walled-garden.example.com.
Passthru (Allow)
Override blocking for specific names:
; Block entire domain
badsite.com CNAME .
*.badsite.com CNAME .
; But allow specific subdomain
api.badsite.com CNAME rpz-passthru.
Drop (Silent Discard)
Silently drop queries (no response sent):
drop-this.com CNAME rpz-drop.
TCP-Only
Force TCP retry (rarely used):
force-tcp.com CNAME rpz-tcp-only.
Trigger Types
RPZ can match queries on different criteria:
QNAME Triggers
Match the queried domain name:
; Exact match
badsite.com CNAME .
; Wildcard match (all subdomains)
*.badsite.com CNAME .
IP Triggers
Match response IP addresses:
; Block any response containing this IP
32.10.20.30.40.rpz-ip CNAME .
; Block entire subnet responses
24.192.168.1.rpz-ip CNAME .
NSIP Triggers
Match nameserver IP addresses:
; Block domains hosted on this nameserver IP
32.10.20.30.40.rpz-nsip CNAME .
NSDNAME Triggers
Match nameserver domain names:
; Block domains using this nameserver
ns.malicious-hosting.com.rpz-nsdname CNAME .
Client IP Triggers
Match client IP addresses:
; Different policy for specific client
32.192.168.1.50.rpz-client-ip CNAME rpz-passthru.
Multiple RPZ Zones
Use multiple zones with different priorities:
options {
response-policy {
zone "rpz-local" policy given; # Local overrides
zone "rpz-malware" policy given; # Malware blocklist
zone "rpz-phishing" policy given; # Phishing blocklist
zone "rpz-ads" policy given; # Ad blocking
};
};
zone "rpz-local" {
type primary;
file "/var/named/rpz/local.zone";
};
zone "rpz-malware" {
type secondary;
file "/var/named/rpz/malware.zone";
primaries { 192.168.1.10; };
};
zone "rpz-phishing" {
type secondary;
file "/var/named/rpz/phishing.zone";
primaries { 192.168.1.10; };
};
zone "rpz-ads" {
type secondary;
file "/var/named/rpz/ads.zone";
primaries { 192.168.1.10; };
};
Zones are evaluated in order - first match wins.
Using External Blocklists
Converting Blocklists to RPZ Format
Many blocklists are available in hosts file format. Convert them to RPZ:
#!/bin/bash
# convert-blocklist.sh
INPUT="$1"
OUTPUT="$2"
SOA_SERIAL=$(date +%Y%m%d%H)
cat > "$OUTPUT" << EOF
\$TTL 300
@ IN SOA localhost. admin.localhost. (
$SOA_SERIAL ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
300 ) ; Minimum TTL
IN NS localhost.
; Converted from $INPUT
EOF
# Convert hosts file format (0.0.0.0 domain or 127.0.0.1 domain)
grep -E "^(0\.0\.0\.0|127\.0\.0\.1)" "$INPUT" | \
awk '{print $2}' | \
grep -v localhost | \
sort -u | \
while read domain; do
echo "$domain CNAME ."
done >> "$OUTPUT"
Usage:
./convert-blocklist.sh hosts.txt /var/named/rpz/blocklist.zone
rndc reload rpz-blocklist
Popular Blocklist Sources
- Steven Black's hosts: Unified hosts with multiple extensions
- OISD: Comprehensive ad/tracking blocklist
- URLhaus: Malware URL blocklist
- PhishTank: Phishing URL database
- Spamhaus: Malware and botnet domains
Automated Updates
#!/bin/bash
# /usr/local/bin/update-rpz.sh
RPZ_DIR="/var/named/rpz"
BLOCKLIST_URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
# Download blocklist
curl -s "$BLOCKLIST_URL" -o /tmp/blocklist.txt
# Convert to RPZ format
/usr/local/bin/convert-blocklist.sh /tmp/blocklist.txt "$RPZ_DIR/blocklist.zone"
# Fix permissions
chown named:named "$RPZ_DIR/blocklist.zone"
chmod 640 "$RPZ_DIR/blocklist.zone"
# Reload zone
rndc reload rpz-blocklist
# Cleanup
rm /tmp/blocklist.txt
echo "RPZ blocklist updated: $(date)"
Schedule with cron:
# Update blocklist daily at 3 AM
0 3 * * * /usr/local/bin/update-rpz.sh >> /var/log/rpz-update.log 2>&1
Creating a Block Page
Set up a web server to display block information:
Nginx Configuration
server {
listen 80;
server_name blocked.example.com;
root /var/www/blocked;
location / {
try_files $uri /index.html;
}
}
Block Page HTML
<!DOCTYPE html>
<html>
<head>
<title>Access Blocked</title>
<style>
body { font-family: sans-serif; text-align: center; padding: 50px; }
h1 { color: #d32f2f; }
.info { color: #666; margin-top: 20px; }
</style>
</head>
<body>
<h1>Access Blocked</h1>
<p>This site has been blocked by your network administrator.</p>
<div class="info">
<p>If you believe this is an error, please contact IT support.</p>
<p>Blocked domain: <script>document.write(location.hostname);</script></p>
</div>
</body>
</html>
RPZ Configuration for Block Page
; Redirect blocked domains to block page
bad-domain.com A 192.168.1.100
*.bad-domain.com A 192.168.1.100
; Or use CNAME
bad-domain.com CNAME blocked.example.com.
Logging RPZ Actions
Enable detailed RPZ logging:
logging {
channel rpz_log {
file "/var/log/named/rpz.log" versions 10 size 50m;
severity info;
print-time yes;
print-category yes;
print-severity yes;
};
category rpz { rpz_log; };
};
Log format shows:
- Query that triggered RPZ
- Which RPZ zone matched
- What action was taken
- Client IP address
Analyzing RPZ Logs
# Top blocked domains today
grep "$(date +%d-%b-%Y)" /var/log/named/rpz.log | \
grep -oP "(?<=QNAME=)[^ ]+" | \
sort | uniq -c | sort -rn | head -20
# Clients triggering most blocks
grep "$(date +%d-%b-%Y)" /var/log/named/rpz.log | \
grep -oP "(?<=client @0x[0-9a-f]+ )[0-9.]+" | \
sort | uniq -c | sort -rn | head -10
Whitelisting
Create a local override zone for whitelisting:
; /var/named/rpz/whitelist.zone
$TTL 300
@ IN SOA localhost. admin.localhost. (
2024010101 ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
300 ) ; Minimum TTL
IN NS localhost.
; Whitelist entries (passthru overrides blocking)
legitimate-site.com CNAME rpz-passthru.
*.legitimate-site.com CNAME rpz-passthru.
required-service.example.com CNAME rpz-passthru.
Place whitelist zone first:
response-policy {
zone "rpz-whitelist"; # Check whitelist first
zone "rpz-malware";
zone "rpz-ads";
};
Performance Considerations
Zone Size Limits
Large RPZ zones impact memory and query performance:
options {
response-policy {
zone "rpz-blocklist"
max-policy-ttl 300
min-update-interval 60;
};
};
NSIP/NSDNAME Performance
NSIP and NSDNAME triggers require additional lookups:
// Disable expensive triggers if not needed
options {
response-policy {
zone "rpz-blocklist" nsip-enable no nsdname-enable no;
};
};
Recursive Queries
RPZ only affects recursive queries. For authoritative-only servers, RPZ has no effect.
Complete Configuration Example
// /etc/named.conf
acl "trusted" {
localhost;
localnets;
192.168.0.0/16;
};
options {
directory "/var/named";
recursion yes;
allow-recursion { trusted; };
dnssec-validation auto;
response-policy {
zone "rpz-whitelist" policy passthru;
zone "rpz-malware" policy given;
zone "rpz-phishing" policy given;
zone "rpz-ads" policy given;
} qname-wait-recurse no;
};
logging {
channel rpz_log {
file "/var/log/named/rpz.log" versions 10 size 50m;
severity info;
print-time yes;
print-category yes;
};
category rpz { rpz_log; };
};
zone "rpz-whitelist" {
type primary;
file "/var/named/rpz/whitelist.zone";
allow-query { none; };
};
zone "rpz-malware" {
type primary;
file "/var/named/rpz/malware.zone";
allow-query { none; };
};
zone "rpz-phishing" {
type primary;
file "/var/named/rpz/phishing.zone";
allow-query { none; };
};
zone "rpz-ads" {
type primary;
file "/var/named/rpz/ads.zone";
allow-query { none; };
};
zone "." {
type hint;
file "named.ca";
};
Testing RPZ
Verify Blocking
# Query blocked domain
dig @localhost blocked-domain.com
# Should return NXDOMAIN or your block page IP
# Check RPZ logs
tail -f /var/log/named/rpz.log
Test Passthru
# Verify whitelisted domain resolves normally
dig @localhost whitelisted-domain.com
# Should return actual IP, not blocked
Debug Mode
Enable query logging temporarily:
rndc querylog on
# Run tests
rndc querylog off
Conclusion
Response Policy Zones provide powerful DNS-level security without requiring client software or network proxies. By blocking malicious domains at the resolver level, you protect all devices on your network - including IoT devices and guests that you can't install security software on.
Combine RPZ with regularly updated blocklists, local whitelisting, and comprehensive logging for an effective first line of defense against malware, phishing, and unwanted content.
The next post will cover DNSSEC validation on resolvers to protect against DNS spoofing and cache poisoning attacks.