MAATRIX / Блог / Что происходит между нажатием Enter в SSH и появлением приглашения

Что происходит между нажатием Enter в SSH и появлением приглашения

MAATRIX

Вы набираете ssh user@ip, жмёте Enter — и дальше либо мгновенно видите приглашение shell, либо секунды зависания, либо предупреждение про изменившийся отпечаток ключа, которое пугает даже опытных админов. За этой паузой скрывается целая последовательность шагов: TCP-соединение, обмен версиями протокола, согласование алгоритмов, обмен ключами Диффи-Хеллмана, проверка сервера и только потом — ваша собственная аутентификация. Разберём всю цепочку по порядку, чтобы вы понимали, что происходит на каждом шаге и на каком именно этапе искать проблему, если что-то пошло не так.

TCP-хендшейк: то, что происходит ещё до всякого SSH

Прежде чем стороны скажут друг другу хоть слово на языке SSH, должно установиться обычное TCP-соединение — SSH работает поверх TCP, обычно на порту 22. Происходит стандартное трёхстороннее рукопожатие: клиент отправляет пакет с флагом SYN, сервер отвечает SYN-ACK, клиент подтверждает пакетом ACK. Только после этого можно передавать данные протокола SSH.

Здесь же скрывается первая частая точка отказа, и она вообще не про SSH. Если порт 22 закрыт фаерволом сервера или облачным security group, поведение зависит от того, как именно блокируют:

  • DROP (пакет молча отбрасывается) — клиент ждёт таймаут, это выглядит как долгое зависание без единой строчки вывода;
  • REJECT (TCP RST или ICMP unreachable) — клиент почти сразу видит Connection refused;
  • порт открыт, но sshd не запущен — тоже мгновенный Connection refused.

Проверить, что порт вообще открыт снаружи, можно отдельно от SSH:

nc -zv ВАШ_IP 22

Если здесь тишина или таймаут — проблема не в ключах и не в конфиге SSH, а в сетевой доступности: фаервол на сервере (iptables/nftables/ufw), правила облачного провайдера или блокировка у вашего провайдера связи. Идти разбирать аутентификацию рано, пока не подтверждено, что TCP-соединение вообще устанавливается.

Обмен версиями протокола и согласование алгоритмов

Как только TCP-соединение установлено, обе стороны обмениваются строкой версии протокола — это первое, что каждая сторона отправляет открытым текстом, ещё без шифрования. Выглядит это примерно так:

SSH-2.0-OpenSSH_9.6

Точная строка зависит от версии на вашем сервере и клиенте — не полагайтесь на конкретный номер, смотрите свою через ssh -V. По ней стороны понимают, что обе говорят на SSH протоколе версии 2 (SSH-1 как небезопасный сейчас не встречается) и какая конкретно реализация на другом конце — это влияет на то, какие расширения протокола доступны.

Дальше начинается уже двоичный обмен, и первый содержательный пакет — SSH_MSG_KEXINIT — стороны обмениваются списками поддерживаемых алгоритмов по нескольким категориям одновременно:

КатегорияЧто определяетПримеры алгоритмов
Key exchange (kex)как будет получен общий секретный ключcurve25519-sha256, diffie-hellman-group16-sha512
Host keyкаким типом ключа сервер докажет, что он — это онssh-ed25519, rsa-sha2-512
Cipherчем будет шифроваться трафик после рукопожатияchacha20-poly1305, aes256-gcm
MACкак проверяется целостность каждого пакетаобычно встроен в AEAD-шифры вроде GCM/Poly1305

Список алгоритмов, которые предложит ваш клиент, зависит от его версии — посмотреть можно командой ssh -Q kex. Сервер и клиент берут пересечение списков и выбирают первый общий по приоритету клиента. Если пересечения нет — например, старый сервер поддерживает только устаревшие алгоритмы, а современный клиент их отключил — соединение обрывается ошибкой вида no matching key exchange method found, и в сообщении обычно перечислены алгоритмы сервера. Решение — обновить SSH-сервер либо (как временная и менее безопасная мера) явно указать клиенту нужный алгоритм через -oKexAlgorithms=....

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Диффи-Хеллман: как рождается общий session key

Дальше — обмен ключами по схеме Диффи-Хеллмана (или его эллиптической разновидности, вроде curve25519, сейчас более распространённой, чем классический DH). Смысл в том, что клиент и сервер, обмениваясь исключительно открытыми, публично видимыми данными, вычисляют один и тот же секретный сеансовый ключ, который наблюдатель канала восстановить не может, даже видя весь обмен.

Грубо это устроено так: обе стороны генерируют временную (эфемерную) пару ключей, обмениваются публичными частями, и каждая, комбинируя свою приватную часть с полученной публичной, получает одно и то же число — общий секрет. Из него через хеширование выводятся ключи шифрования и ключи проверки целостности, отдельные для каждого направления.

Ключевое свойство схемы — forward secrecy: эфемерные ключи генерируются заново для каждой сессии и нигде не хранятся, поэтому компрометация постоянного ключа сервера (host key, о нём ниже) в будущем не позволяет расшифровать уже прошедший трафик. Host key нужен не для шифрования, а для другого — подписать этот обмен и подтвердить, что на том конце действительно ваш сервер. Это следующий шаг.

Host key сервера: отпечаток и что значит его "изменение"

После вычисления общего секрета сервер должен доказать, что он — именно тот сервер, к которому вы хотели подключиться, а не кто-то, подменивший соединение посередине. Для этого сервер подписывает данные обмена своим host key (постоянным, в отличие от эфемерных ключей DH — этот ключ живёт в /etc/ssh/ssh_host_ed25519_key и подобных файлах и не меняется от сессии к сессии) и отправляет клиенту публичную часть вместе с подписью.

Именно тут вы видите знакомое предупреждение при первом подключении к серверу:

The authenticity of host 'ВАШ_IP (ВАШ_IP)' can't be established.
ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Проблема, которую SSH тут честно признаёт: при первом подключении у клиента нет способа проверить, что этот host key принадлежит именно вашему серверу, а не злоумышленнику, перехватившему соединение (модель доверия называется TOFU — trust on first use). Проверить по-настоящему можно, только сверив отпечаток (fingerprint) с тем, что показывает панель управления хостинга или что вы сняли на сервере через консоль (VNC/serial), а не через тот же SSH-канал, который проверяете. Для большинства арендованных серверов это избыточная предосторожность, но для чувствительных случаев — сверяйте.

Если вы подтвердили подключение (ответили yes), отпечаток сохраняется в файл ~/.ssh/known_hosts на вашей машине, и при всех последующих подключениях клиент молча сверяет присланный ключ с сохранённым — без лишних вопросов, если они совпадают.

А вот что происходит, если ключ не совпал с уже сохранённым:

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!

Это самое громкое сообщение SSH — и оно не всегда означает атаку. Легитимные причины смены host key: вы переустановили ОС на том же сервере (новая установка — новые host keys), провайдер выдал вам после переустановки или восстановления из бэкапа тот же IP на физически другой машине, или администратор намеренно перевыпустил ключи после утечки.

Но эта же ошибка — ровно то, что вы увидите при реальной MITM-атаке, когда кто-то на пути пакетов подменяет сервер своим прокси. Отличить одно от другого по самому сообщению нельзя — проверяйте контекст: вы сами недавно переустанавливали систему или меняли IP через провайдера? Если объяснимой причины нет — не снимайте предупреждение бездумным ssh-keygen -R, сначала уточните у провайдера, не менялась ли машина за этим IP. Удаление строки из known_hosts снимает блокировку, но "вслепую" — вы соглашаетесь довериться новому ключу, не проверив его.

Аутентификация: как вы доказываете, что ключ ваш

К этому моменту канал уже зашифрован session key из шага с Диффи-Хеллманом. Осталось доказать серверу, что вы — тот, за кого себя выдаёте.

При аутентификации по публичному ключу (рекомендуемый способ, настраивается через SSH-ключи вместо пароля) сервер не "шифрует что-то приватным ключом клиента" и не хранит ваш приватный ключ — он его никогда не видел. Схема устроена как доказательство владения ключом без передачи самого ключа:

  1. Клиент сообщает серверу, каким публичным ключом собирается аутентифицироваться (он уже должен быть в ~/.ssh/authorized_keys на сервере).
  2. Сервер проверяет, разрешён ли такой ключ для этого пользователя. Если нет — отказ, или клиент пробует следующий ключ.
  3. Если ключ разрешён, сервер формирует challenge — случайные данные, привязанные к сессии — и просит подписать их приватным ключом.
  4. Клиент подписывает данные приватным ключом (он никогда не покидает вашу машину; если ключ защищён паролем, тут запросится пароль ключа — не путать с паролем пользователя на сервере) и отправляет подпись.
  5. Сервер проверяет подпись открытым ключом из authorized_keys. Подпись сходится — значит, у клиента есть приватный ключ, аутентификация пройдена.

Кража одного публичного ключа бесполезна для злоумышленника — он лежит открыто в authorized_keys и не позволяет пройти этот шаг, нужен приватный. И поэтому потеря приватного ключа — серьёзная проблема: без него доказать владение нечем, а SSH не восстановит доступ сам, если это был единственный способ входа.

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

Запуск shell через PTY

Аутентификация пройдена — но приглашение появляется не мгновенно. Клиент запрашивает у сервера канал (channel) для сессии и просит выделить псевдотерминал (PTY) — виртуальное терминальное устройство, через которое сервер запускает вашу login shell (bash, zsh) так, будто вы сидите за реальным терминалом. Именно PTY даёт корректную работу курсора, цветов, изменения размера окна, программ вроде top или vim.

После выделения PTY сервер запускает login shell, она выполняет файлы инициализации (/etc/profile, ~/.bash_profile или ~/.zprofile), выводит MOTD (приветственное сообщение с версией ОС и иногда сводкой обновлений) и печатает приглашение. На этом моменте вы видите user@host:~$ и получаете интерактивный доступ.

Как читать вывод ssh -vvv, чтобы найти, на каком шаге зависло

Когда подключение не срабатывает или зависает, самый быстрый способ понять, на каком именно из описанных выше шагов — не гадать, а запустить SSH в режиме подробного лога:

ssh -v user@ВАШ_IP

Одного -v обычно достаточно, чтобы увидеть основные этапы. Если нужно ещё подробнее — вплоть до деталей согласования конкретных алгоритмов и попыток аутентификации по каждому найденному ключу — добавляйте v, вплоть до -vvv (максимальный уровень детализации).

Дальше — на какой строке лога остановился прогресс, там и ищите проблему:

  • Лог обрывается сразу после debug1: Connecting to ВАШ_IP port 22 и дальше долгая тишина до таймаута — проблема на уровне TCP: фаервол, security group у провайдера или неверный порт. Это ещё до всякого SSH-протокола, см. первый раздел.
  • Есть строка debug1: Remote protocol version 2.0, remote software version OpenSSH_x.y, но дальше зависание на согласовании алгоритмов (SSH2_MSG_KEXINIT) — редкий случай, обычно указывает на несовместимость версий протокола или проблему на промежуточном узле (прокси, балансировщик), который вмешивается в трафик.
  • Строка debug1: Host key verification failed или явный запрос подтверждения fingerprint — вы на этапе проверки host key, см. соответствующий раздел выше.
  • Видите последовательность debug1: Authentications that can continue: publickey,password, а затем Offering public key, Server accepts key и всё равно Permission denied (publickey) — проблема не в сетевой части, а именно в аутентификации: не тот ключ предложен, ключ не добавлен в authorized_keys, либо неверные права на файлы (~/.ssh должен быть 700, authorized_keys600).
  • Аутентификация прошла (debug1: Authentication succeeded), но команда ssh всё равно висит без приглашения — вероятно, проблема уже не в SSH, а в самой login shell на сервере: что-то зависает в .bashrc/.bash_profile (например, скрипт пытается достучаться до недоступной сети) или диск сервера переполнен и shell не может запуститься нормально.

Такой построчный разбор быстрее любых догадок: вместо "SSH не работает" вы получаете точный этап, на котором остановился протокол, и дальше уже целенаправленно чините именно его — сеть, host key, права доступа или конфиг shell. Если базовый вход по паролю на сервере ещё не заменён на ключи, стоит один раз пройти настройку SSH-ключей на VPS — это уберёт целый класс проблем аутентификации из списка выше. А если сервер вообще не отвечает по SSH ни на каком этапе — там же собран отдельный чек-лист на случай, когда сервер недоступен по SSH.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Почему подключение зависает именно на пару секунд, а не мгновенно?

Обычно это накладные расходы на резолвинг DNS имени (если вы подключаетесь по домену, а не по IP), RTT до сервера при нескольких обменах пакетами туда-обратно на разных этапах хендшейка, и иногда — попытка сервера сделать обратный DNS-резолвинг вашего IP (для логов), которая при недоступном DNS-сервере ждёт таймаут. Конкретное время зависит от вашей сети и настроек сервера, точных цифр без замера на месте не назвать.

SSH использует один и тот же ключ для шифрования всей сессии от начала до конца?

Нет. Сеансовые ключи шифрования выводятся из общего секрета Диффи-Хеллмана и действуют для текущей сессии; при долгих соединениях протокол также поддерживает rekeying — периодическую пересогласование ключей по объёму переданных данных или времени, без разрыва соединения.

Можно ли перехватить пароль, если он передаётся при парольной аутентификации?

На пути следования — нет, потому что к моменту аутентификации канал уже зашифрован сеансовым ключом. Риск в другом: если host key не проверен и вы подключились не к тому серверу (успешная MITM), тогда пароль достанется атакующему, который и представился вам легитимным сервером. Это ещё одна причина не игнорировать предупреждение о смене host key.

Что произойдёт, если оборвать соединение прямо во время обмена ключами Диффи-Хеллмана?

Ничего критичного — эфемерные ключи для этой сессии просто выбрасываются с обеих сторон, session key не сформируется, и клиент увидит обрыв соединения. Никакого "недошифрованного" состояния не остаётся, начинать пришлось бы заново с нуля.

Нужно ли мне вообще что-то из этого знать, если SSH и так работает?

Для повседневной работы — нет, всё происходит автоматически за доли секунды. Разбор полезен ровно в двух случаях: когда подключение не работает и непонятно, на каком шаге (тогда ssh -vvv и таблица выше экономят часы), и когда вы видите предупреждение про host key и должны решить, доверять или нет.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →