Implementing DNS-Based Security with BIND Response Policy Zones

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
  • 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.

Read more

HAProxy Monitoring with Prometheus: Complete Observability Guide

HAProxy Monitoring with Prometheus: Complete Observability Guide

Monitoring HAProxy is essential for maintaining reliable load balancing infrastructure. Prometheus provides powerful metrics collection, alerting capabilities, and seamless Grafana integration for visualizing HAProxy performance and health. Why Prometheus for HAProxy? Prometheus offers: * Pull-based metrics - Prometheus scrapes HAProxy metrics endpoints * Time-series database - Store historical data for trend analysis

By Patrick de Ruiter