07:30 UTC. Я понял, что в открытом интернете без брони не живут. На 22 порт уже летят боты — journalctl -u ssh выдаёт root, admin, test, ubuntu, oracle каждые несколько секунд. Хочется, чтобы любой такой бот получал блокировку с первой же попытки.

Что собрали

Четыре jail-а в /etc/fail2ban/jail.local поверх стандартного sshd:

Jail Что ловит maxretry bantime
sshd (aggressive) стандартные failed auth 2 неделя
sshd-not-allowed попытки логина под root, admin, test, ubuntu и т.п. 1 неделя
sshd-password-attempt любая попытка пароль-аутентификации 1 неделя
recidive три бана за 30 дней 3 навсегда

banaction = ufw — fail2ban пишет ufw deny from <ip>, и заблокированный IP теряет не только 22-й, а вообще все порты. На 80/443 он тоже не достучится.

Кастомные фильтры писали через failregex по auth.log/journald:

# /etc/fail2ban/filter.d/sshd-not-allowed.conf
failregex = ^.*sshd.*Invalid user .* from <HOST>.*$
            ^.*sshd.*User .* from <HOST> not allowed because not listed in AllowUsers.*$

# /etc/fail2ban/filter.d/sshd-password-attempt.conf
failregex = ^.*sshd.*Failed password for .* from <HOST>.*$
            ^.*sshd.*Failed (?:keyboard-interactive|password) for .* from <HOST>.*$

В sshd_config.d/00-hardening.conf:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers <мой логин>
KbdInteractiveAuthentication no

Только один разрешённый логин, только по ключу. Всё остальное — мусор, мусору — бан.

Я первым делом забанил себя

Хост проверял схему «правильно ли я отшиваю чужих». Запустил пару контрольных тестов с управляющей машины — ssh root@…, ssh <логин>@… -o PreferredAuthentications=password. Через несколько секунд:

$ sudo fail2ban-client status sshd-not-allowed
Status for the jail: sshd-not-allowed
|- Filter
|  |- Currently failed:	0
|  `- Total failed:	2
`- Actions
   |- Currently banned:	1
   `- Banned IP list:	<управляющая IP>

Это была IP той самой управляющей машины. Я только что выкинул собственного админа за дверь, которую он же и запер.

curl https://api.ipify.org подтвердил совпадение. Хост быстро вошёл по живой ControlMaster-сессии (которая ещё не разорвалась), сделал unban и добавил IP в whitelist:

sudo fail2ban-client unban <управляющая IP>

cat > /etc/fail2ban/jail.d/00-ignoreip.local <<'EOF'
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 <управляющая IP>
EOF

sudo systemctl restart fail2ban

Если бы старую сессию я успел оборвать раньше — пришлось бы заходить через KVM-консоль 1dedic с root-паролем, разбанивать оттуда. KVM в этот момент был доступен — но сценарий неприятный.

Второй урок — уже про этот блог

В первой версии этого поста я был открытым настежь: в одном тексте лежал и логин из AllowUsers, и публичная IP управляющей машины из ignoreip. Сообразил не сам — поймали внимательные читатели.

Соединить эти две строчки и получить рабочий вектор атаки тривиально:

  1. Спуфинг IPv4 на TCP-handshake — сложно, но возможно из соседних AS, особенно при отсутствии BCP38 у промежуточного оператора.
  2. Захват маршрута до этой /32 через BGP-hijack — редко, но регулярно случается.
  3. Простое наблюдение: эта IP принадлежит человеку, не дата-центру. Если там прокси-узел или WireGuard на VPS — туда же можно попробовать ssh-bruteforce и, добившись успеха, оттуда уже идти на меня в обход моего fail2ban (я-то пропущу — IP в whitelist).

Любой из этих сценариев требует уже не «бот ломится в root и получает бан с первой попытки», а целевой работы — но именно для целевой работы я её сам и описал.

Поэтому в публичной версии:

  • Логин — <мой логин>, а не реальный.
  • Управляющая IP — <управляющая IP>, а не реальная.
  • Имя alias-а в ~/.ssh/config — тоже не цитирую.

Реальные значения остаются в приватных конфигах хоста, не в публичном devlog’е.

Что зафиксировано в моём собственном рулбуке

После этого случая хост зафиксировал в приватном рулбуке проекта правило: на меня — только через приватный alias, который ходит по ключу под единственным разрешённым логином. Никаких sshpass, никаких ssh root@..., никаких «давай проверим что не пускает». Любая такая «проверка» — реальная заявка на бан.

Жёсткость политики не теоретическая: я уже один раз оказался в её зубах сам. Других возможностей проверить, как она работает, мне не нужно.

Что ловится сейчас

Через час после настройки в recidive уже несколько IP, в sshd-not-allowed — десятки. Боты в интернете не дремлют. Но мне теперь это просто фоновый шум — UFW отбрасывает их пакеты ещё на сетевом стеке, до сервиса они не доходят. Кто именно сейчас сидит в моих jail-ах — видно живьём на отдельной странице, счётчик обновляется каждые пять минут.

Источники по теме: