Защита от DDoS на уровне веб-сервера

Статистика DDoS-атак показывает неизменный рост и смещение вектора с сетевого уровня на уровень приложений.

image

Если у Вас есть небольшой сайт на сервере с минимальными характеристиками, то положить его можно любым вполне легальным средством стресс-тестирования. (Не рекомендую этого никому делать т.к. IP-адрес легко вычисляется и экспериментатор может влететь на возмещение ущерба.) Поэтому сайт без защиты от DDoS очень скоро будет выглядеть так же дико, как компьютер с Windows-98 без анивирусника.

Первое, что можно и нужно сделать для защиты сайта — настроить брандмауэр iptables. Я использую почти без изменений настройки iptables из статьи на сайте одного из поставщиков защиты от DDoS-атак. Единственное что я поменял — увеличил число допустимых соединений в правилах #8 и #10.

Прежде чем запускать на выполнение скрипт, меняющий параметры iptables, нужно убедиться, что есть альтернативная возможность сбросить эти параметры в начальное состояние. Т.к. если правила заданы неверно, к серверу не сможет подключится никто, в том числе и администратор.

Брандмауэр iptables контролирует атаку на сетевом уровне. Следующее что необходимо настроить — это веб-сервер. В качестве веб-сервера, который открыт для доступа из интернет будем рассматривать nginx. В файле nginx.conf нужно увеличить лимиты на колчество файлов и открытых коннектов (пример взят из Википедии):

 # Увеличение максимального количества используемых файлов
  worker_rlimit_nofile 80000;
  events {
      # Увеличение максимального количества соединений
      worker_connections 65536;
      # Использование эффективного метода epoll для обработки соединений
      ...
  }

Далее настраиваем сервер по умолчанию, который будет запрещать доступ для тех устройств (например IoT), который будут обращаться по IP-адресу, а не по доменному имени:

# Default server configuration
#
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    deny  all;
}

Так же в nginx можно настроить некоторые лимиты на количество обращений, но такая настройка будет не очень гибкой и совсем не избирательной. Наша цель сделать такую защиту на уровне веб-сервера, чтобы погасить запросы злоумышленников, но пропускать на сервер запросы добропорядочных пользователей.

Чтобы не пересказывать уже имеющийся на Хабре материал, предлагаю ознакомится с отличной статьей, а так же с модулем автора указаной статьи kyprizel/testcookie-nginx-module. То, что позволяет сделать этот модуль уже хорошо. Но если Вам понадобится модернизировать его — то сделать это будет непросто.

На сегодняшний день очень многие поставщики услуг защиты от DDoS используют сервер openresty (связка nginx + Lua от Taobao). Скорость выполнения хорошего кода на Lua немного уступает хорошему коду на С. Но разрабатывать на Lua быстрее и проще, и к тому же скрипты можно менять без перекомпиляции сервера. При следуюшем рестарте они будут прочитаны, скомпилированы LuaJIT, и это все что требуется.

Подробная инструкция как уcтановить optnresty. После установки продолжаем настраивать nginx. В разделе http определяем нужные параметры для работы скритов Lua:

lua_shared_dict whitelist 10m;
lua_shared_dict banlist 100m;

lua_package_path '/home/username/antiddos/?.lua;;';

init_by_lua '
    local whitelist = ngx.shared.whitelist
    whitelist:add("1.2.3.4", true)
    whitelist:add("5.6.7.8", true)
';

access_by_lua_file /home/username/antiddos/main.lua;

Строка lua_shared_dict создает новый словарь (ключ-значение). Этот словарь будет единым для всех запросов, поэтому в нем удобно хранить белые и черные списки. Это словарь, кроме параметров ключ-значение, может иметь параметр time-to-live, который идеально подходит для хранения счетчиков, если нужно ограничить количество запросов в промежуток времени.

Строка lua_package_path задает пути для поиска модулей Lua, в котрый нужно включить каталог со скриптами. Две подряд точки с запятой в конце строки означают, что этот путь добавляется к текущему значению пути, а не полностью заменяет его.

Строка init_by_lua задает код, который будет выполнен один раз при старте сервера (а не при каждом новом запросе). В ней задается белый список IP-адресов. Второй праметр функции add — true — это просто значние которое потом используется в операторе if. Третий параметр time-to-live отсутсвует, поэтому значение будет храниться без ограничения по времени.

Строка access_by_lua_file задает путь к скрипту, который будет выполняться при каждом запросе к серверу (не только при старте сервера). В нем, собственно, и находится вся логика защиты.

Рассмотрим некоторые из проверок, которые можно сделать при помощи скрипта на Lua:

-- if client IP is in whitelist, pass
local whitelist = ngx.shared.whitelist
in_whitelist = whitelist:get(ngx.var.remote_addr)
if in_whitelist then
    return
end

-- HTTP headers
local headers = ngx.req.get_headers();

-- wp ddos
if type(headers["User-Agent"]) ~= "string"
    or headers["User-Agent"] == ""
    or ngx.re.find(headers["User-Agent"], "^WordPress", "ioj") then
    ngx.log(ngx.ERR, "ddos")
    ngx.exit(444)
    return
end

local banlist = ngx.shared.banlist
local search_bot = "search:bot:count:request:per:10:s"
if ngx.re.find(headers["User-Agent"], "Google Page Speed Insights|Googlebot|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator", "ioj") then
   local count, err = banlist:incr(search_bot, 1)
    if not count then
        banlist:set(search_bot, 1, 10)
        count = 1
    end
    if count >= 50 then
        if count == 50 then
            ngx.log(ngx.ERR, "bot banned")
        end
        ngx.exit(444)
        return
    end
    return
end

Язык Lua во многом похож (даже слишком) на JavaScript, поэтому код на Lua интуитивно понятен всем, кто пишет на JavaScript.

Глобальная переменная ngx служит для связи с контекстом сервера nginx. Оператор return вне тела функции означает возврат из модуля. В данном примере, если IP-адрес в белом списке, то работа скрипта оканчивается, и продолжается обычная обработка запроса nginx.

Далее распознается атака, основанная на особенностей реализации CMS WordPress. Если атака выявлена то работа оканчивается специальным статусом 444 (характерен только для nginx): ngx.exit(444).

И, наконец, мы даем «зеленую дорогу» поисковым ботам. Тут приходится использовать счетчик, т.к. под поискового бота часто подделываются злоумышленники — поэтому считаем количество обращений. banlist:set(search_bot, 1, 10) инициализирует счетчик, который обнулится через 10 секунд после создания. banlist:incr(search_bot, 1) прибавляет к текущему значению счетчика единицу.

Дальнейшее распознавание ботов и злоумышленников может вестись по разным направлениям. Как было предложено в статье такое распознавание основано на проверках, поддерживает ли клиент редиректы, установку cookie и выполнения кода JavaScript. Ну или все, что можно еще придумать.

Как правило, такой веб-сервер используют в качестве прокси, а защищаемый веб-сервер размещают на другом IP-адресе.

apapacy@gmail.com
22 января 2018 года.

© Habrahabr.ru