0887 371 498 support@itservice-bg.net
Unbound с RPZ: DNS защита от фишинг и малуер — инсталация, конфигурация и тест
30.10.2026 · Самуил Арсов · Ubuntu, Киберсигурност

Unbound с RPZ: DNS защита от фишинг и малуер — инсталация, конфигурация и тест

Обновено на 30.10.2026: статията е пренаписана като технически наръчник.

RPZ (Response Policy Zone) е стандартен начин кеширащият DNS сървър да отговаря различно за определени имена. Най-честата употреба е да не резолва известни фишинг, малуер и измамни домейни. Устройството получава отговор „няма такъв домейн“ (NXDOMAIN) и връзката изобщо не започва. Така работят и двата кеширащи DNS сървъра на АЙТИСЪРВИС.

По-долу е целият път на Ubuntu 24.04 с Unbound 1.19: инсталация на рекурсивен резолвер, включване на RPZ първо в режим „само логва“, проверка, преминаване към блокиране, изключения и статистика. Адресите в примерите са документационни: резолверът е 192.0.2.53, клиентите са в 198.51.100.0/24.

1. Инсталация

apt update
apt install unbound dnsutils

Пакетът на Ubuntu идва с два файла в /etc/unbound/unbound.conf.d/: root-auto-trust-anchor-file.conf (ключът за DNSSEC) и remote-control.conf (сокетът за unbound-control). Оставяме ги както са.

На Ubuntu systemd-resolved слуша на 127.0.0.53:53. Ако Unbound слуша на конкретни адреси (както по-долу), двата не си пречат. Ако искате interface: 0.0.0.0, първо сложете DNSStubListener=no в /etc/systemd/resolved.conf.

2. Рекурсивен резолвер

Без forward-zone: сървърът сам пита root, TLD и авторитетните сървъри. Така не зависите от Google или Cloudflare и DNSSEC се проверява при вас. Файл /etc/unbound/unbound.conf.d/server.conf:

server:
    verbosity: 1
    extended-statistics: yes
    interface: 127.0.0.1
    interface: 192.0.2.53
    interface: 2001:db8::53
    do-ip4: yes
    do-ip6: yes

    # respip е задължителен за RPZ (виж по-долу)
    module-config: "respip validator iterator"

    # 4 ядра: 4 нишки и 4 slabs за всеки кеш
    num-threads: 4
    msg-cache-slabs: 4
    rrset-cache-slabs: 4
    infra-cache-slabs: 4
    key-cache-slabs: 4
    msg-cache-size: 1024m
    rrset-cache-size: 2048m
    so-rcvbuf: 8m
    so-sndbuf: 8m
    outgoing-range: 8192
    num-queries-per-thread: 4096

    # бързина: обновявай популярното преди да изтече,
    # при проблем навън отговаряй от стария кеш
    prefetch: yes
    prefetch-key: yes
    serve-expired: yes
    serve-expired-ttl: 3600
    serve-expired-client-timeout: 1800
    aggressive-nsec: yes

    hide-identity: yes
    hide-version: yes
    harden-dnssec-stripped: yes

    # само за вашите мрежи, иначе е отворен резолвер
    access-control: 127.0.0.0/8 allow
    access-control: ::1 allow
    access-control: 198.51.100.0/24 allow
    access-control: 2001:db8::/32 allow

За so-rcvbuf: 8m ядрото трябва да позволява толкова голям буфер:

cat > /etc/sysctl.d/90-unbound.conf <<'EOF'
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
EOF
sysctl --system

3. Списъкът и изключенията

Ползваме HaGeZi Threat Intelligence Feeds във вариант mini: около 214 000 домейна (фишинг, малуер, командни сървъри на ботнети, измами), готов в RPZ формат и обновяван няколко пъти на ден. Unbound го сваля сам по HTTPS и го пази локално.

Изключенията са отделна малка RPZ зона, която се проверява преди списъка. Файл /etc/unbound/unbound.conf.d/rpz.conf:

rpz:
    name: "whitelist.rpz"
    zonefile: "/etc/unbound/whitelist.rpz"
    rpz-log: yes
    rpz-log-name: "whitelist"

rpz:
    name: "hagezi-tif.rpz"
    url: "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/rpz/tif.mini.txt"
    zonefile: "/var/lib/unbound/hagezi-tif.rpz"
    # първата седмица: само логва, не блокира
    rpz-action-override: passthru
    rpz-log: yes
    rpz-log-name: "hagezi-tif"

Зоните се проверяват по реда в конфигурацията и печели първото съвпадение. Затова whitelist.rpz е първа.

Празната зона с изключения, /etc/unbound/whitelist.rpz:

$TTL 300
@ SOA localhost. root.localhost. 2026102901 3600 600 86400 300
  NS localhost.
; изключение:  домейн  CNAME rpz-passthru.  ; дата | причина
;   example.com     CNAME rpz-passthru.
;   *.example.com   CNAME rpz-passthru.

Всеки домейн в списъка е записан два пъти: домейн CNAME . и *.домейн CNAME .. CNAME . в RPZ значи NXDOMAIN. Блокира се домейнът и всичките му поддомейни, но не и другите домейни, чиито DNS сървъри са при него. Затова и изключението се пише в два реда.

Капанът с respip. С rpz: блок и старото module-config: "validator iterator" Unbound не стартира, а unbound-checkconf не го хваща. На продукционен резолвер това е спрял DNS за всички клиенти. Модулът respip трябва да е първи.

4. Сух старт на друг порт

Преди да рестартирате работещия резолвер, пуснете втори Unbound със същата конфигурация на порт 5353, с малък кеш и в преден план. Ако не тръгне или не блокира, клиентите не са засегнати.

unbound-checkconf

# тестовият файл ТРЯБВА да е в /etc/unbound/: AppArmor не пуска Unbound да чете /tmp
T=/etc/unbound/test-5353.conf
C=/etc/unbound/unbound.conf.d
sed -e 's/^\(\s*interface:\).*/\1 127.0.0.1/' -e 's/^\(\s*msg-cache-size:\).*/\1 4m/' \
    -e 's/^\(\s*rrset-cache-size:\).*/\1 8m/' -e 's/^\(\s*num-threads:\).*/\1 1/' \
    $C/server.conf > $T
echo '    port: 5353' >> $T
cat $C/root-auto-trust-anchor-file.conf >> $T
# отделно копие на списъка; тестът блокира наистина (nxdomain), за да се види, че работи
cp /var/lib/unbound/hagezi-tif.rpz /var/lib/unbound/hagezi-test.rpz 2>/dev/null
chown unbound: /var/lib/unbound/hagezi-test.rpz 2>/dev/null
sed -e 's#/var/lib/unbound/hagezi-tif.rpz#/var/lib/unbound/hagezi-test.rpz#' -e 's/passthru/nxdomain/' $C/rpz.conf >> $T

(timeout 120 unbound -d -c $T &)
sleep 30      # при първо пускане списъкът се сваля

dig -p 5353 @127.0.0.1 casinoonline.ac | grep status    # очаквано: NXDOMAIN
dig -p 5353 @127.0.0.1 isc.org | grep status            # очаквано: NOERROR

pkill -f "unbound -d -c $T"; rm -f $T /var/lib/unbound/hagezi-test.rpz

casinoonline.ac е просто първият домейн в текущия списък. Вземете който и да е:

grep -m1 'CNAME \.$' /var/lib/unbound/hagezi-tif.rpz

5. Включване и проверка

systemctl restart unbound
systemctl is-active unbound

# двете зони са заредени
unbound-control list_auth_zones

# DNS работи, DNSSEC се проверява (флаг ad), счупеният подпис дава SERVFAIL
dig @192.0.2.53 isc.org +dnssec | grep -E 'status|flags'
dig @192.0.2.53 dnssec-failed.org | grep status

В режим passthru всичко се резолва нормално, но всяко съвпадение се записва в журнала:

journalctl -u unbound -g 'rpz: applied' --since -1h | tail
... info: rpz: applied [hagezi-tif] ads.example-bad.com. rpz-passthru 198.51.100.23@41522 ads.example-bad.com. A IN

Кои домейни и от колко устройства:

journalctl -u unbound -g 'rpz: applied \[hagezi' --since -24h -o cat \
  | awk '{split($8,a,"@"); print $6, a[1]}' | sort | uniq -c | sort -rn | head -20

Седмица в този режим показва какво би се блокирало и кои легитимни домейни трябва да влязат в изключенията, преди някой клиент да се обади.

6. Блокиране

Сменете passthru на nxdomain, пуснете отново сухия старт и рестартирайте:

cp -a /etc/unbound/unbound.conf.d/rpz.conf /root/rpz.conf.bak-passthru
sed -i 's/rpz-action-override: passthru/rpz-action-override: nxdomain/' \
    /etc/unbound/unbound.conf.d/rpz.conf
unbound-checkconf && systemctl restart unbound

dig @192.0.2.53 casinoonline.ac | grep status      # NXDOMAIN
dig @192.0.2.53 isc.org | grep status              # NOERROR

Връщане: копирайте бекъпа обратно и рестартирайте.

Защо NXDOMAIN, а не страница „Блокирано“: почти всичко е по HTTPS и страница от ваш сървър на мястото на чужд домейн браузърът показва като грешка в сертификата. Потребителят свиква да натиска „Продължи въпреки това“, а точно това не искаме.

7. Изключение за домейн

Добавя се без рестарт и важи веднага:

F=/etc/unbound/whitelist.rpz
# 1. сериалът в SOA трябва да се вдигне
sed -i 's/ 2026102901 / 2026102902 /' $F
# 2. домейнът и поддомейните му
cat >> $F <<'EOF'
example.com     CNAME rpz-passthru. ; 2026-10-30 | новинарски сайт
*.example.com   CNAME rpz-passthru.
EOF
# 3. презареждане само на тази зона
unbound-control auth_zone_reload whitelist.rpz
dig @192.0.2.53 example.com | grep status          # NOERROR

При два резолвера (основен и резервен) изключението се прави и на двата.

8. Ако източникът на списъка не е достъпен

Unbound обновява зоната по стойностите в нейния SOA: refresh 8 часа, retry 1 час, expire 3 дни. Ако GitHub не е достъпен, той продължава с последното локално копие. Ако копието изтече или липсва, Unbound пак стартира и DNS работи, само без филтриране (fail-open). Проверено е със спрян достъп до URL-а и изтрит zonefile.

Затова наблюдаваме възрастта на локалния файл:

stat -c '%y' /var/lib/unbound/hagezi-tif.rpz             # последно сваляне
awk '$2=="SOA"{print $5; exit}' /var/lib/unbound/hagezi-tif.rpz   # сериал
grep -c '^[^*;$@ ].* CNAME \.$' /var/lib/unbound/hagezi-tif.rpz   # брой домейни

# ръчно обновяване
unbound-control auth_zone_transfer hagezi-tif.rpz

При нас дневният отчет предупреждава, ако списъкът е на повече от 24 часа, и вдига тревога над 72 часа.

9. Статистика

С extended-statistics: yes Unbound брои отговорите по вид, типовете заявки и действията на RPZ:

unbound-control stats_noreset | grep -E \
  '^total\.num\.(queries|cachehits)=|^total\.recursion\.time\.(avg|median)=|^num\.answer\.rcode\.(NOERROR|NXDOMAIN|SERVFAIL)=|^num\.rpz\.action'
  • cachehits / queries: колко отговора идват от кеша. При рекурсивен резолвер обикновено е над 80%.
  • recursion.time: колко време отнема заявка, която не е в кеша. Десетки ms е нормално.
  • SERVFAIL: заявки, за които сървърът не е намерил отговор (счупен DNSSEC, недостъпни авторитетни сървъри). Внезапен скок обикновено значи проблем с връзката навън.
  • num.rpz.action.rpz-nxdomain: блокирани заявки. rpz-passthru: заявки, пуснати заради изключение.

unbound-control set_option extended-statistics: yes го включва без рестарт, но тогава се броят само част от отговорите, а типовете заявки изобщо липсват. За верни числа е нужен рестарт.

Как изглежда при нас

Двата кеширащи DNS сървъра на АЙТИСЪРВИС работят точно с тази конфигурация. През първите 24 часа спряха над 13 000 опита за достъп от 125 устройства към 60 зловредни домейна. Статистиката е публична на страницата Локална инфраструктура: блокирани заявки по часове, най-търсените блокирани домейни, изключенията и търсачка в целия списък. Показва се само броят устройства, без IP адреси на клиентите.

Ако ваш домейн е блокиран погрешно от нашите сървъри, пишете на support@itservice-bg.net.