Category: Tech articles

  • Оперативная реакция на DDoS-атаки

    \"DDoS-network-map\"Один из ресурсов, за которым я присматриваю, вдруг стал неожиданно популярным как у хороших пользователей, так и у плохих. Мощное, в общем-то, железо перестало справляться с нагрузкой. Софт на сервере самый обычный — Linux, Nginx, PHP-FPM (+APC), MySQL, версии — самые последние. На сайтах крутится Drupal и phpBB. Оптимизация на уровне софта (memcached, индексы в базе, где их не хватало) чуть помогла, но кардинально проблему не решила. А проблема — большое количество запросов, к статике, динамике и особенно базе. Поставил следующие лимиты в Nginx:

    на соединения

    limit_conn_zone $binary_remote_addr zone=perip:10m;
    limit_conn perip 100;

    и скорость запросов на динамику (fastcgi_pass на php-fpm)

    limit_req_zone $binary_remote_addr zone=dynamic:10m rate=2r/s;
    limit_req zone=dynamic burst=10 nodelay;

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

    Но плохиши продолжали долбить, и захотелось их отбрасывать раньше — на уровне фаервола, и надолго.

    Сначала сам парсил логи, и особо настырных добавлял через iptables в баню. Потом парсил уже по крону каждые 5 минут. Пробовал fail2ban. Когда понял, что плохишей стало очень много, перенёс их в ipset ip hash.

    Почти всё хорошо стало, но есть неприятные моменты:
    — парсинг/сортировка логов тоже приличное (процессорное) время отнимает
    — сервер тупит, если началась новая волна между соседними разборками (логов)

    Нужно было придумать как быстро добавлять нарушителей в черный список. Сначала была идея написать/дописать модуль к Nginx + демон, который будет ipset-ы обновлять. Можно и без демона, но тогда придётся запускать Nginx от рута, что не есть красиво. Написать это реально, но понял, что нет столько времени. Ничего похожего не нашёл (может плохо искал?), и придумал вот такой алгоритм.

    При привышении лимита, Nginx выбрасывает 503-юю ошибку Service Temporarily Unavailable. Вот я решил на неё и прицепиться!

    Для каждого location создаём свою страничку с ошибкой

    error_page 503 =429 @blacklist;

    И соответствующий именованный location

    location @blacklist {
        fastcgi_pass    localhost:1234;
        fastcgi_param   SCRIPT_FILENAME    /data/web/cgi/blacklist.sh;
        include         fastcgi_params;
    }

    Дальше интересней.

    Нам нужна поддержка CGI-скриптов. Ставим, настраиваем, запускаем spawn-fcgi и fcgiwrap. У меня уже было готовое для collectd.

    Сам CGI-скрипт fc

    #!/bin/bash
    
    BAN_TIME=5
    DB_NAME=\"web_black_list\"
    SQLITE_DB=\"/data/web/cgi/${DB_NAME}.sqlite3\"
    CREATE_TABLE_SQL=\"\\
    CREATE TABLE $DB_NAME (\\
        ip varchar(16) NOT NULL PRIMARY KEY,\\
        added DATETIME NOT NULL DEFAULT (DATETIME()),\\
        updated DATETIME NOT NULL DEFAULT (DATETIME()),\\
        counter INTEGER NOT NULL DEFAULT 0
    )\"
    ADD_ENTRY_SQL=\"INSERT OR IGNORE INTO $DB_NAME (ip) VALUES (\"$REMOTE_ADDR\")\"
    UPD_ENTRY_SQL=\"UPDATE $DB_NAME SET updated=DATETIME(), counter=(counter+1) WHERE ip=\"$REMOTE_ADDR\"\"
    SQLITE_CMD=\"/usr/bin/sqlite3 $SQLITE_DB\"
    IPSET_CMD=\"/usr/sbin/ipset\"
    
    $IPSET_CMD add $DB_NAME $REMOTE_ADDR > /dev/null 2>&1
    
    if [ ! -f $SQLITE_DB ]; then
         $SQLITE_CMD \"$CREATE_TABLE_SQL\"
    fi
    
    $SQLITE_CMD \"$ADD_ENTRY_SQL\"
    $SQLITE_CMD \"$UPD_ENTRY_SQL\"
    
    echo \"Content-type: text/html\"
    echo \"\"
    
    echo \"<html>\"
    echo \"<head><title>429 Too Many Requests</title></head>\"
    echo \"<body bgcolor=\"white\">\"
    echo \"<center><h1>429 Too Many Requests</h1></center>\"
    echo \"<center><small><p>Your address ($REMOTE_ADDR) is blacklisted for $BAN_TIME minutes</p></small></center>\"
    echo \"<hr><center>$SERVER_SOFTWARE</center>\"
    echo \"</body>\"
    echo \"</html>\"

    Собственно всё очевидно, кроме, разве что, SQLite. Я его добавил пока просто для статистики, но в принципе можно использовать и для удаления устаревших плохишей из черного списка. Время 5 минут пока тоже не используется.

    Черный список создавался вот так

    ipset create web_black_list hash:ip

    Правило в iptables у каждого может быть своё, в зависимости от конфигурации и фантазии.

    З.Ы.: Улыбнуло сообщение одного «хакера» на форуме, как быстро он положил сервер. Он и не догадывался, что это сервер положил на него.

    Источник: http://habrahabr.ru/post/176119/