self-hosted

Раунд 2. Ботнет, 3.2 млн запросов и return 444

Раунд 2. Ботнет, 3.2 млн запросов и return 444

Боты вернулись: 3.2 млн запросов к commit hash URL-ам, CPU 136%, fail2ban 431 match и 0 banned. Пришлось учить Nginx говорить тишиной.

Я уже воевал с ботами на Gitea — тогда помогли memory limits, rate limiting на 5r/s и robots.txt. CPU упал с 173% до 26%, RAM с 7.2 до 3.5 GiB. Казалось, всё.

Не всё.

Gitea снова упёрлась в потолок: CPU 136%, RAM плотно у лимита 2 GiB. SQL-запросы висят по 6 секунд. Это уже не краулер — что-то другое.

Полез в логи Nginx. 3.2 миллиона запросов, и почти все — к одному репозиторию: OpenSourceArk/mkdocs-material. Причём не к README и не к файлам — к commit hash URL-ам. src/commit/a3f8b2c, blame/commit/7d1e4f9, archive/commits/... — Gitea на каждый такой запрос лезет в базу, строит diff, рендерит страницу. Дорого.

Попробовал fail2ban. Настроил jail, запустил — 431 match, 0 banned. Ноль. Потому что у ботнета уникальных IP как у краулера ног: каждый заходит, делает пару запросов и уходит. Rate limiting тоже мимо: за интервал один IP укладывается в норму. Боты игнорируют robots.txt ещё с прошлого раза — это вообще не сюрприз.

User-Agent-ы при этом честные до неприличия: Amazonbot, meta-externalagent (это Facebook), ну и классика — поддельные Chrome и Firefox с нулевым сходством с реальными браузерами.

Если IP бесполезны — остаётся блокировать по паттерну URL. Все проблемные запросы имеют общую структуру: commit hash в пути — 40-символьная шестнадцатеричная строка. Добавил в конфиг Nginx:

location ~* /(src|commits|blame|raw)/commit/[a-f0-9] {
    return 444;
}
location ~* /archive/[a-f0-9] {
    return 444;
}
location ~* /compare/ {
    return 444;
}

return 444 — это не 403 и не 429. Nginx просто закрывает соединение без ответа. Никаких байт назад, никакого тела ответа. Боты получают разрыв и уходят.

Эффект почти мгновенный. CPU упал до 12.7%, RAM — до 306 MiB. Из 3.2 миллиона запросов за время атаки только 13 получили 429 от rate limiting — rate limiting вообще не работал как защита, просто шумел в логах.

Но есть нюанс, который меня немного беспокоит. Боты всё равно достучались до сервера — TLS handshake, парсинг заголовков — это уже стоит ресурсов. Nginx отбивает запрос быстро, но при 3 миллионах попыток даже «быстро» складывается. Плюс — это был паттерн commit/[a-f0-9]. Если завтра они начнут ломиться через другой эндпоинт, правило не сработает. Я закрыл конкретную дыру, а не решил проблему.

Не знаю, стоит ли ставить geo-блокировку или пробовать crowdsec — или это оверинжиниринг для личного сервера. Пока смотрю на метрики и жду раунда 3...

Все записи загружены