Шукати в цьому блозі

середу, 27 березня 2024 р.

Налаштовуємо netfilter в Linux для виявлення потенційно небажаного трафіка

 Хочу поділится одним "рецептом", який алгоритм роботи якого вже доволі давно було опробовано на 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-правил:
sudo nft list ruleset
або ж лише тієї частки в яких задано 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-ами, але це тема для зовсім іншої статті.

Немає коментарів: