Хочу поділится одним "рецептом", який алгоритм роботи якого вже доволі давно було опробовано на RouterOS і от лише нещодавно переписано під netfilter.
В чому полягає ідея?
До того як з'єднання буде встановлено у стан established перевірити потік трафіку, скажемо так, на періодичність доступу. Адже, давайте скажемо чесно, доступ до наших ресурсів (до нашого хоста) з тих чи інших адрес в Інтернет, на порти, на яких не задіяно жодних сервісів, завжди виглядає як не небажаним то підозрілим.
Що ми робимо? Ми організовуємо set-и з timeout-ами. Ці timeout-и можуть бути виставлені в доволі різні значення, але ідея полягає саме в тому, щоб обмежити доступ до хоста, якщо хтось доволі сильно знахабніє у встановлений час. Set-ів може бути багато, може бути мало, в прикладі їх наведено 16. За аналогією можна зробити 10, а можна зробити й 20, тут вже лише фантазія обмежує.
Від лірики до практики.
sudo nft add table ip scanDetector
Створимо "списки довіри". Хтось може обізвати whitelist, але я обізвав trusted та ignore, так вже історично склалося.
sudo nft add set ip scanDetector trusted { type ipv4_addr\; flags interval\; auto-merge\; comment \"Truster ip and net\" \; }
sudo nft add element ip scanDetector trusted { 192.168.1.0/24, 195.38.16.2, 195.38.16.8 }
sudo nft add set ip scanDetector ignore { type ipv4_addr\; flags interval\; auto-merge\; comment \"Ignore ip and net\" \; }
sudo nft add element ip scanDetector ignore { 0.0.0.0, 127.0.0.0/8 }
Тепер створимо set-и у створеній нами таблиці scanDetector:
sudo nft add set ip scanDetector level15 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 15\" \; timeout 1m \; }
sudo nft add set ip scanDetector level14 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 14\" \; timeout 2m \; }
sudo nft add set ip scanDetector level13 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 13\" \; timeout 3m \; }
sudo nft add set ip scanDetector level12 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 12\" \; timeout 4m \; }
sudo nft add set ip scanDetector level11 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 11\" \; timeout 5m \; }
sudo nft add set ip scanDetector level10 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 10\" \; timeout 10m \; }
sudo nft add set ip scanDetector level9 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 9\" \; timeout 15m \; }
sudo nft add set ip scanDetector level8 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 8\" \; timeout 20m \; }
sudo nft add set ip scanDetector level7 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 7\" \; timeout 30m \; }
sudo nft add set ip scanDetector level6 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 6\" \; timeout 40m \; }
sudo nft add set ip scanDetector level5 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 5\" \; timeout 50m \; }
sudo nft add set ip scanDetector level4 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 4\" \; timeout 1h \; }
sudo nft add set ip scanDetector level3 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 3\" \; timeout 1h10m \; }
sudo nft add set ip scanDetector level2 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 2\" \; timeout 1h20m \; }
sudo nft add set ip scanDetector level1 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 1\" \; timeout 1h30m \; }
sudo nft add set ip scanDetector level0 { type ipv4_addr\; flags dynamic\; comment \"SynScan Level 0\" \; timeout 3h \; }
sudo nft add set ip scanDetector scan { type ipv4_addr\; flags dynamic\; comment \"Scan Detect\" \; timeout 31d \; }
"Повісимо" hook і дамо йому priority нижчий за той в якому робимо всі інші перевірки з контролю доступу:
sudo nft add chain ip scanDetector input { type filter hook input priority -50 \; }
Ну й нарешті правила, які контролюють всі пакети, які ще не пройшли перевірки, тобто мають state new, а не established, тощо:
do nft add rule scanDetector input ct state new ip saddr @trusted counter log return
sudo nft add rule scanDetector input ct state new ip saddr @ignore counter log return
sudo nft add rule scanDetector input ct state new ip saddr != @scan ip saddr @level0 add @scan { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level0 ip saddr @level1 add @level0 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level1 ip saddr @level2 add @level1 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level2 ip saddr @level3 add @level2 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level3 ip saddr @level4 add @level3 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level4 ip saddr @level5 add @level4 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level5 ip saddr @level6 add @level5 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level6 ip saddr @level7 add @level6 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level7 ip saddr @level8 add @level7 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level8 ip saddr @level9 add @level8 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level9 ip saddr @level10 add @level9 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level10 ip saddr @level11 add @level10 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level11 ip saddr @level12 add @level11 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level12 ip saddr @level13 add @level12 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level13 ip saddr @level14 add @level13 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level14 ip saddr @level15 add @level14 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr != @level15 add @level15 { ip saddr } counter
sudo nft add rule scanDetector input ct state new ip saddr @scan update @scan { ip saddr } counter log prefix \"[nft] scandetect-input-drop \" drop
sudo nft add rule scanDetector input ip saddr @scan update @scan { ip saddr } counter log prefix \"[nft] scandetect-input-another-drop \" drop
Тобто, безумовно пропускаємо трафак з хостів та мереж, які було додано в set-и trusted та ignore. Хости в списки level15…level0 додаються поступово. І це нормально. Не кожен хто до нас "стукає" може бути зловмисником. Але якщо стукає ну дуже вже нахабно то рано чи пізно він потрапить в список scan. Так, в set scan потрапляють лише і виключно ті хости, які були ну ду-у-у-у-уже нахабними. А вибратися з set-у scan не так вже й просто, бо якщо виявляється new-пакет з хоста, що вже є в set-і scan то час його перебування в списку оновлюється на початковий, той, що задано параметром timeout.
От такий от рецепт з блокування доступу.
Масштабуємо рішення на доступ до docker-контейнерів.
Все це добре, але щоб контролювати транзитний трафік hook на input нам не дуже підходить. То додаємо hook на forward:
sudo nft add chain ip scanDetector forward { type filter hook forward priority -50 \; }
Ну, а далі треба пройтися по інтерфейсах, на яких "живуть" docker-контейнери і на них "підвісити" правила. Для цього можна скористатися переглядом або всіх netfilter-правил:
або ж лише тієї частки в яких задано chain DOCKER, в моєму випадку це:
sudo nft list table filter
То ж сформуємо правила:
for IFACE in $( sudo nft list table filter | sed '/chain DOCKER {/,/^$/!d;/^$/,$d' | awk '$4~/^oifname$/ { print $5 }' | tr -d \" | sort -u ); do
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr @trusted counter log return
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr @ignore counter log return
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @scan ip saddr @level0 add @scan { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level0 ip saddr @level1 add @level0 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level1 ip saddr @level2 add @level1 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level2 ip saddr @level3 add @level2 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level3 ip saddr @level4 add @level3 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level4 ip saddr @level5 add @level4 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level5 ip saddr @level6 add @level5 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level6 ip saddr @level7 add @level6 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level7 ip saddr @level8 add @level7 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level8 ip saddr @level9 add @level8 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level9 ip saddr @level10 add @level9 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level10 ip saddr @level11 add @level10 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level11 ip saddr @level12 add @level11 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level12 ip saddr @level13 add @level12 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level13 ip saddr @level14 add @level13 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level14 ip saddr @level15 add @level14 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr != @level15 add @level15 { ip saddr } counter
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ct state new ip saddr @scan update @scan { ip saddr } counter log prefix \"[nft] scandetect-forward-drop \" drop
sudo nft add rule scanDetector forward iifname != ${IFACE} oifname ${IFACE} ip saddr @scan update @scan { ip saddr } counter log prefix \"[nft] scandetect-forward-another-drop \" drop
done
Все, всі правила в table scanDetector сформовано. Заданий priority для chain нижчий за той, що задано hook-ами в table filter, відповідно й всі ці перевірки будуть виконуватися раніше. І лише після цих перевірок пакети потралятимуть на перевірку в table filter.
Подивитися на те, що вийшло
Всі set-и та правила:
sudo nft list table scanDetector
Подивитися на вміста set-у scan:
sudo nft list table scanDetector | sed '/set scan {/,/^$/!d;/^$/,$d'
Кількість елементів у set-і scan:
sudo nft list table scanDetector | sed '/set scan {/,/^$/!d;/^$/,$d' | egrep expires | awk -v RS='[[:space:]]+' '/expires/' | wc -l
Які підводні камені?
Ну, наприклад, одних з неприємних моментів, при застосуванні лише цих правил, може бути блокування зовнішніх клієнтів, наприклад, web-сервісів, які можуть бути розташовані на хосту. Вірішеється це доволі просто, теж з set-ами, але це тема для зовсім іншої статті.