I finally got some free time and decided to use it for improving the security of my home network. The most effective measures for my environment would be any additional controls on the network level so that both the LAN and IOT networks could benefit from it. I recently setup a recursive nameserver using Unbound and have forced all DNS traffic to go through that. It’s nice because I get visibility on all DNS traffic which I monitor using Splunk. But now, it’s time to add some additional hardening to the DNS service using the publicly available blocklist from OISD.

The blocklist prevents your devices from connecting to unwanted or harmful domains. It reduces ads, decreases the risk of malware, and enhances privacy.

The solution provided in this note is very simple, the only thing that needed to be created was a simple shell script to periodically update the blocklist.

A somewhat secure execution environment for the script needs to be created first:

daemon# groupadd -g 5000 _dnsbl
daemon# useradd -g 5000 -u 5000 -s /sbin/nologin -d /nonexistent -c "DNS Blacklist Updater" _dnsbl

The script expects a writable directory to drop the blocklist, I placed it under the Unbound configuration directory:

daemon# mkdir /var/unbound/etc/blocklists
daemon# chown -R _dnsbl:_unbound /var/unbound/etc/blocklists
daemon# chmod 750 /var/unbound/etc/blocklists

Grab the code and place it in /usr/local/sbin/dnsbl-update and setup the permissions so that only root can make changes to the code and the sandbox user _dnsbl can execute it.

daemon# chmod 750 /usr/local/sbin/dnsbl-update
daemon# chown root:_dnsbl /usr/local/sbin/dnsbl-update

Since the script runs as a low privileged user we need to setup doas to allow it to restart the Unbound service.

daemon# cat /etc/doas.conf
permit nopass _dnsbl as root cmd /usr/sbin/rcctl args restart unbound 

Configure Unbound to use the actual blocklist, for that you just need to add an include directive to /var/unbound/etc/unbound.conf. In my config this directive is actually the first configuration statement.

daemon# head -3 /var/unbound/etc/unbound.conf 
# SX: unbound configuration

include: "/var/unbound/etc/blocklists/blocklist.conf"

The next step is to have the script run periodically from cron by issuing the crontab -e command. I run it every 5 minutes, make sure to change it to fit your needs.

daemon# su -s /bin/sh _dnsbl
daemon$ crontab -e
# DNS Blocklist Updater
30  4       *       *       *       /usr/local/sbin/dnsbl-update > /dev/null 2>&1

Finally we need to make sure that we’re not receiving email for the _dnsbl user by adding the following to /etc/mail/aliases:

daemon# grep _dnsbl /etc/mail/aliases
_dnsbl:      /dev/null

After any modifications to the /etc/mail/aliases file you need to run the newaliases command.

The script:

#!/bin/sh
# Simple DNS blocklist update script

# enable for debugging
#set -x

# manage file permissions
umask 027

# configuration
UPDATE_URL="https://big.oisd.nl/unbound"
GETTER="/usr/bin/ftp"
DEST="/var/unbound/etc/blocklists/blocklist.conf"

# code - no changes needed here
LOGGER() {
     /usr/bin/logger -i -p local0.info -t $0 "$1"
}

RESTART_UNBOUND() {
    LOGGER "Restarting Unbound service"
    doas /usr/sbin/rcctl restart unbound > /dev/null 2>&1
}

UPDATE_BLOCKLIST() {
    LOGGER "Updating blacklist file: ${DEST}" 
    /bin/mv -f ${DEST} ${DEST}.old
    /bin/mv -f ${DEST}.new ${DEST}
}

RESTORE_BLOCKLIST() {
    LOGGER "Restoring previous configuration"
    /bin/rm -f ${DEST}
    /bin/cp -f ${DEST}.old ${DEST}
}

LOGGER "Downloading latest DNS blacklist from: ${UPDATE_URL}"
${GETTER} -o ${DEST}.new ${UPDATE_URL} > /dev/null 2>&1
if [ $? -ne 0 ]; then
    LOGGER "Fatal error, download failed"
    exit 1
fi

if [ -s ${DEST}.new ]; then
    /usr/bin/diff ${DEST} ${DEST}.new > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        LOGGER "Blocklist has changed and needs updating"
        UPDATE_BLOCKLIST
        RESTART_UNBOUND
        if [ $? -ne 0 ]; then
	    LOGGER "Warning, could not restart Unbound"
            RESTORE_BLOCKLIST
            RESTART_UNBOUND
        fi
        LOGGER "Blocklist updated"
    else
        LOGGER "Blocklist has not changed, skipping update"
    fi
fi

# EOF

That’s it!