Введение: почему безопасность SSH важна как никогда
SSH (Secure Shell) остаётся главным протоколом удалённого администрирования Linux-серверов. Через него ежедневно проходят миллионы подключений — от ручного администрирования до автоматизированных CI/CD-пайплайнов. И именно поэтому SSH — приоритетная цель для атакующих. Компрометация SSH-доступа означает полный контроль над сервером. Без вариантов.
Статистика, надо сказать, впечатляет. По данным мониторинговых систем, средний сервер с открытым портом 22 получает от 10 000 до 100 000 попыток подбора паролей в сутки. Ботнеты вроде Mirai и его потомков непрерывно сканируют интернет в поисках слабозащищённых SSH-сервисов.
Начало 2025 года ознаменовалось обнаружением двух критических уязвимостей в OpenSSH. CVE-2025-26465 — уязвимость типа Man-in-the-Middle, позволяющая атакующему выдать себя за легитимный сервер при включённой опции VerifyHostKeyDNS. Злоумышленник перехватывает соединение и получает доступ к учётным данным. CVE-2025-26466 — DoS-уязвимость, которая позволяет исчерпать память и процессор на стороне сервера ещё до завершения аутентификации, фактически делая SSH-сервис недоступным.
А в 2024-м была обнаружена regreSSHion (CVE-2024-6387) — состояние гонки в обработчике сигналов sshd, позволяющее удалённое выполнение кода с правами root без аутентификации. Серьёзно — без аутентификации. Эта уязвимость затронула серверы на базе glibc и стала хорошим напоминанием: даже самый проверенный код может содержать критические ошибки.
Все эти случаи подчёркивают простую истину: даже зрелый протокол требует постоянного внимания к конфигурации и своевременного обновления. Так что давайте пройдём путь от базового аудита до развёртывания сертификатов, бастионных хостов и автоматизированных проверок безопасности.
Аудит текущей конфигурации SSH
Прежде чем что-то менять, нужно понять, с чем мы вообще работаем. Начнём с инструментов диагностики.
Утилита ssh-audit
ssh-audit — один из лучших инструментов для анализа безопасности SSH-сервера. Проверяет поддерживаемые алгоритмы, находит устаревшие и небезопасные параметры, выявляет известные уязвимости. Если вы ещё не пробовали его — самое время.
# Установка ssh-audit
pip3 install ssh-audit
# Сканирование локального сервера
ssh-audit localhost
# Сканирование удалённого сервера
ssh-audit example.com
# Аудит конфигурации клиента
ssh-audit -c
Вывод ssh-audit использует цветовую маркировку: зелёным — безопасные алгоритмы, жёлтым — допустимые, красным — небезопасные, которые нужно срочно отключить. Особое внимание стоит обратить на разделы с алгоритмами обмена ключами (KEX), шифрами и MAC-кодами.
Ещё полезно запустить ssh-audit в режиме генерации политики — это создаёт базовый профиль безопасности, который потом можно использовать для автоматических проверок:
# Генерация политики на основе текущей конфигурации
ssh-audit --make-policy example.com > ssh_policy.txt
# Проверка сервера на соответствие политике
ssh-audit --policy=ssh_policy.txt example.com
Проверка активной конфигурации sshd
Файл /etc/ssh/sshd_config может содержать директивы Include, подключающие дополнительные файлы. Чтобы увидеть итоговую конфигурацию со всеми включениями:
# Просмотр активной расширенной конфигурации
sshd -T
# Проверка конфигурации для конкретного пользователя и хоста
sshd -T -C user=deploy,host=10.0.0.1,addr=10.0.0.1
# Проверка синтаксиса конфигурации
sshd -t
sshd -T особенно полезна — она показывает абсолютно все параметры, включая значения по умолчанию, которые явно не прописаны в конфиге. Помогает выявить неожиданные настройки, о которых вы, возможно, и не подозревали.
Проверка используемых ключей хоста
# Список ключей хоста и их типов
for key in /etc/ssh/ssh_host_*_key.pub; do
ssh-keygen -l -f "$key"
done
# Проверка наличия устаревших DSA-ключей
ls -la /etc/ssh/ssh_host_dsa_key* 2>/dev/null && \
echo "ВНИМАНИЕ: Обнаружены DSA-ключи — их необходимо удалить"
Базовое усиление sshd_config
Итак, давайте разберёмся с основными директивами, которые должны быть настроены на каждом сервере. Ниже — эталонный фрагмент /etc/ssh/sshd_config с пояснениями.
Запрет входа root и парольной аутентификации
# Запретить прямой вход под root
PermitRootLogin no
# Отключить парольную аутентификацию
PasswordAuthentication no
# Отключить пустые пароли (на всякий случай)
PermitEmptyPasswords no
# Отключить challenge-response аутентификацию
# (предотвращает обход PasswordAuthentication через PAM)
KbdInteractiveAuthentication no
Запрет входа под root — фундаментальная мера. Атакующий, перебирающий пароли, всегда пробует root первым. Даже если вы используете ключи, оставлять PermitRootLogin yes — это неоправданный риск. Для задач с привилегиями root используйте sudo.
Отключение парольной аутентификации убирает целый класс атак — brute force. Только обязательно убедитесь, что SSH-ключи уже настроены, прежде чем применять это изменение. Иначе рискуете заблокировать себя на собственном сервере. И да, я лично видел, как это происходило не раз — причём с опытными администраторами.
Использование ключей Ed25519
Ed25519 — современный стандарт для SSH-ключей. Обеспечивает высокую безопасность при коротких ключах и работает заметно быстрее RSA.
# Генерация ключа Ed25519
ssh-keygen -t ed25519 -C "user@hostname" -a 64
# Если требуется совместимость со старыми системами, используйте RSA 4096
ssh-keygen -t rsa -b 4096 -C "user@hostname"
Параметр -a 64 увеличивает количество раундов KDF (Key Derivation Function) для защиты приватного ключа. Это затрудняет подбор парольной фразы, если файл ключа попадёт не в те руки.
Смена порта по умолчанию
# Смена порта (security through obscurity — не замена реальной защите)
Port 2222
Смена порта с 22 на нестандартный снижает автоматизированный шум от ботов, но не является настоящей мерой безопасности. Направленный сканер найдёт SSH на любом порту за секунды. Тем не менее на практике это полезно: логи становятся чище, а нагрузка от ботов падает на 90-95%. По моему опыту, это одна из тех мер, которые не спасают от целенаправленной атаки, но заметно упрощают повседневную жизнь.
Тайминги и ограничения сессий
# Время на аутентификацию (по умолчанию 120 секунд — слишком много)
LoginGraceTime 30
# Максимальное число попыток аутентификации за соединение
MaxAuthTries 3
# Максимальное число сессий на соединение
MaxSessions 3
# Проверка активности клиента
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 30 даёт клиенту 30 секунд на аутентификацию. Не успел — соединение разрывается. Это ограничивает возможности для атак типа CVE-2025-26466, когда злоумышленник удерживает соединения в преаутентификационном состоянии.
ClientAliveInterval и ClientAliveCountMax вместе определяют таймаут неактивной сессии. При указанных значениях неактивная сессия разрывается через 10 минут (300 секунд × 2 пакета).
Ограничение пользователей и групп
# Разрешить доступ только указанным пользователям
AllowUsers deploy monitoring
# Или использовать группы (более масштабируемый подход)
AllowGroups ssh-users ssh-admins
AllowUsers и AllowGroups работают как белый список. Все, кто не указан — не подключатся по SSH, даже с валидными учётными данными. Это критически важно на серверах, где может быть куча системных учёток.
Отключение ненужных функций
# Отключить X11 forwarding (если не используется графика)
X11Forwarding no
# Отключить проброс SSH-агента (снижает риск при компрометации)
AllowAgentForwarding no
# Отключить TCP-перенаправление (если не нужны туннели)
AllowTcpForwarding no
# Отключить перенаправление потоков
AllowStreamLocalForwarding no
# Отключить туннелирование
PermitTunnel no
# Баннер перед аутентификацией
Banner /etc/ssh/banner.txt
Каждая включённая функция — это дополнительная поверхность атаки. Отключайте всё, что не нужно. Если определённым пользователям требуется TCP-forwarding, используйте Match-блоки для выборочного разрешения (об этом поговорим чуть ниже).
Баннер (Banner) отображается перед аутентификацией. Он служит юридическим предупреждением о несанкционированном доступе — и это важно во многих юрисдикциях.
Сводная эталонная конфигурация
Объединим все базовые настройки в один блок:
# /etc/ssh/sshd_config — базовое усиление
# Сетевые настройки
Port 2222
AddressFamily inet
ListenAddress 0.0.0.0
# Аутентификация
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
# Ограничения
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 3
MaxStartups 10:30:60
# Контроль сессий
ClientAliveInterval 300
ClientAliveCountMax 2
# Доступ
AllowGroups ssh-users ssh-admins
# Отключение ненужных функций
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
AllowStreamLocalForwarding no
PermitTunnel no
PermitUserEnvironment no
PermitUserRC no
# Логирование
SyslogFacility AUTH
LogLevel VERBOSE
# Баннер
Banner /etc/ssh/banner.txt
Обратите внимание на MaxStartups 10:30:60. Эта директива ограничивает одновременные неаутентифицированные подключения: при 10 активных соединениях новые начинают отклоняться с вероятностью 30%, при 60 — отклоняются все. Это весьма эффективная мера против атак истощения ресурсов вроде CVE-2025-26466.
LogLevel VERBOSE обеспечивает детальное логирование, включая отпечатки ключей — то, что нужно для полноценного аудита.
Криптографические алгоритмы и протокол
Конфигурация криптографических алгоритмов — один из важнейших аспектов защиты SSH. Начиная с OpenSSH 9.0, поддержка DSA-ключей отключена по умолчанию, а в 9.8+ она полностью удалена. И правильно — DSA давно устарел.
Рекомендуемые алгоритмы обмена ключами (KexAlgorithms)
KexAlgorithms [email protected],curve25519-sha256,[email protected],diffie-hellman-group18-sha512,diffie-hellman-group16-sha512
Обратите внимание на [email protected] — это гибридный алгоритм, объединяющий классический X25519 с постквантовым NTRU Prime. Он обеспечивает защиту от потенциальных атак квантовых компьютеров. OpenSSH поддерживает его начиная с версии 8.5, и использовать его стоит уже сейчас.
Почему это важно? Квантовые компьютеры пока не представляют прямой угрозы, но данные, перехваченные сегодня, вполне могут быть расшифрованы завтра. Концепция «harvest now, decrypt later» — не паранойя, а вполне реалистичный сценарий.
Рекомендуемые шифры (Ciphers)
Ciphers [email protected],[email protected],[email protected]
chacha20-poly1305 — предпочтительный шифр для большинства систем. Он особенно хорош на платформах без аппаратного ускорения AES (ARM-устройства, встраиваемые системы). На серверах с поддержкой AES-NI шифры aes256-gcm и aes128-gcm обеспечивают сравнимую безопасность при высокой производительности.
Рекомендуемые MAC-алгоритмы
MACs [email protected],[email protected]
Используйте только варианты etm (Encrypt-then-MAC). Этот режим сначала шифрует данные, затем вычисляет MAC по зашифрованному тексту — криптографически это надёжнее, чем MAC-then-encrypt. Небольшой нюанс: шифры GCM и ChaCha20-Poly1305 являются AEAD-шифрами со встроенной проверкой целостности, так что настройка MACs для них не задействуется. Но указанная конфигурация применяется как запасной вариант для не-AEAD соединений.
Алгоритмы ключей хоста (HostKeyAlgorithms)
HostKeyAlgorithms [email protected],[email protected],ssh-ed25519,[email protected],[email protected],rsa-sha2-512,rsa-sha2-256
Приоритет — Ed25519 и сертификаты. Алгоритмы с префиксом sk- поддерживают аппаратные ключи безопасности (FIDO2/U2F). RSA оставлен для совместимости, но только в варианте с SHA-2. Устаревший ssh-rsa с SHA-1 — это категорическое нет.
Полный блок криптографических настроек
# /etc/ssh/sshd_config — криптографические алгоритмы
# Обновлено для OpenSSH 9.x+
KexAlgorithms [email protected],curve25519-sha256,[email protected],diffie-hellman-group18-sha512,diffie-hellman-group16-sha512
Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected]
HostKeyAlgorithms [email protected],ssh-ed25519,[email protected],rsa-sha2-512,rsa-sha2-256
# Использовать только Ed25519 и RSA ключи хоста
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
# Удалить устаревшие ключи DSA и ECDSA
# rm /etc/ssh/ssh_host_dsa_key* /etc/ssh/ssh_host_ecdsa_key*
Аутентификация по сертификатам SSH
Аутентификация по SSH-сертификатам — это существенный шаг вперёд по сравнению с обычными ключами. Вместо того чтобы раскладывать публичные ключи по authorized_keys на каждом сервере (что превращается в настоящий кошмар при масштабировании), вы создаёте удостоверяющий центр (CA) и подписываете ключи пользователей и хостов. Серверу достаточно доверять CA — и он автоматически принимает любой подписанный им ключ.
Создание удостоверяющего центра (CA)
# Создание CA для подписи пользовательских ключей
ssh-keygen -t ed25519 -f /etc/ssh/ca/user_ca -C "User CA Key"
# Создание CA для подписи ключей хостов
ssh-keygen -t ed25519 -f /etc/ssh/ca/host_ca -C "Host CA Key"
# Защитите приватные ключи CA!
chmod 400 /etc/ssh/ca/user_ca /etc/ssh/ca/host_ca
Важно: приватные ключи CA — самый чувствительный элемент всей инфраструктуры. Они должны храниться на изолированной машине, а в идеале — на аппаратном токене (YubiKey, Nitrokey). Компрометация ключа CA означает возможность выпуска сертификатов для любого пользователя и хоста. Честно говоря, именно этот момент чаще всего недооценивают — пока не становится слишком поздно.
Подписание ключей хоста
# Подписание ключа хоста сервера
ssh-keygen -s /etc/ssh/ca/host_ca \
-I "server01.example.com" \
-h \
-n "server01.example.com,10.0.1.10" \
-V +52w \
/etc/ssh/ssh_host_ed25519_key.pub
# Результат: файл /etc/ssh/ssh_host_ed25519_key-cert.pub
Параметры команды:
-s— путь к ключу CA для подписи-I— идентификатор сертификата (попадает в логи аудита — выбирайте что-то осмысленное)-h— флаг, указывающий, что подписывается ключ хоста, а не пользователя-n— список принципалов (имена и IP хоста)-V +52w— срок действия сертификата (52 недели)
Подписание ключей пользователей
# Подписание ключа пользователя с ограничением срока действия
ssh-keygen -s /etc/ssh/ca/user_ca \
-I "[email protected]" \
-n "deploy,ivan" \
-V +8h \
-O source-address=10.0.0.0/8 \
~/.ssh/id_ed25519.pub
# Результат: файл ~/.ssh/id_ed25519-cert.pub
Обратите внимание на параметры безопасности — тут всё продумано:
-V +8h— сертификат действителен только 8 часов (идеально для рабочего дня)-O source-address=10.0.0.0/8— сертификат работает только при подключении из указанной подсети-n "deploy,ivan"— список имён пользователей, под которыми разрешён вход
Настройка sshd для доверия CA
# /etc/ssh/sshd_config
# Доверять пользовательским сертификатам, подписанным CA
TrustedUserCAKeys /etc/ssh/ca/user_ca.pub
# Использовать подписанный сертификат хоста
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
# Файл маппинга принципалов (опционально)
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
Файл AuthorizedPrincipalsFile позволяет контролировать, какие принципалы допустимы для каждого пользователя. Скажем, для пользователя deploy:
# /etc/ssh/auth_principals/deploy
deploy
ci-service
release-manager
На стороне клиента нужно добавить доверие к CA хоста в ~/.ssh/known_hosts или глобально:
# Добавить доверие к CA хоста (на клиентской машине)
echo "@cert-authority *.example.com $(cat host_ca.pub)" >> ~/.ssh/known_hosts
Преимущества сертификатов перед обычными ключами
- Централизованное управление — не нужно раскладывать
authorized_keysпо серверам - Автоматическое истечение — сертификаты имеют срок действия, обычные ключи — нет
- Отзыв — можно отозвать скомпрометированный сертификат через KRL (Key Revocation List)
- Аудит — идентификатор сертификата записывается в логи
- Масштабируемость — новый сервер автоматически доверяет всем пользователям с валидными сертификатами
Двухфакторная аутентификация
Для сред с повышенными требованиями безопасности одного SSH-ключа может быть недостаточно. Если у вас продакшн с чувствительными данными — его определённо недостаточно. Двухфакторная аутентификация (2FA) добавляет ещё один уровень защиты, требуя подтверждения личности вторым фактором.
Настройка AuthenticationMethods
# /etc/ssh/sshd_config
# Требовать и ключ, и второй фактор
AuthenticationMethods publickey,keyboard-interactive
# Включить PAM для обработки второго фактора
UsePAM yes
KbdInteractiveAuthentication yes
Директива AuthenticationMethods определяет цепочку методов аутентификации. Запятая между методами означает «и» — оба метода обязательны. Точка с запятой означала бы «или» — достаточно одного. Путать их точно не стоит.
Настройка TOTP через google-authenticator
# Установка PAM-модуля
# Debian/Ubuntu
apt install libpam-google-authenticator
# RHEL/CentOS/Fedora
dnf install google-authenticator
Каждый пользователь должен инициализировать TOTP:
# Запуск от имени пользователя
google-authenticator -t -d -f -r 3 -R 30 -w 3
Настройка PAM (/etc/pam.d/sshd):
# Добавить в начало файла /etc/pam.d/sshd
auth required pam_google_authenticator.so nullok
# nullok позволяет пользователям без настроенного 2FA входить
# Уберите nullok после того, как все пользователи настроят 2FA
После настройки при входе по SSH пользователь сначала предъявляет свой ключ, а затем вводит шестизначный одноразовый код из приложения-аутентификатора (Google Authenticator, Authy, FreeOTP). Немного неудобно? Безусловно. Но это та самая неудобность, которая реально стоит свеч.
Аппаратные ключи безопасности (FIDO2)
Начиная с OpenSSH 8.2, поддерживаются аппаратные ключи FIDO2/U2F — YubiKey, SoloKey и подобные. Это, пожалуй, наиболее надёжный второй фактор: требует физического присутствия устройства и не подвержен фишингу.
# Генерация ключа, привязанного к аппаратному токену
ssh-keygen -t ed25519-sk -C "user@hostname-yubikey"
# Вариант с требованием подтверждения касанием
ssh-keygen -t ed25519-sk -O resident -O verify-required -C "user@hostname-yubikey"
Опция -O resident сохраняет ключ непосредственно на токене — можно использовать с любой машины без копирования файлов. Опция -O verify-required требует PIN-код токена при каждом использовании.
Match-блоки: гибкая конфигурация доступа
Директива Match в sshd_config позволяет применять разные настройки в зависимости от пользователя, группы, IP-адреса или имени хоста. Без этого инструмента сложно обойтись в любой более-менее серьёзной инфраструктуре.
SFTP-пользователи с изолированным окружением
# Группа для SFTP-only пользователей
Match Group sftp-users
ChrootDirectory /data/sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
AllowAgentForwarding no
PermitTTY no
X11Forwarding no
Эта конфигурация полностью изолирует пользователей группы sftp-users:
ChrootDirectory— ограничивает видимость файловой системы каталогом конкретного пользователяForceCommand internal-sftp— разрешает только SFTP, запрещая shell-доступPermitTTY no— запрещает выделение терминала
Не забудьте про права на chroot-каталог — тут часто ошибаются:
# Chroot-каталог должен принадлежать root
chown root:root /data/sftp/username
chmod 755 /data/sftp/username
# Каталог для записи пользователя внутри chroot
mkdir /data/sftp/username/uploads
chown username:sftp-users /data/sftp/username/uploads
Различные методы аутентификации для разных групп
# Администраторы — требуется ключ + 2FA
Match Group ssh-admins
AuthenticationMethods publickey,keyboard-interactive
AllowTcpForwarding yes
PermitTTY yes
# Сервисные учётные записи — только ключ, из определённой подсети
Match Group service-accounts
AuthenticationMethods publickey
AllowTcpForwarding no
PermitTTY no
AllowAgentForwarding no
# Доступ из внутренней сети — менее строгие требования
Match Address 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
MaxAuthTries 5
LoginGraceTime 60
# Доступ из внешней сети — максимальная строгость
Match Address *,!10.0.0.0/8,!172.16.0.0/12,!192.168.0.0/16
MaxAuthTries 2
LoginGraceTime 20
AuthenticationMethods publickey,keyboard-interactive
Ограничение по конкретному пользователю
# Пользователь для резервного копирования
Match User backup
ForceCommand /usr/local/bin/backup-script.sh
AllowTcpForwarding no
PermitTTY no
X11Forwarding no
ForceCommand гарантирует, что пользователь backup может выполнить только заданный скрипт — неважно, что он пытается запустить на самом деле. Принцип минимальных привилегий в чистом виде.
Важно помнить: Match-блоки всегда размещаются в конце файла sshd_config. Все директивы после строки Match относятся к этому блоку, пока не встретится следующий Match или конец файла. И не все директивы допускаются внутри Match-блоков — проверяйте конфигурацию командой sshd -t перед применением.
Я встречал случаи, когда Match-блок с недопустимой директивой просто тихо игнорировался, и админ неделями был уверен, что всё работает как надо. Будьте внимательнее.
Бастионные хосты и ProxyJump
В правильно спроектированной инфраструктуре серверы не торчат голым SSH в интернет. Вместо этого используется бастионный хост (он же jump host) — единственная точка входа в приватную сеть.
Архитектура
Типичная схема выглядит так:
Интернет → [Бастион (публичный IP)] → [Приватная сеть]
├── web-01 (10.0.1.10)
├── db-01 (10.0.2.10)
└── app-01 (10.0.3.10)
Бастион — единственный сервер с открытым SSH-портом в интернете. Все остальные принимают SSH-подключения только из приватной сети.
Настройка клиента SSH (ProxyJump)
# ~/.ssh/config
Host bastion
HostName bastion.example.com
User jump-user
Port 2222
IdentityFile ~/.ssh/id_ed25519
Host web-01
HostName 10.0.1.10
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
Host db-01
HostName 10.0.2.10
User dbadmin
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
# Шаблон для всех серверов в приватной сети
Host 10.0.*
User admin
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
Теперь подключение к серверу в приватной сети — буквально одна команда:
# SSH прозрачно проходит через бастион
ssh web-01
# Или напрямую через командную строку
ssh -J bastion.example.com [email protected]
Усиление безопасности бастиона
# /etc/ssh/sshd_config на бастионном хосте
# Разрешить TCP-перенаправление (необходимо для ProxyJump)
AllowTcpForwarding yes
# Запретить интерактивные сессии на самом бастионе
Match User jump-user
PermitTTY no
X11Forwarding no
AllowAgentForwarding no
ForceCommand /usr/sbin/nologin
PermitOpen 10.0.0.0/8:22
PermitOpen ограничивает, к каким адресам и портам можно пробрасывать соединения — только порт 22 в приватной подсети. Даже при компрометации учётки на бастионе атакующий не сможет пробиться к другим сервисам.
Дополнительно на бастионе стоит настроить минимальное окружение:
# Дополнительные меры на бастионном хосте
# Минимальный набор пакетов — удалить всё лишнее
# Отключить историю команд (на случай получения shell-доступа)
# /etc/profile.d/no-history.sh
unset HISTFILE
HISTSIZE=0
# Настроить файрвол: разрешить только SSH-входящий и SSH-исходящий в приватную сеть
nft add rule inet filter input tcp dport 2222 accept
nft add rule inet filter output ip daddr 10.0.0.0/8 tcp dport 22 accept
nft add rule inet filter output ct state established accept
nft add rule inet filter output drop
ProxyJump vs Agent Forwarding: вопрос безопасности
Раньше для доступа через промежуточный сервер часто использовали проброс SSH-агента (ssh -A). Так вот — это серьёзная уязвимость. Если бастион скомпрометирован, злоумышленник может использовать ваш проброшенный агент для подключения к любому серверу, к которому у вас есть доступ.
ProxyJump лишён этого недостатка. Соединение туннелируется через бастион, но аутентификация на целевом сервере происходит напрямую от клиента. Приватный ключ никогда не покидает вашу машину — и это принципиально.
# НЕБЕЗОПАСНО — агент проброшен на потенциально скомпрометированный хост
ssh -A bastion
ssh web-01
# БЕЗОПАСНО — ключ остаётся на клиенте
ssh -J bastion web-01
Fail2Ban и мониторинг
Даже при идеальной конфигурации SSH нужна система активной защиты. Fail2Ban анализирует логи и автоматически блокирует IP-адреса, с которых идут подозрительные действия. Инструмент существует давно, но свою задачу по-прежнему выполняет отлично.
Настройка Fail2Ban для SSH
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
filter = sshd
backend = systemd
maxretry = 3
findtime = 600
bantime = 3600
banaction = nftables-multiport
# Агрессивная блокировка для повторных нарушителей
[sshd-aggressive]
enabled = true
port = 2222
filter = sshd[mode=aggressive]
backend = systemd
maxretry = 1
findtime = 86400
bantime = 604800
banaction = nftables-multiport
Основной jail [sshd] блокирует IP на 1 час после 3 неудачных попыток в течение 10 минут. Дополнительный [sshd-aggressive] банит на целую неделю после первой же подозрительной активности (сканирование, попытки использования запрещённых алгоритмов).
Жёстко? Возможно. Но работает.
Мониторинг логов с journalctl
# Просмотр логов SSH в реальном времени
journalctl -u sshd -f
# Неудачные попытки входа за последние 24 часа
journalctl -u sshd --since "24 hours ago" | grep -i "failed\|invalid\|refused"
# Успешные входы
journalctl -u sshd --since "24 hours ago" | grep "Accepted"
# Статистика по IP-адресам атакующих
journalctl -u sshd --since "24 hours ago" \
| grep "Failed password" \
| awk '{print $(NF-3)}' \
| sort | uniq -c | sort -rn | head -20
Настройка оповещений
Для оперативного реагирования настройте уведомления о критических событиях:
# /etc/fail2ban/action.d/notify.conf
# Добавить действие отправки уведомления при блокировке
[Definition]
actionban = notify-send-email <ip> <name>
# Или использовать systemd-монитор с webhook
# /etc/systemd/system/ssh-alert.service
[Unit]
Description=SSH Login Alert
[Service]
Type=oneshot
ExecStart=/usr/local/bin/ssh-alert.sh
#!/bin/bash
# /usr/local/bin/ssh-alert.sh
# Скрипт оповещения о входе по SSH
LOGINS=$(journalctl -u sshd --since "5 minutes ago" | grep "Accepted")
if [ -n "$LOGINS" ]; then
echo "$LOGINS" | mail -s "SSH Login Alert: $(hostname)" [email protected]
fi
Признаки вторжения в логах
Знание характерных признаков атаки помогает быстро обнаружить компрометацию. Вот на что стоит обращать внимание:
- Массовые неудачные попытки с одного IP — классический brute force
- Неудачные попытки под разными именами — перебор учётных записей
- Успешный вход в нерабочее время — потенциальная компрометация (если ваши админы, конечно, не страдают бессонницей)
- Вход с неизвестного IP после множества неудачных попыток — успешный brute force
- Сообщения о несовпадении версий протокола — попытка эксплуатации уязвимостей
- Попытки использования отключённых методов — целенаправленная разведка
- Необычный объём данных в рамках SSH-сессии — возможная эксфильтрация
- Множественные SSH-сессии от одного пользователя — потенциально захваченные учётные данные
Для централизованного сбора и анализа SSH-логов рекомендую пересылать их в SIEM-систему (Wazuh, Elastic Security, Splunk) или хотя бы использовать logwatch для ежедневных отчётов:
# Установка и настройка logwatch для ежедневных отчётов SSH
apt install logwatch
# Запуск отчёта за последние сутки
logwatch --service sshd --range yesterday --detail high --output mail --mailto [email protected]
Автоматизация и CI/CD
Ручная настройка SSH на десятках серверов — занятие не из приятных, а на сотнях — это уже просто нереалистично. Автоматизация обеспечивает единообразие конфигурации и возможность быстро обновить всё, когда вдруг появляется очередной CVE.
Ansible-плейбук для настройки SSH
# ssh-hardening.yml
---
- name: SSH Hardening
hosts: all
become: yes
vars:
ssh_port: 2222
ssh_allowed_groups:
- ssh-users
- ssh-admins
tasks:
- name: Установить актуальную версию OpenSSH
ansible.builtin.package:
name: openssh-server
state: latest
- name: Развернуть усиленную конфигурацию sshd
ansible.builtin.template:
src: templates/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
validate: 'sshd -t -f %s'
notify: Restart sshd
- name: Удалить устаревшие ключи хоста
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /etc/ssh/ssh_host_dsa_key
- /etc/ssh/ssh_host_dsa_key.pub
- /etc/ssh/ssh_host_ecdsa_key
- /etc/ssh/ssh_host_ecdsa_key.pub
notify: Restart sshd
- name: Сгенерировать Ed25519 ключ хоста (если отсутствует)
ansible.builtin.command:
cmd: ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
creates: /etc/ssh/ssh_host_ed25519_key
- name: Развернуть баннер
ansible.builtin.copy:
content: |
******************************************************************
* Доступ к этой системе разрешён только авторизованным *
* пользователям. Все действия регистрируются и отслеживаются. *
******************************************************************
dest: /etc/ssh/banner.txt
mode: '0644'
- name: Настроить Fail2Ban для SSH
ansible.builtin.template:
src: templates/fail2ban-sshd.j2
dest: /etc/fail2ban/jail.d/sshd.local
notify: Restart fail2ban
handlers:
- name: Restart sshd
ansible.builtin.systemd:
name: sshd
state: restarted
- name: Restart fail2ban
ansible.builtin.systemd:
name: fail2ban
state: restarted
Интеграция ssh-audit в CI/CD
# .gitlab-ci.yml (фрагмент)
ssh-audit:
stage: security
image: python:3.12-slim
script:
- pip install ssh-audit
- |
SERVERS="web-01.example.com db-01.example.com app-01.example.com"
FAILED=0
for server in $SERVERS; do
echo "=== Аудит $server ==="
if ! ssh-audit --level warn "$server"; then
FAILED=1
echo "ОШИБКА: $server не прошёл аудит безопасности"
fi
done
exit $FAILED
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
# GitHub Actions вариант
# .github/workflows/ssh-audit.yml
name: SSH Security Audit
on:
schedule:
- cron: '0 6 * * 1' # Каждый понедельник в 6:00
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Install ssh-audit
run: pip install ssh-audit
- name: Run audit
run: |
ssh-audit --level warn ${{ secrets.SERVER_HOST }}
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{"text": "SSH аудит не пройден для ${{ secrets.SERVER_HOST }}"}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
Автоматизированная проверка соответствия
#!/bin/bash
# ssh-compliance-check.sh
# Скрипт проверки соответствия конфигурации SSH политикам безопасности
SSHD_CONFIG="/etc/ssh/sshd_config"
ERRORS=0
check_setting() {
local setting="$1"
local expected="$2"
local actual
actual=$(sshd -T 2>/dev/null | grep -i "^${setting} " | awk '{print $2}')
if [ "$actual" != "$expected" ]; then
echo "[FAIL] $setting = $actual (ожидается: $expected)"
ERRORS=$((ERRORS + 1))
else
echo "[ OK ] $setting = $actual"
fi
}
echo "=== Проверка соответствия SSH-конфигурации ==="
echo "Дата: $(date)"
echo "Хост: $(hostname)"
echo ""
check_setting "permitrootlogin" "no"
check_setting "passwordauthentication" "no"
check_setting "permitemptypasswords" "no"
check_setting "x11forwarding" "no"
check_setting "maxauthtries" "3"
check_setting "protocol" "2"
check_setting "allowagentforwarding" "no"
check_setting "allowtcpforwarding" "no"
# Проверка версии OpenSSH
SSH_VERSION=$(ssh -V 2>&1 | grep -oP 'OpenSSH_\K[0-9.]+')
REQUIRED_VERSION="9.0"
if [ "$(printf '%s
' "$REQUIRED_VERSION" "$SSH_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]; then
echo "[FAIL] Версия OpenSSH $SSH_VERSION ниже минимальной ($REQUIRED_VERSION)"
ERRORS=$((ERRORS + 1))
else
echo "[ OK ] Версия OpenSSH: $SSH_VERSION"
fi
# Проверка наличия устаревших ключей хоста
if [ -f /etc/ssh/ssh_host_dsa_key ]; then
echo "[FAIL] Обнаружен устаревший DSA-ключ хоста"
ERRORS=$((ERRORS + 1))
fi
echo ""
if [ $ERRORS -eq 0 ]; then
echo "Результат: ВСЕ ПРОВЕРКИ ПРОЙДЕНЫ"
exit 0
else
echo "Результат: ОБНАРУЖЕНО ПРОБЛЕМ: $ERRORS"
exit 1
fi
Чек-лист безопасности SSH
Ниже — краткий чек-лист для быстрой проверки SSH на ваших серверах. Можете распечатать и повесить рядом с монитором — пригодится не раз:
- Обновление — OpenSSH обновлён до последней стабильной версии
- Протокол — используется только SSH-2
- Root-доступ —
PermitRootLogin no - Парольная аутентификация —
PasswordAuthentication no - Ключи — используются Ed25519; DSA и ECDSA удалены
- Криптография — настроены современные KexAlgorithms, Ciphers и MACs
- Постквантовый обмен ключами — включён sntrup761x25519-sha512
- Белый список — настроены AllowUsers или AllowGroups
- Тайминги — LoginGraceTime ≤ 30, MaxAuthTries ≤ 3
- Ненужные функции — отключены X11Forwarding, AgentForwarding, TcpForwarding
- Баннер — настроен предупредительный баннер
- Сертификаты — по возможности используется аутентификация по сертификатам
- 2FA — настроена двухфакторная аутентификация для администраторов
- Match-блоки — применяются для SFTP-пользователей и сервисных учёток
- Бастион — SSH-доступ идёт через бастионный хост
- ProxyJump — используется вместо Agent Forwarding
- Fail2Ban — настроен и работает
- Мониторинг — настроены оповещения о подозрительных входах
- Аудит — регулярные проверки ssh-audit
- Автоматизация — конфигурация управляется через Ansible или аналог
Заключение
Безопасность SSH — это не одноразовая настройка, а непрерывный процесс. CVE-2025-26465 и CVE-2025-26466 в очередной раз показали: даже проверенный годами протокол может преподнести серьёзные сюрпризы. А появление квантовых компьютеров делает переход на постквантовые алгоритмы актуальным уже сегодня — не «когда-нибудь потом».
Ключевые принципы, которых стоит придерживаться:
- Минимальные привилегии — каждый пользователь и сервис получает ровно тот доступ, который необходим, и ни байтом больше
- Глубокая защита — не полагайтесь на один механизм; комбинируйте ключи, сертификаты, 2FA, бастионы и мониторинг
- Автоматизация — ручная конфигурация не масштабируется и неизбежно приводит к ошибкам
- Непрерывный мониторинг — настройте оповещения и регулярный аудит
- Своевременное обновление — оперативно устанавливайте патчи OpenSSH
Начните с аудита текущей конфигурации при помощи ssh-audit, примените базовое усиление из этого руководства, а затем последовательно внедряйте более продвинутые меры — сертификаты, 2FA, бастионную архитектуру. Каждый шаг ощутимо повышает общий уровень защиты.
И помните: цена компрометации SSH-доступа — полный контроль над сервером. Время, потраченное на правильную настройку, окупается многократно. А вот время, потраченное на разбор последствий взлома — не окупается никогда.