Налаштування UFW та IPv6 на VPS
Дізнайтеся, чому правила UFW можуть не захищати IPv6 на VPS. Навчіться виявляти відкриті порти та закривати вразливості через IPv6 на Ubuntu 24.04.
Пастка IPv6 firewall в одному реченні
Ваш firewall захищає IPv4. Ваш VPS майже напевно також має публічну IPv6 адресу, і багато служб за замовчуванням прослуховують її. Якщо ваш firewall охоплює лише IPv4, або якщо ви використовуєте хмарний firewall, який фільтрує лише IPv4, кожна з цих служб буде доступна з усього інтернету через IPv6, хоча з боку IPv4 система здаватиметься захищеною. Ви перевіряєте порт за допомогою curl, бачите відхилено з'єднання і вважаєте, що ви в безпеці. Зловмисник підключається до того самого порту через IPv6 і отримує доступ.
Цей посібник показує, звідки виникає цей розрив у звичайному VPS на Ubuntu 24.04, як точно побачити, що саме ви відкриваєте, і як це виправити. UFW не є причиною проблеми. У сучасній установці Ubuntu UFW вже обробляє IPv6. Проблема виникає через рівні навколо нього та через служби, про прослуховування яких ви не знали.
Чому ваш VPS має IPv6
Майже кожен сучасний VPS постачається з публічною адресою IPv6, часто це ціла /64, на додаток до IPv4. Перевірте свою:
ip -6 addr show scope global2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
inet6 2001:db8:2a::1/64 scope globalЦя 2001:db8:2a::1 є маршрутизованою з будь-якої точки інтернету, так само як і ваша IPv4 адреса. Тепер подивіться, які сервіси очікують на з'єднання:
sudo ss -tlnpState Recv-Q Local Address:Port Process
LISTEN 0 0.0.0.0:22 sshd
LISTEN 0 [::]:22 sshd
LISTEN 0 127.0.0.1:5432 postgres
LISTEN 0 [::]:8080 docker-proxyУважно вивчіть колонку Local Address. 0.0.0.0:22 означає «слухати на кожній IPv4 адресі». [::]:22 означає «слухати на кожній IPv6 адресі». 127.0.0.1:5432 прив'язана до loopback і не є публічною, тому рядок Postgres у безпеці. Два рядки [::] відповідають на запити всього інтернету через IPv6, а docker-proxy — це тип, про запуск якого ви можете забути.
Більшість демонів за замовчуванням прив'язуються до ::, тому що в Linux сокет :: зазвичай приймає також і IPv4. Отже, стандартна поведінка нового сервера — «відповідати на обох стеках всюди». Єдине, що цьому перешкоджає — це ваш firewall. Саме тому firewall, який бачить лише один стек, є серйозною проблемою.
Звідки насправді береться прогалина в IPv6
Існує чотири поширені причини. На конкретному сервері може бути одна або кілька з них одночасно.
1. Хмарний firewall, що фільтрує лише IPv4. Багато firewall-рішень та продуктів security-groups розроблялися під IPv4, тому вони або ігнорують IPv6, або потребують окремих правил IPv6, які потрібно додавати вручну. Якщо ваш єдиний firewall — це інструмент у панелі керування провайдера, і він не підтримує IPv6, ваші [::] сервіси будуть відкриті, незалежно від налаштувань порту 22 для IPv4. Перегляньте документацію firewall вашого провайдера та шукайте саме термін IPv6.
2. Самостійно налаштований iptables без ip6tables. Команда iptables працює лише з таблицями IPv4. Для IPv6 існує окрема команда, ip6tables, з власними правилами. Якщо ви написали скрипт firewall, що складається лише з рядків iptables -A INPUT ..., і не додали відповідні правила ip6tables, ваш IPv6 firewall буде порожнім. Порожня ланцюжок INPUT з політикою за замовчуванням ACCEPT дозволяє будь-який трафік:
sudo ip6tables -L INPUT -nChain INPUT (policy ACCEPT)
target prot opt source destinationЦей вивід демонструє проблему: IPv4 фільтрується, а IPv6 приймає все.
3. Docker, що публікує порти в обхід firewall. Коли ви запускаєте docker run -p 8080:80, Docker вставляє власні правила перед правилами UFW. Через це опублікований порт залишається доступним, навіть якщо ufw status забороняє цей порт. У сучасних версіях Docker це стосується і IPv6. Why Docker bypasses UFW, and how to filter container ports properly пояснює цей механізм та способи вирішення. Щоб дізнатися, як оголошуються ці опубліковані порти, дивіться the basics of Docker Compose on a VPS.
4. UFW з вимкненим IPv6. UFW підтримує IPv6, але лише якщо це активовано. Перевірте налаштування:
grep IPV6 /etc/default/ufwСучасна Ubuntu постачається з IPV6=yes, тому UFW застосовує кожне правило до обох стеків. Якщо ви бачите IPV6=no (у старих образах або старих інструкціях), то всі ваші правила UFW діють лише для IPv4, а IPv6 залишається без керування.
Перевірте, що саме ви відкриваєте для зовнішнього світу
Не використовуйте припущення. Використовуйте вимірювання ззовні. Спочатку виведіть список слухачів і зверніть увагу на кожен, що прив'язаний до :::
sudo ss -tlnp | grep '::'Потім з іншої машини підключіться до публічної IPv6-адреси сервера та спробуйте порт, який, на вашу думку, є закритим:
curl -6 -v http://[2001:db8:2a::1]:8080/Якщо ви отримаєте сторінку або banner, порт відкритий через IPv6. Закритий порт поверне Connection refused або timeout. Щоб отримати повну картину, виконайте сканування IPv6-адреси за допомогою nmap з іншої машини:
nmap -6 2001:db8:2a::1Кожен порт, який nmap показує як відкритий через IPv6, доступний для всього інтернету, незалежно від результатів сканування IPv4. Порівняння результатів сканування IPv4 та IPv6 — це найшвидший спосіб виявити розбіжності: будь-який порт, відкритий на -6, але закритий на IPv4, є службою, яку ваш firewall не блокує.
Close the gap
Налаштуйте UFW для обох стеків і встановіть заборону за замовчуванням. Перевірте зміни, потім встановіть політику default-deny для вхідного трафіку та дозвольте лише необхідне:
sudo sed -i 's/^IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verboseЯкщо UFW вже був активним під час зміни IPV6=yes, зміни набудуть чинності лише після виконання sudo ufw reload.
ufw status відображає кожне правило двічі: звичайне та з суфіксом (v6). Рядки з (v6) означають, що UFW фільтрує IPv6:
22/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)Якщо ви керуєте iptables вручну, дублюйте кожне правило в ip6tables, або перейдіть на nftables. Таблиці inet у nftables охоплюють IPv4 та IPv6 в одному місці, що усуває такий тип помилок. Використання однієї таблиці фільтрації nftables inet є найпростішим рішенням при самостійному написанні правил.
Прив'язуйте сервіси, які не мають бути публічними, до loopback. Бази даних, панелі адміністрування або ендпоінти метрик рідко потребують публічної адреси. Прив'яжіть їх до 127.0.0.1 та ::1, щоб вони не прослуховували маршрутну адресу. Для Postgres встановіть listen_addresses = 'localhost'. Для серверів додатків використовуйте прив'язку до 127.0.0.1 і встановіть перед ними reverse proxy. Закриття прослуховування на інтерфейсі ефективніше за налаштування фаєрвола, оскільки до такого сервісу неможливо буде підключитися.
Не покладайтеся на UFW для захисту відкритих портів Docker. Публікуйте порти контейнерів на конкретній адресі, а не на всіх інтерфейсах (наприклад, -p 127.0.0.1:8080:80). Це зробить порт доступним лише з хоста та через визначені проксі. Якщо контейнер має бути публічним, розмістіть його за reverse proxy Traefik і публікуйте лише проксі, а не кожен додаток окремо.
Додайте правила IPv6 до фаєрвола вашого провайдера, або враховуйте, що він не захищає IPv6, і дозвольте UFW або nftables на хості виконувати цю функцію.
Переконайтеся, що доступ дійсно закрито
Запустіть той самий зовнішній тест після внесення змін:
curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1Порт, який відповідав раніше, тепер має відхиляти запит або переривати з'єднання за таймаутом. nmap має повідомити, що порт є filtered або closed. Якщо порт все ще відкритий, перевірте чотири попередні причини: сервіс все ще прив'язаний до :: без відповідного правила, правило Docker стоїть перед UFW або фаєрвол провайдера взагалі не підтримує IPv6.
Повне виключення критичних сервісів з публічного інтернету забезпечує вищий рівень захисту. Налаштуйте SSH та панелі адміністрування через WireGuard VPN та закрийте їхні порти фаєрволом. Тоді вони будуть доступні лише через тунель, і питання витоку через IPv6 для них зникне. Щоб сповільнити brute-force сканування публічних сервісів, використовуйте Fail2ban перед SSH разом із фаєрволом з політикою default-deny.
Якщо ви тільки знайомитеся з портами, спочатку прочитайте що таке порти та як сервіси їх слухають.
FAQ
Чи блокує UFW IPv6 за замовчуванням?
У сучасних інсталяціях Ubuntu 24.04 — так. UFW зчитує IPV6=yes з /etc/default/ufw і застосовує кожне правило до IPv4 та IPv6; ufw status відображає правила IPv6 із суфіксом (v6). Проблеми виникають, коли IPV6=no (з давніх образів або туторіалів), коли ви використовуєте фаєрвол провайдера, що фільтрує лише IPv4, або коли Docker публікує порт в обхід UFW. Перевірте налаштування за допомогою grep IPV6 /etc/default/ufw.
Як перевірити, які порти VPS відкриті для IPv6?
Запустіть sudo ss -tlnp і зверніть увагу на кожен listener, локальна адреса якого починається з [::]; це означає, що він відповідає на всіх інтерфейсах IPv6. Потім з іншої машини протестуйте публічну IPv6 адресу сервера безпосередньо за допомогою curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/ або проскануйте її за допомогою nmap -6 YOUR:IPV6::ADDR. Будь-який порт, відкритий під час сканування IPv6, але закритий для IPv4, є вразливістю.
Чому я можу отримати доступ до порту Docker-контейнера, якщо UFW каже, що він заблокований?
Docker вставляє власні правила фаєрвола перед правилами UFW, якщо ви публікуєте порт за допомогою -p. Тому опублікований порт доступний, навіть якщо ufw status вказує на заборону. Це стається в IPv4, а також в IPv6, якщо увімкнено підтримку IPv6 у Docker. Публікуйте порти на конкретну адресу, наприклад -p 127.0.0.1:8080:80, або використовуйте реверс-проксі та публікуйте лише порт проксі.
Чи потрібен мені IPv6 фаєрвол, якщо мій IPv4 фаєрвол налаштований правильно?
Так. IPv4 та IPv6 — це окремі мережеві стеки з окремими правилами фаєрвола. Набір правильних правил IPv4 не захищає трафік IPv6. Якщо ваш VPS має публічну IPv6 адресу (а вона є майже всюди), то будь-який сервіс, що слухає на ::, залишається доступним через IPv6, доки його не зупинить правило IPv6 фаєрвола або прив'язка до loopback.
Як змусити сервіс слухати лише IPv4 або лише localhost?
Встановіть bind address сервісу у його власному конфігураційному файлі. Використовуйте 127.0.0.1 для прив'язки лише до IPv4 loopback або 0.0.0.0 для всіх IPv4 адрес без створення listener для IPv6. Postgres використовує listen_addresses, SSH використовує ListenAddress, а більшість серверів додатків мають прапори host або bind. Перевірте результат за допомогою sudo ss -tlnp і переконайтеся, що Local Address більше не показує [::].