UFW і IPv6: як закрити відкриті порти на VPS
UFW може захищати IPv4, але не IPv6 на всіх рівнях. Дізнайтеся, як перевірити Ubuntu 24.04 VPS і закрити доступ до сервісів через IPv6.
Пастка брандмауера IPv6 в одному реченні
Ваш брандмауер захищає IPv4. Ваш VPS майже напевно також має публічну IPv6-адресу, і багато сервісів типово прослуховують її. Якщо ваш брандмауер охоплює лише IPv4 або ви покладаєтеся на хмарний брандмауер, який фільтрує тільки IPv4, кожен із цих сервісів доступний через IPv6 з усього інтернету, хоча з боку IPv4 доступ виглядає повністю обмеженим. Ви перевіряєте порт за допомогою curl, бачите відхилене з’єднання й вважаєте систему захищеною. Зловмисник підключається до того самого порту через IPv6 і отримує доступ.
У цьому посібнику показано, звідки виникає така прогалина на звичайному Ubuntu 24.04 VPS, як точно перевірити, що саме ви відкрили назовні, і як це закрити. 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-group продуктів провайдерів спочатку створювалися для IPv4. Вони можуть ігнорувати IPv6 або потребувати окремих правил IPv6, які потрібно додавати вручну. Якщо єдиний firewall налаштований у панелі провайдера і він не охоплює IPv6, ваші [::] сервіси відкриті незалежно від того, що в ньому зазначено про порт 22 для IPv4. Прочитайте документацію провайдера щодо firewall і окремо знайдіть у ній слово IPv6.
2. Самостійно написані правила iptables без ip6tables. Команда iptables працює лише з таблицями IPv4. Для IPv6 є окрема команда ip6tables із власними правилами. Якщо ви написали firewall-скрипт із великою кількістю рядків iptables -A INPUT ..., але не додали відповідні правила ip6tables, firewall IPv6 порожній. Порожній ланцюжок 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. У статті Чому Docker обходить UFW і як правильно фільтрувати порти контейнерів пояснено механізм і способи виправлення. У статті Основи Docker Compose на 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/Якщо у відповідь надходить сторінка або банер, порт відкритий через IPv6. Закритий порт повертає Connection refused або спричиняє тайм-аут. Ці два результати мають різне значення. Різниця між відхиленим з’єднанням і тайм-аутом показує, чи хост відповів і відхилив запит, чи firewall мовчки відкинув ваш пакет. Щоб отримати повну картину, проскануйте IPv6-адресу за допомогою nmap із зовнішньої машини:
nmap -6 2001:db8:2a::1Кожен порт, який nmap визначає як відкритий через IPv6, доступний з усього інтернету незалежно від результатів сканування IPv4. Порівняння сканувань IPv4 та IPv6 поруч — найшвидший спосіб виявити розбіжність: усе, що відкрите через -6, але закрите через IPv4, — це сервіс, для якого у вашому firewall відсутнє правило.
Закрийте прогалину
Налаштуйте UFW для обох стеків і встановіть політику заборони за замовчуванням. Перевірте перемикач, потім встановіть політику заборони вхідних з’єднань і дозвольте лише потрібний трафік:
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 охоплюють IPv4 та IPv6 в одному місці й усувають цей клас помилок. Якщо ви пишете правила самостійно, одна фільтрувальна таблиця nftables inet є найпростішим рішенням. Якщо ваш VPS працює на Rocky або AlmaLinux, а не на Ubuntu, UFW там немає: натомість ви керуєте firewalld як фронтендом, і його правила зон одночасно застосовуються до обох стеків.
Прив’язуйте сервіси, які не мають бути публічними, до loopback. Базі даних, панелі адміністрування або endpoint для метрик зазвичай взагалі не потрібна публічна адреса. Прив’яжіть сервіс до 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; або firewall у провайдера взагалі не обробляє IPv6.
Ще надійніше повністю прибрати конфіденційні сервіси з публічного інтернету. Розмістіть SSH і панелі адміністрування за VPN WireGuard та обмежте їхні порти правилами firewall, щоб вони відповідали лише через тунель. Тоді питання доступності через IPv6 до них більше не застосовується. Щоб уповільнити brute-force-сканування публічно доступних сервісів, додайте Fail2ban перед SSH поверх firewall із політикою заборони за замовчуванням.
Якщо поняття портів для вас нове, спочатку прочитайте що таке порти та як сервіси прослуховують їх.
FAQ
Чи блокує UFW IPv6 за замовчуванням?
У сучасній інсталяції Ubuntu 24.04 — так. UFW читає IPV6=yes з /etc/default/ufw і застосовує кожне правило до IPv4 та IPv6, а ufw status показує правила IPv6 із суфіксом (v6). Проблема виникає, якщо IPV6=no (зі старого образу або старого посібника), якщо ви покладаєтеся на firewall провайдера, який фільтрує лише 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 вставляє власні правила firewall перед правилами UFW, коли ви публікуєте порт за допомогою -p. Тому опублікований порт доступний, навіть якщо ufw status показує його як заборонений. Це відбувається в IPv4, а також в IPv6, якщо підтримку IPv6 у Docker увімкнено. Опублікуйте порт для конкретної адреси, наприклад -p 127.0.0.1:8080:80, або розмістіть контейнер за reverse proxy і публікуйте лише порт проксі.
Чи потрібен мені firewall IPv6, якщо мій firewall IPv4 правильно налаштований?
Так. IPv4 та IPv6 — це окремі мережеві стеки з окремими правилами firewall. Ідеальний набір правил IPv4 не впливає на трафік IPv6. Якщо ваш VPS має публічну IPv6-адресу, а її мають майже всі VPS, будь-який сервіс, що слухає ::, залишається доступним через IPv6, доки його не зупинить правило firewall IPv6 або прив’язка до loopback-адреси.
Як налаштувати сервіс на прослуховування лише IPv4 або лише localhost?
У конфігурації самого сервісу задайте адресу прив’язки. Використовуйте 127.0.0.1 лише для loopback IPv4 або 0.0.0.0 для всіх адрес IPv4 без listener IPv6. Postgres використовує listen_addresses, SSH — ListenAddress, а більшість серверів застосунків мають параметр host або bind. Перевірте результат за допомогою sudo ss -tlnp і переконайтеся, що Local Address більше не показує [::].