РУКОВОДСТВО ПО БЕЗОПАСНОСТИ СЕРВЕРОВ

Как защититься от брутфорса SSH на VPS

Коротко: брутфорс SSH по-настоящему прекращает одно изменение: отключить пароли, чтобы подбирать было нечего. Делайте это только после того, как убедились, что вход по ключу работает. Сверху добавьте более дешёвые слои: PermitRootLogin no, список AllowUsers, собственные лимиты sshd, а также fail2ban или ограничение частоты в ufw. Сначала измерьте атаку, чтобы увидеть работу каждого слоя: посчитайте неудачные входы через journalctl, узнайте, откуда они идут, измените одну вещь и посчитайте снова. Основной шум после этого не исчезает и безвреден. Порядок внедрения ниже не оставит вас без доступа к собственному серверу.

Быстрый результат (15 минут)

  • Посчитайте исходный уровень: sudo journalctl -u ssh --since yesterday --no-pager -o cat -g 'Failed password' | wc -l (в семействе RHEL юнит называется sshd).
  • Откройте вторую SSH-сессию и не закрывайте её, а также найдите веб-консоль или режим восстановления в панели хостинга.
  • Убедитесь, что ключ работает, из нового терминала: ssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no myserver.
  • Задайте PasswordAuthentication no, KbdInteractiveAuthentication no и PermitRootLogin no, выполните sudo sshd -t, перезагрузите конфигурацию и проверьте из третьего терминала.
  • Завтра повторите подсчёт и сравните: попытки продолжают приходить, но ни одна не может удаться.

Что на самом деле останавливает брутфорс SSH, по порядку

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

  1. Пароли выключены. PasswordAuthentication no, после того как вход по ключу подтверждён. Это убирает саму цель.
  2. Вход root выключен. PermitRootLogin no убирает из игры самое часто подбираемое имя.
  3. Список разрешённых. AllowUsers означает, что войти могут только названные учётные записи.
  4. Собственные лимиты sshd. MaxAuthTries, LoginGraceTime и MaxStartups замедляют каждое соединение.
  5. Ограничение частоты. fail2ban или ufw limit сокращают число попыток, доходящих до sshd.
  6. Другой порт. Косметика: тише журналы, но настоящей защиты нет.
  7. Двухфакторная аутентификация или бастион. Для серверов, где украденный ключ обойдётся дорого.

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

Сначала измерьте атаку

Прежде чем что-либо менять, посчитайте, с чем вы имеете дело. Исходные цифры позволяют увидеть эффект каждого слоя, а не гадать. На сервере с systemd sshd пишет в журнал, который читается через journalctl. Юнит называется ssh в Debian и Ubuntu и sshd в семействе RHEL. Фильтруйте по юниту, а не по имени процесса: начиная с OpenSSH 9.8 сервер разделён на процесс-слушатель и отдельный бинарный файл для сессий, поэтому часть сообщений приходит от процесса с именем sshd-session (см. примечания к выпуску OpenSSH 9.8).

# неудачные попытки по паролю со вчерашнего дня
sudo journalctl -u ssh --since yesterday --no-pager -o cat -g 'Failed password' | wc -l

# десять самых активных адресов
sudo journalctl -u ssh --since yesterday --no-pager -o cat -g 'Failed password' \
  | grep -oE 'from [0-9a-fA-F.:]+ port' | awk '{print $2}' \
  | sort | uniq -c | sort -rn | head

# десять самых часто пробуемых имён
sudo journalctl -u ssh --since yesterday --no-pager -o cat -g 'Failed password' \
  | sed -E 's/.*Failed password for (invalid user )?([^ ]+) from.*/\2/' \
  | sort | uniq -c | sort -rn | head

Руководство journalctl описывает -u (юнит), --since (понимает yesterday), -g (фильтр по регулярному выражению) и -o cat (только текст сообщения). Если на сервере пароли уже отключены, ищите 'Invalid user', так как строк о неверном пароле не будет. Если sshd пишет в файл, например /var/log/auth.log (Debian, Ubuntu) или /var/log/secure (семейство RHEL), выполните тот же grep по этому файлу. Две команды только для чтения показывают неудачные и успешные входы:

sudo lastb -i -n 20
last -i -n 20

Согласно руководству lastb, он читает /var/log/btmp, а этого файла в системе может не быть, поэтому надёжным источником считайте journalctl. Подбор выглядит так (адреса взяты из диапазонов для документации):

Failed password for invalid user admin from 203.0.113.50 port 41022 ssh2
Failed password for root from 203.0.113.50 port 41030 ssh2
Invalid user test from 198.51.100.7 port 52114
maximum authentication attempts exceeded for root from 203.0.113.50 port 41044 ssh2

Читать это нужно так. Invalid user означает, что такой учётной записи на сервере нет: бот перебирает список имён и не целится именно в вас. Много имён с одного адреса означает перебор по словарю. Одно имя с множества адресов означает распределённый перебор, и именно с ним хуже всего справляется счётчик по адресу. Строка maximum authentication attempts exceeded показывает, что MaxAuthTries делает свою работу. Ничто из этого не является взломом. Важна строка успешного входа сразу после серии неудач с того же адреса или для той же учётной записи, поэтому проверьте также sudo journalctl -u ssh --since yesterday --no-pager -o cat -g 'Accepted' и сравните со своими входами. Если нашли вход, которого не можете объяснить, переходите к руководству по реагированию на взлом сервера.

Запишите исходные цифры: общее число неудач, главные адреса, главные имена. Это значение вам предстоит улучшить.

Слой 1: безопасно отключаем пароли

Это изменение прекращает атаку, и это же единственное изменение, способное закрыть вам доступ, поэтому каждый раз, когда вы трогаете sshd, действуйте по одной схеме:

  1. Не закрывайте текущую сессию, пока не откроется новая.
  2. Заранее узнайте, как попасть в веб-консоль или режим восстановления хостинга: они работают, даже когда SSH не работает.
  3. Сделайте копию файла: sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.
  4. Перед каждой перезагрузкой проверяйте конфигурацию командой sudo sshd -t и тестируйте из второго терминала.

Сначала докажите, что вход по ключу работает. Псевдоним Host в ~/.ssh/config на вашей рабочей станции делает команды короче:

Host myserver
    HostName 203.0.113.10
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
ssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no myserver

Если команда открыла оболочку, не спросив пароль, ключ работает. Если нет, остановитесь и сначала исправьте это, начав с руководства по ошибке Permission denied (publickey). Пара ключей создаётся командой ssh-keygen. Затем отредактируйте sshd_config:

PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no

Важны обе строки. В руководстве sshd_config(5) для PasswordAuthentication и для KbdInteractiveAuthentication по умолчанию указано yes, а в портируемом руководстве сказано, что при UsePAM интерактивный метод с клавиатуры обычно играет ту же роль, что и вход по паролю (см. PAM). Отключив только один из них, можно оставить запрос пароля. Теперь проверьте синтаксис, прочитайте значения, которые sshd реально применит, и перезагрузите конфигурацию:

sudo sshd -t
sudo sshd -T | grep -iE '^(passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication) '
sudo systemctl reload ssh     # в семействе RHEL юнит называется sshd

Согласно руководству sshd(8), -t проверяет файл конфигурации, а -T выводит действующую конфигурацию. Затем из нового терминала подтвердите обе половины: ключ по-прежнему работает, а пароль отвергается.

ssh myserver
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password myserver

Вторая команда должна закончиться сообщением Permission denied, ни разу не предложив ввести пароль. Если пароль всё же запрашивается, ваш параметр что-то переопределяет. В руководстве сказано, что для каждого ключевого слова используется первое полученное значение, а файлы Include обрабатываются в лексическом порядке, поэтому может победить более ранняя строка или файл, который принёс образ хостинга. Найдите его:

sudo grep -rsiE '^[[:space:]]*(passwordauthentication|kbdinteractiveauthentication|permitrootlogin)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/

Если доступ всё же потерян, дорогу назад описывает руководство по восстановлению доступа по SSH.

Слои 2 и 3: вход root и AllowUsers

Когда пароли выключены, root всё ещё может войти по ключу, если тот лежит в authorized_keys пользователя root. В руководстве для PermitRootLogin перечислены значения yes, prohibit-password, forced-commands-only и no, а значением по умолчанию названо prohibit-password. Оно закрывает пароли для root, но не ключи, поэтому задайте no и работайте через sudo из именованной учётной записи. Прежде убедитесь, что обычная учётная запись с рабочим sudo существует и вы можете в неё войти.

Затем добавьте список разрешённых. Когда задан AllowUsers, по руководству вход разрешён только для имён, совпадающих с одним из шаблонов, а по умолчанию вход разрешён всем. Перечислите все учётные записи, которым нужен доступ, включая резервное копирование и развёртывание, иначе их задания начнут падать:

PermitRootLogin no
AllowUsers deploy admin
sudo sshd -t
sudo sshd -T | grep -iE '^(permitrootlogin|allowusers) '
sudo systemctl reload ssh
ssh -l root myserver

Последняя команда, запущенная из второго терминала, должна быть отвергнута. Отказ из-за PermitRootLogin попадает в журнал как ROOT LOGIN REFUSED FROM, а учётная запись вне списка даёт строку о том, что пользователь не допущен, так как не указан в AllowUsers. Найти обе можно командой journalctl -u ssh -g 'not allowed|REFUSED'. В AllowUsers имя пользователя можно сочетать с шаблоном адреса источника; точную форму смотрите в sshd_config(5). Если заданы и AllowUsers, и AllowGroups, вход должен удовлетворять обоим спискам.

Эти два слоя хорошо заметны в журналах: боты продолжают подбирать root и admin, а эти имена теперь отвергаются ещё до этапа аутентификации.

Хотите убедиться, что защита SSH действительно работает?

Бесплатная проверка сервера только для чтения оценит настройки SSH, межсетевой экран и открытые сервисы и покажет, что всё ещё пропускает подбор.

Получить бесплатную проверку сервера Связаться с инженером по безопасности

Слой 4: лимиты sshd и встроенные штрафы

OpenSSH уже ограничивает неаутентифицированных клиентов. Значения по умолчанию из руководства sshd_config(5):

  • MaxAuthTries равен 6: число попыток аутентификации на одно соединение.
  • LoginGraceTime равен 120 секундам: сколько клиент может оставаться неаутентифицированным, прежде чем сервер его отключит.
  • MaxStartups равен 10:30:100: когда открыто 10 неаутентифицированных соединений, новые отклоняются с вероятностью 30%, которая линейно растёт, пока при 100 не отклоняются все.
  • PerSourcePenalties включён по умолчанию: начиная с OpenSSH 9.8 (выпущен 2024-07-01) адреса, которые проваливают аутентификацию, держат соединения без входа или роняют демон, временно отклоняются вместе с остальной их подсетью. В действующем руководстве указаны значения по умолчанию, например 5 секунд за неудачную аутентификацию с ростом до максимума в 10 минут.

Выведите значения, которые сервер реально использует. В старых пакетах нового параметра нет, поэтому проверьте и версию:

ssh -V
sudo sshd -T | grep -Ei 'maxauthtries|logingracetime|maxstartups|persourcepenalties'

Умеренное ужесточение разумно, если помнить, чем являются эти лимиты. Они работают на уровне соединения или источника, замедляют атакующего, а бот, который переподключился, получает новый счётчик. Цель они не убирают, поэтому и стоят после паролей:

MaxAuthTries 3
LoginGraceTime 30
MaxStartups 10:30:60

Две оговорки. Клиент, у которого в агенте много ключей, может израсходовать MaxAuthTries, не дойдя до нужного, и увидеть Too many authentication failures. Исправляется на стороне клиента параметром IdentitiesOnly yes, как в блоке Host выше. А слишком малый LoginGraceTime может оказаться коротким, если позже вы добавите второй фактор, который нужно вводить вручную. Подумайте и о PerSourcePenaltyExemptList с адресом вашего офиса или VPN, чтобы штрафы никогда не касались вас. Проверьте командой sudo sshd -t и перезагрузите конфигурацию, как раньше.

Слой 5: fail2ban или ограничение частоты в ufw

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

Вариант A: fail2ban

fail2ban читает строки журнала, считает неудачи по адресам и банит нарушителей на уровне межсетевого экрана. В поставляемом jail.conf по умолчанию заданы bantime = 10m, findtime = 10m и maxretry = 5, и там же сказано, что изменения нужно класть в jail.local. Перед включением впишите свой адрес в ignoreip:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.42

[sshd]
enabled  = true
maxretry = 4
findtime = 10m
bantime  = 1h
bantime.increment = true
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Вывод статуса показывает счётчики неудач и банов, это и есть доказательство, что всё работает. Его слабое место названо выше: он считает по адресам, поэтому атакующий, меняющий адреса, может оставаться ниже порога. Если jail запущен, но никого не банит, прочитайте почему fail2ban не банит. Полная настройка описана в руководстве по fail2ban.

Вариант B: ufw limit

Руководство ufw описывает правило limit: оно обычно разрешает соединение, но запрещает его, если адрес пытается установить 6 или более соединений за 30 секунд. Типичное использование: ufw limit ssh/tcp. Это ограничение частоты без дополнительного демона, и тот же механизм разобран в руководстве по межсетевому экрану Linux и в статье ufw. Есть одна ловушка: в руководстве сказано, что порядок правил важен и срабатывает первое совпадение, поэтому существующее обычное разрешающее правило для SSH делает правило limit мёртвым. Сначала добавьте limit, затем удалите allow, и доступ не пропадёт ни на миг:

sudo ufw limit ssh/tcp
sudo ufw status numbered
sudo ufw delete allow 22/tcp     # или: sudo ufw delete allow OpenSSH
sudo ufw status verbose

Если указать исходное правило после слова delete, удаляются и IPv4-, и IPv6-варианты. Если ufw ещё не включён, добавьте правило для SSH до sudo ufw enable: руководство предупреждает, что включение может разорвать существующие соединения, например SSH. Помните, что limit считает попытки соединения, а не неудачи: скрипт, открывающий много SSH-соединений за несколько секунд, может привести к срабатыванию лимита.

Честная ценность смены порта

Перенос sshd с порта 22 действительно сокращает поток проходных попыток, потому что большинство ботов пробуют только порт по умолчанию. Это можно измерить: посчитайте неудачи до и после. Чего он не делает, так это не останавливает того, кто смотрит внимательно. Сканирование портов находит новый порт за секунды, а целевого атакующего он не замедляет вовсе. Это фильтр шума, а не средство защиты, поэтому он так низко в списке. При этом у него есть реальная цена: каждый клиент, правило межсетевого экрана, проверка мониторинга и скрипт должны узнать новый номер.

Если всё же хотите, используйте тот факт, что руководство допускает несколько строк Port: можно держать оба порта на время проверки и ни разу не остаться без доступа:

# /etc/ssh/sshd_config
Port 22
Port 2222
  1. Сначала откройте новый порт в ufw (sudo ufw allow 2222/tcp) и в любом межсетевом экране или группе безопасности хостинга.
  2. Выполните sudo sshd -t, перезагрузите конфигурацию и убедитесь, что именно слушается: sudo ss -tlnp | grep sshd.
  3. Из второго терминала проверьте: ssh -p 2222 myserver.
  4. Только когда это работает, уберите Port 22 и его правило межсетевого экрана, проверьте и перезагрузите конфигурацию ещё раз.

Если нового порта нет в выводе ss, возможно, в вашей системе sshd запускается через сокет-юнит systemd, который владеет слушающим портом. Посмотрите в документации дистрибутива, как его менять, и не закрывайте рабочую сессию, пока новый порт не ответит. Обновите и порт для limit или fail2ban, например sudo ufw limit 2222/tcp.

Повышенный риск: двухфакторная аутентификация или бастион

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

Двухфакторная аутентификация. Директива AuthenticationMethods позволяет требовать несколько методов подряд, например ключ и второй фактор. Два практичных варианта: аппаратный ключ FIDO2 и код TOTP через PAM. Для победы над брутфорсом ни один не нужен, но оба делают украденный ключ недостаточным. Неверная попытка ввода второго фактора тоже тратит MaxAuthTries, поэтому не задавайте его слишком низким. У внедрения свои риски блокировки, они разобраны по шагам в руководстве по двухфакторной аутентификации SSH.

Бастион. Бастионный хост это единственная намеренно открытая машина. Внутренние серверы принимают SSH только от неё, поэтому у них нет публичного порта SSH, который можно атаковать. OpenSSH поддерживает это напрямую: в руководстве ssh опция -J и параметр ProxyJump описаны как подключение сначала к промежуточному хосту и перенаправление к цели уже оттуда.

Host bastion
    HostName jump.example.com
    User ops

Host app
    HostName 10.0.0.12
    User deploy
    ProxyJump bastion

После этого ssh app идёт через бастион. Плата за это честная: бастион концентрирует и вашу защиту, и ваш риск, он является единой точкой отказа, поэтому защищайте его строже всего остального и держите консоль хостинга как запасной путь. Для одного VPS это обычно больше механики, чем требует задача.

Проверка, мониторинг и защита от блокировки

Вот весь порядок внедрения целиком. Каждый шаг небольшой, обратимый и проверяется из второго терминала, прежде чем вы идёте дальше:

  1. Измерьте исходный уровень и держите открытыми сессию и консоль хостинга.
  2. Докажите, что вход по ключу работает, затем задайте PasswordAuthentication no и KbdInteractiveAuthentication no.
  3. Задайте PermitRootLogin no и AllowUsers со всеми нужными учётными записями.
  4. Умеренно ужесточите MaxAuthTries, LoginGraceTime и MaxStartups.
  5. Добавьте fail2ban или ufw limit, исключив свой адрес из ограничений.
  6. По желанию смените порт через две строки Port, а затем добавьте 2FA или бастион, если риск это оправдывает.

После каждого изменения выполняйте sudo sshd -t, читайте результат через sudo sshd -T и перезагружайте конфигурацию. Если перезагрузка что-то сломала, восстановите резервную копию из сохранённой открытой сессии и перезагрузите снова.

Затем проверьте результат, а не только конфигурацию. Через сутки повторите команды измерения. Неудачные попытки, скорее всего, продолжат приходить, и это нормально: боты не читают вашу конфигурацию, они просто пробуют. Изменилось другое: ни одна не может удаться, строк в журнале стало меньше, если вы добавили ограничение частоты или сменили порт, а единственные успехи ваши. Не гонитесь за нулём.

Для постоянного мониторинга проверяйте это по расписанию или подключите к конвейеру из руководства по мониторингу и анализу журналов:

sudo journalctl -u ssh --since yesterday --no-pager -o cat -g 'Accepted'
sudo fail2ban-client status sshd
sudo ufw status verbose
last -i -n 20

Настройте оповещения на две вещи: вход с незнакомого адреса и успешный вход после серии неудач. Всё остальное фоновый шум. Для следующих слоёв защиты изучите защиту SSH: лучшие практики и чек-лист безопасности VPS.

Часто задаваемые вопросы

Как проверить, что мой SSH-сервер подбирают брутфорсом?

Посчитайте неудачные входы в журнале, например sudo journalctl -u ssh --since yesterday --no-pager -o cat -g 'Failed password' | wc -l, и выведите главные адреса источников. В семействе RHEL юнит называется sshd. Строки Failed password for invalid user и Invalid user указывают на автоматический подбор.

Как лучше всего остановить брутфорс SSH?

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

Достаточно ли сменить порт SSH, чтобы остановить брутфорс?

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

Важны ли неудачные попытки входа по SSH, если разрешены только ключи?

В основном нет. Боты пробуют независимо от вашей конфигурации, но при отключённых паролях ни одна попытка не может удаться. Следите за другим: за необъяснимым успешным входом, особенно сразу после серии неудач с того же адреса.

Что выбрать для SSH: fail2ban или ufw limit?

Подойдёт любой как второй слой. fail2ban читает журналы и банит адреса на выбранное время, а ufw limit запрещает адрес, сделавший 6 и более попыток соединения за 30 секунд, без дополнительного демона. Начните с одного и измерьте результат.

Какие значения по умолчанию у MaxAuthTries и LoginGraceTime?

По руководству sshd_config, MaxAuthTries равен 6 попыткам на соединение, а LoginGraceTime равен 120 секундам. MaxStartups по умолчанию 10:30:100. Выполните sudo sshd -T, чтобы увидеть значения, которые реально использует ваш сервер.

Как не заблокировать себя при защите SSH?

Держите текущую сессию открытой, проверяйте конфигурацию командой sudo sshd -t и тестируйте каждое изменение из второго терминала, прежде чем что-либо закрывать. Заранее узнайте, как попасть в консоль хостинга, и храните резервную копию sshd_config.

Может ли ufw limit заблокировать мои собственные SSH-соединения?

Да, если вы или скрипт открываете 6 и более соединений к порту за 30 секунд, ufw запретит дальнейшие попытки с этого адреса. Не открывайте соединения в быстрых циклах или используйте fail2ban со своим адресом в ignoreip.

Не уверены, в каком состоянии ваш сервер?

Получите бесплатную проверку безопасности в режиме только чтения или поговорите с инженером по безопасности об управляемой защите.

Получить бесплатную проверку сервера Связаться с инженером по безопасности