Настройка UFW для IPv6 на VPS: как закрыть уязвимости
Ваш VPS может быть открыт для атак через IPv6, даже если правила UFW для IPv4 настроены верно. Узнайте, как проверить состояние портов и правильно закрыть доступ к службам.
Ловушка IPv6-файрвола в одном предложении
Ваш файрвол защищает IPv4. Ваш VPS почти наверняка имеет публичный IPv6-адрес, и многие службы по умолчанию ожидают соединений на нем. Если ваш файрвол настроен только для IPv4 или вы полагаетесь на облачный файрвол, фильтрующий только 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-трафик. Таким образом, стандартное состояние свежего сервера — «отвечать на обоих стеках, отовсюду». Ваш межсетевой экран — это единственное, что защищает сервер, поэтому использование брандмауэра, который видит только один стек, является серьезной проблемой.
Откуда на самом деле берется пробел в IPv6
Существует четыре распространенных источника. На конкретном сервере у вас может быть один из них или сразу несколько.
1. Облачный межсетевой экран, фильтрующий только IPv4. Многие брандмауэры провайдеров и продукты для управления группами безопасности развивались вокруг IPv4. Они либо игнорируют IPv6, либо требуют ручного добавления отдельных правил для IPv6. Если ваш единственный межсетевой экран — это панель управления провайдера, и она не охватывает IPv6, ваши [::] сервисы открыты, независимо от того, что там сказано про 22 порт в IPv4. Изучите документацию провайдера по межсетевому экрану и ищите упоминание именно IPv6.
2. Самописные правила iptables без ip6tables. Команда iptables затрагивает только таблицы IPv4. Для IPv6 существует отдельная команда ip6tables со своим собственным набором правил. Если вы написали скрипт межсетевого экрана, полный строк iptables -A INPUT ..., но не добавили соответствующие правила ip6tables, ваш IPv6-брандмауэр пуст. Пустая цепочка INPUT с политикой по умолчанию ACCEPT разрешает всё:
sudo ip6tables -L INPUT -nChain INPUT (policy ACCEPT)
target prot opt source destinationЭтот вывод — вся ловушка на одном экране. IPv4 фильтруется, а IPv6 принимает весь мир.
3. Docker публикует порты в обход вашего межсетевого экрана. Когда вы запускаете 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 или приведет к тайм-ауту. Эти два типа сбоев означают разное, и разница между отказом в соединении и тайм-аутом показывает, ответил ли хост отказом или пакет был молча отброшен межсетевым экраном. Для получения полной картины просканируйте IPv6-адрес с помощью nmap с внешнего узла:
nmap -6 2001:db8:2a::1Каждый порт, который nmap помечает как открытый по IPv6, доступен из Интернета, независимо от результатов сканирования по IPv4. Сравнение результатов сканирования IPv4 и IPv6 — самый быстрый способ обнаружить брешь: всё, что открыто на -6, но закрыто на IPv4, является сервисом, который вы упустили при настройке межсетевого экрана.
Устранение уязвимостей
Настройте 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 одновременно, что исключает подобные ошибки. Единая таблица фильтрации inet в nftables — наиболее чистое решение при написании правил вручную. Если на вашем VPS используется Rocky или AlmaLinux, а не Ubuntu, то UFW отсутствует, и в качестве интерфейса управления используется firewalld, который применяет правила зон к обоим стекам одновременно.
Привязывайте сервисы, не предназначенные для публичного доступа, к 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 в дополнение к политике сетевого экрана по умолчанию «запретить всё».
Если понятие портов для вас ново, что такое порты и как сервисы ожидают соединений — это базовый материал, с которого стоит начать.
FAQ
Блокирует ли UFW IPv6 по умолчанию?
В современной установке Ubuntu 24.04 — да. UFW считывает IPV6=yes из файла /etc/default/ufw и применяет каждое правило как к IPv4, так и к IPv6, а команда ufw status отображает правила IPv6 с суффиксом (v6). Проблема возникает, если в IPV6=no установлено значение IPV6=no (из старого образа или устаревшего руководства), если вы полагаетесь на сетевой экран провайдера, который фильтрует только IPv4, или если Docker публикует порт в обход UFW. Проверьте состояние переключателя с помощью grep IPV6 /etc/default/ufw.
Как проверить, какие порты VPS открыты для IPv6?
Выполните sudo ss -tlnp и обратите внимание на все процессы, чей локальный адрес начинается с [::]: это означает, что они отвечают на всех интерфейсах 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, или поместите контейнер за reverse proxy и публикуйте только сам прокси.
Нужен ли мне IPv6-файрвол, если мой IPv4-файрвол настроен надежно?
Да. IPv4 и IPv6 — это отдельные сетевые стеки с независимыми правилами фильтрации. Идеально настроенные правила IPv4 никак не влияют на трафик IPv6. Если ваш VPS имеет публичный IPv6-адрес (а они есть почти у всех), любой сервис, слушающий ::, остается доступным по IPv6, пока его не ограничит правило IPv6-файрвола или привязка к loopback-интерфейсу.
Как сделать так, чтобы сервис слушал только IPv4 или только localhost?
Укажите адрес привязки в конфигурации самого сервиса. Используйте 127.0.0.1 для привязки только к IPv4 loopback или 0.0.0.0 для всех IPv4-адресов без IPv6-слушателя. Postgres использует listen_addresses, SSH использует ListenAddress, а большинство серверов приложений имеют флаг для указания хоста или адреса привязки. Подтвердите результат командой sudo ss -tlnp и убедитесь, что в Local Address больше не отображается [::].