Как SSH защищает ваше соединение – подробное руководство и примеры

Если вы когда-либо удалённо подключались к UNIX-подобному серверу, вы, скорее всего, использовали SSH, сокращение от «Secure Shell» (Безопасная оболочка). SSH, как следует из названия, предоставляет безопасный доступ к оболочке удалённых машин и используется практически повсеместно. Но что именно здесь означает «безопасный», и как эта безопасность обеспечивается протоколом? В этой статье мы рассмотрим функции безопасности SSH и то, как они защищают от типовых атак.

Что вообще значит «безопасный»?

Прежде чем мы начнём обсуждать, как SSH защищает от атак, нам сначала нужно выяснить, от чего мы вообще защищаемся и какие свойства нам для этого необходимо обеспечить. Это называется «модель угроз».

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

У Алисы есть веские причины подозревать Еву. Помимо простой ненависти к Алисе, Ева также обладает значительной властью над сетевым соединением Алисы, поскольку она — сетевой администратор в университете, где Алиса управляет своим сервером. Вполне возможно, что Ева может злоупотребить этой властью, чтобы не только проверять пакеты, отправленные Алисой, но и изменять их содержимое, если захочет.

Чтобы защититься от Евы, Алиса хочет помешать ей делать три вещи:

  • выполнять команды (что, очевидно, может вызвать хаос)
  • видеть выполняемые команды и их вывод (который может содержать секреты)
  • выводить сервер из строя (не позволяя Алисе или другим использовать его)

Это означает, что Алисе требуются следующие свойства от нашего SSH-соединения:

  • Конфиденциальность (Ева не может видеть, что происходит)
  • Целостность (Ева не может вмешаться в соединение — позже мы подробнее разберём, почему это так важно)
  • Доступность (Ева не может помешать Алисе работать на сервере)
  • Аутентификация (сервер знает, кто такая Алиса (и кто ею не является!), и поэтому может определить, можно ли выполнять команды)

Конфиденциальность, целостность и доступность образуют «CIA Triad», которая не имеет ничего общего с Центральным разведывательным управлением и лежит в основе информационной безопасности. Хотя аутентификация также подпадает под эту триаду, мы выделим её отдельно, чтобы лучше обсудить.

Мы собираемся шаг за шагом выстроить SSH для обеспечения этих свойств безопасности. «База», с которой мы начинаем, — это протокол, где вы можете отправить команду на сервер, который затем выполнит её и ответит выводом. Это, очевидно, небезопасно, но это оболочка, так что вторая часть у нас уже есть. Теперь давайте добавим немного безопасности!

Конфиденциальность через шифрование

Решение для конфиденциальности простое, но в то же время сложное: шифрование. Мы просто шифруем и команду, и вывод, что означает, что злоумышленники больше не могут их прочитать. К сожалению, шифрование также очень сложно и трудно реализовать безопасно, и очень легко — небезопасно³. У нас есть два варианта типа шифрования:

  • симметричное шифрование (с общим секретом)
  • асимметричное шифрование (с открытым ключом)

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

При асимметричном шифровании принимающая сторона объявляет миру свой открытый (публичный) ключ, который отправляющая сторона использует для шифрования данных. Закрытый (приватный) ключ затем используется для расшифровки этих данных.

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

Наш обмен будет выглядеть так:

  1. Алиса генерирует пару ключей и отправляет открытый ключ.
  2. Сервер генерирует пару ключей и отправляет открытый ключ.
  3. Они используют открытые ключи для шифрования своих сообщений и закрытые ключи для их расшифровки.

Конфиденциальность через шифрование

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

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

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

Теперь Ева не может вклиниться в середину, но она всё ещё настроена оптимистично. Когда Алиса подключится в следующий раз, Ева просто сохранит все сообщения, даже если не сможет их расшифровать. Они могут пригодиться позже. И действительно, пригодятся. Год спустя Алиса совершает фатальную ошибку при миграции своего сервера с Ubuntu на NixOS и случайно выкладывает свой закрытый ключ в открытый доступ. Ошибки случаются, и Алиса быстро это замечает и генерирует новый ключ, прежде чем Ева успевает что-либо перехватить. Но о нет, Ева увидела ключ и теперь может пойти и расшифровать все сообщения, которые Алиса когда-либо отправляла и которые Ева так усердно хранила. Это плохо, нам нужно помешать Еве расшифровать все эти сообщения, иначе утечка ключа станет катастрофой! Это свойство называется прямая секретность (forward secrecy). Способ обеспечить его — избегать использования долгоживущих секретов, таких как наш ранее установленный открытый ключ, для шифрования сообщений.

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

Но у нас есть проблема с производительностью: асимметричные операции довольно медленные, поэтому шифрование всего соединения с их помощью будет не очень быстрым. Время привлечь симметричное шифрование! Алгоритмы симметричного шифрования, такие как AES или ChaCha, очень быстры, но им нужен общий секрет. Самый простой способ создать общий секрет — это обмен ключами Диффи-Хеллмана. При обмене ключами Диффи-Хеллмана обе стороны генерируют открытый ключ и закрытый секрет, а затем используют открытый ключ другой стороны в сочетании со своим закрытым ключом для установления общего секрета.

Мы не можем напрямую использовать общий секрет из обмена Диффи-Хеллмана в качестве ключа шифрования. Это связано с тем, что общий секрет по математическим причинам не является равномерно случайным, то есть это не просто случайные на вид байты. Но симметричные шифры, такие как AES, требуют, чтобы секрет был равномерно случайным, то есть неотличимым от случайных данных (что также верно для вывода хороших симметричных шифров). К счастью, есть криптографический инструмент для превращения чего угодно в равномерно случайные байты: хеш-функция! Хеш-функция принимает произвольное количество данных и выводит короткий (для SHA-256 — 32 байта⁸) дайджест, который можно рассматривать как отпечаток данных. Важное свойство, которое нас интересует, — это то, что этот дайджест является равномерно случайным и может использоваться в качестве ключа. SSH обычно использует чистый SHA-256, хешируя общий секрет и несколько других вещей. Этот процесс называется «формирование ключа (Key Derivation)». Другое популярное решение для этого — HKDF (HMAC Key Derivation Function), которое используется в TLS, служащем для отображения этой веб-страницы. Надеюсь, вы цените эту веб-страницу. HKDF основан на HMAC (к которому мы ещё вернёмся позже) и, по сути, также сводится к хешу (обычно SHA-256) с некоторыми дополнительными блестящими элементами, о которых мы не будем беспокоиться.

Итак, наша следующая версия протокола выглядит так:

  1. Алиса генерирует ключ Диффи-Хеллмана и отправляет открытый ключ.
  2. Сервер генерирует ключ Диффи-Хеллмана и отправляет открытый ключ.
  3. Сервер отправляет подпись общего секрета своим долгоживущим ключом.
  4. Алиса проверяет правильность подписи.
  5. Они могут отправлять сообщения, зашифрованные с помощью симметричного шифра и полученного общего секрета.

Это уже намного лучше.

Прежде чем дальше защищать наше соединение, сделаем небольшое отступление относительно выбора алгоритма шифрования.

Защита от атак понижения уровня (downgrade attacks)

Ранее мы упоминали, что будет использоваться безопасный алгоритм шифрования, но не говорили, как этот алгоритм выбирается. SSH (версия 2, которую все используют) датируется 2006 годом. Алгоритмы, используемые сегодня, появились позже. Это возможно благодаря тому, что в SSH есть согласование алгоритмов. Перед фактическим обменом ключами обе стороны отправляют сообщение со списком поддерживаемых ими алгоритмов для шифра, метода обмена ключами (Диффи-Хеллман) и алгоритма подписи ключа хоста. Затем обе стороны берут списки поддерживаемых алгоритмов и определяют, какой из них использовать. Поскольку SSH довольно старый, он поддерживает некоторые шифры, которые сегодня не считаются безопасными, например 3DES или даже RC4⁹. У Алисы также могут быть причины не использовать определённые современные шифры. Например, если ей очень важно, чтобы эти данные оставались секретными в течение 50 лет, она может предпочесть не использовать AES-128 (хотя он абсолютно безопасен сегодня и, фактически, используется для отображения этой веб-страницы), поскольку он не является квантово-устойчивым. Если ей действительно это важно, она должна отключить его поддержку в своей конфигурации, но мы хотим оставаться в безопасности, даже если она забудет это сделать.

Сервер Алисы поддерживает и предпочитает самые последние и лучшие шифры… но что, если Ева обманет Алису, заставив её поверить, что сервер их не поддерживает? Когда сервер отправляет свой список поддерживаемых алгоритмов, Ева изменяет его, оставляя только 3DES (или какой-то другой нежелательный шифр). Когда клиент Алисы сообщает серверу о поддерживаемых шифрах, Ева снова изменяет его, оставляя только 3DES. Теперь и сервер, и клиент думают, что их собеседник поддерживает только 3DES, и выбирают 3DES¹⁰, чего Алиса бы не хотела! Это называется «атака понижения уровня», поскольку она понижает хорошую безопасность до плохой, которой может воспользоваться Ева.

Мы хотим от этого защититься. Атаку понижения уровня можно остановить, если одна из сторон заметит, что сообщение со списком поддерживаемых алгоритмов было изменено. Сервер мог бы аутентифицировать своё сообщение, чтобы клиент мог его проверить. У нас уже есть способ для сервера аутентифицировать что-либо: подпись ключа хоста! Поэтому SSH берёт всё сообщение о согласовании алгоритмов, которое он отправил клиенту, а также то, которое он получил от клиента, и включает их в подпись. Он хеширует сообщения, общий секрет и несколько дополнительных вещей, а затем подписывает хеш. Когда клиент приступает к проверке подписи, он также собирает сообщение о согласовании алгоритмов, полученное от сервера, и то, которое он отправил серверу, и включает их в свой хеш. Только если подписанный хеш совпадает с его собственным хешем, он продолжает работу. Это означает, что если Ева вмешается в любое из сообщений, сервер и клиент разойдутся во мнениях о содержимом этого сообщения, что приведёт к разнице в хеше, и подпись станет недействительной. Сервер сам это не проверяет и полагается на то, что клиент заметит любые проблемы.

Вся эта схема шифрования хорошо сохраняет данные в тайне, но нам всё ещё чего-то не хватает, чтобы сделать соединение действительно безопасным…

Целостность через MAC

Хотя теперь связь зашифрована, она не защищена от подделки. Чтобы точнее понять, как это может выглядеть, и даже провести конкретную атаку, нам сначала нужно понять, как на самом деле работает симметричное шифрование. Шифр может быть либо блочным, либо потоковым. Блочный шифр разбивает сообщение на множество блоков (например, по 16 байт) и затем шифрует один блок за другим. AES — это блочный шифр.

Для этого вам нужен режим работы, который описывает, как именно шифруются блоки. Самым популярным режимом работы AES в наши дни является режим счётчика (CTR) (или, вернее, его улучшенный брат — режим Галуа/счётчика (GCM), к которому мы вернёмся позже). Режим счётчика на самом деле не шифрует сообщение по блокам, он просто шифрует число, счётчик, для каждого блока. Полученный шифротекст затем можно использовать как ключевой поток для потокового шифра.

Потоковый шифр использует поток случайно выглядящих байтов (ключевой поток), которые затем складываются по модулю 2 (XOR) с открытым текстом для получения шифротекста. Примером потокового шифра является ChaCha (который по своей сути является потоковым шифром, а не просто блочным шифром в режиме счётчика). При расшифровке получатель генерирует тот же ключевой поток и выполняет операцию XOR над шифротекстом, получая обратно открытый текст. Это использует свойство обратимости операции XOR.

Теперь, когда Ева тоже это знает, она хочет провести атаку против Алисы, которая использует потоковый шифр для шифрования своих команд.

Ева знает, что Алиса очень любит выполнять ls -l на своём сервере. В нашем примере Алиса использует ChaCha20 (число означает количество раундов, которое настраивается для ChaCha) с каким-то ключом, неизвестным Еве и вам. Но что Ева видит, так это шифротекст: 7a1f7b420f52102640f4. Поскольку она знает, что Алиса часто выполняет ls -l, она предполагает, что это ls -l какого-то каталога. Ева любит хаос, поэтому она хотела бы вместо этого удалить каталог с помощью rm -r. Как мы видели ранее, открытый текст получается путём операции XOR над шифротекстом и ключевым потоком. Это означает, что если мы изменим бит в шифротексте, он изменится и в открытом тексте! Поскольку ls -l и rm -r имеют одинаковую длину, нам просто нужно изменить ls в rs , а sв m. Если предположить кодировку ASCII/UTF-8, мы получим следующие значения для символов:

l=6C, s=73, r=72, m=6d.

Ева использует это, чтобы определить, какие биты ей нужно изменить, маски f1 и f2 (которые вычисляются как XOR двух букв).

l =0110 1100      s =0111 0011

r =0111 0010      m =0110 1101

f1=0001 1110      f2=0001 1110

Ева затем использует f1 чтобы изменить биты для первого и пятого байта и f2 для второго. Это приводит к шифротексту 64017b421152102640f4. Ева передаёт сообщение серверу, который затем расшифровывает его в… rm -r /etc! Очень плохо, весь каталог /etc Алисы только что был удалён! Таким образом, несмотря на то, что Ева не смогла расшифровать шифротекст, она смогла использовать свои знания о том, чем он может быть, чтобы подделать его, заставив выполнить свою команду. Посмотрите код на Rust, реализующий это.

Нам нужно убедиться, что этого не произойдёт.

Способ сделать это — использовать код аутентификации сообщения (MAC). MAC — это хеш сообщения, но он также включает общий ключ в хешируемое содержимое, чтобы Ева не могла просто пересоздать хеш после подделки. HMAC — самый популярный алгоритм для этого, поэтому мы будем использовать его с какой-нибудь криптографической хеш-функцией, такой как SHA-256. HMAC — это умный и безопасный способ хеширования ключа и сообщения, чтобы доказать, что сообщение исходит от кого-то, у кого есть ключ. После шифрования сообщения мы прогоняем его через HMAC с ключом и получаем хеш, который мы добавляем в конец сообщения. Получатель сначала сам вычисляет хеш, и только если он совпадает, расшифровывает сообщение.

Это работает хорошо, но в наши дни используется реже. Современное шифрование использует так называемые «AEAD», что означает «Authenticated Encryption with Associated Data» (аутентифицированное шифрование с присоединёнными данными). С AEAD вам не нужно проверять свой собственный MAC, так как создание и проверка MAC являются частью самого процесса шифрования, что упрощает использование. Два AEAD, используемые в SSH (и TLS), — это AES-GCM (который является AES в режиме Галуа/счётчика, то есть режим счётчика, который мы описали ранее, за исключением того, что он также включает хеш в качестве MAC) и ChaCha20Poly1305 (который является ChaCha20 с MAC, сгенерированным хеш-алгоритмом Poly1305).

Теперь у нас есть полностью зашифрованный и аутентифицированный (здесь аутентификация относится к аутентификации сообщения, то есть к защите от подделки; это не связано с аутентификацией пользователя) поток для отправки наших команд, оставляя нам реализовать последнюю часть, за которую отвечает SSH:

Аутентификация пользователя через пароли и открытые ключи

Хотя очень круто, что Ева не может использовать соединение Алисы, это также довольно бессмысленно, если Ева может просто войти на сервер напрямую сама. Нам нужно помешать Еве это сделать.

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

Вход по паролю очень прост. Алиса отправляет свой пароль на сервер, сервер проверяет его так же, как если бы она входила в систему физически, а затем предоставляет ей доступ или нет. Для этого очень важно, чтобы у нас было зашифрованное соединение, потому что если бы мы передавали пароль в открытом тексте, Ева могла бы просто прочитать его и использовать сама. Аутентификация по паролю также раскрывает пароль серверу, что может быть плохо в случае, если Еве удастся как-то захватить сервер. Тогда Алиса сообщила бы свой пароль, который она может использовать и в других местах, если не использует менеджер паролей, прямо Еве. Вдобавок к этому недостатку, удобство использования паролей также очень низкое, так как их нужно вводить каждый раз, что раздражает.

Всё это решается с помощью аутентификации по открытому ключу. У Алисы есть закрытый ключ и открытый ключ, открытый ключ хранится на сервере, и она затем использует этот ключ, чтобы подписать что-то для сервера, что доказывает, что она — Алиса. Это очень похоже на то, что мы делали ранее при обмене ключами, когда доказывали, что сервер действительно тот, за кого себя выдаёт. Здесь (и для сервера ранее тоже) очень важно, чтобы Алиса каждый раз подписывала что-то новое, потому что если бы ей приходилось каждый раз подписывать одно и то же сообщение, Ева могла бы просто перехватить подпись в первый раз, а затем просто отправить её снова, чтобы притвориться Алисой (это называется атака повторного воспроизведения (replay attack)). SSH обеспечивает это, подписывая, среди прочего, «идентификатор сессии» — набор случайных байтов, которыми обмениваются в начале соединения.

Доступность через надежду

Последний кусочек в нашей головоломке безопасности — это доступность. Ева любит устраивать хаос, а вывод сервера из строя — это, безусловно, большой хаос. Нам нужно помешать Еве провести атаку типа «отказ в обслуживании (Denial of Service)», которая остановит работу сервера. Сам протокол SSH этим не занимается.

Основной вектор атаки здесь — обмен ключами. Просто отправив свой открытый ключ Диффи-Хеллмана, клиент может заставить сервер выполнить дорогостоящее вычисление обмена ключами, не выполняя никаких вычислений самостоятельно. Если клиент делает это много раз, он может занять довольно много ресурсов сервера.

Хотя в ядре протокола нет ничего для защиты от этого, реализации SSH это делают. Например, в OpenSSH есть конфигурация MaxStartups (по умолчанию 10), которая ограничивает количество соединений, чтобы не было слишком много одновременных обменов ключами. Хотя это может сэкономить ресурсы, это также можно использовать для блокировки входа Алисы на её собственный сервер, если Ева заполнит этот лимит.

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

Если это станет проблемой для Алисы, лучшим решением для защиты от Евы будет изменение брандмауэра, чтобы разрешить доступ только с определённых IP-адресов, используемых Алисой, блокируя при этом все IP-адреса Евы. Она также могла бы использовать VPN-решение, такое как Wireguard, чтобы подключаться к серверу только через VPN и никогда не выставлять SSH в публичный доступ.

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

Всё в порядке

И вот так, всё в порядке. По крайней мере, мы на это надеемся.

Наш окончательный процесс работает так:

  1. Алиса открывает соединение, отправляя свой открытый ключ Диффи-Хеллмана.
  2. Сервер отвечает своим открытым ключом Диффи-Хеллмана и подписью со своим ключом хоста.
  3. Алиса проверяет подпись с помощью известного ей открытого ключа сервера.
  4. Затем Алиса подписывает соединение своим открытым ключом.
  5. Сервер проверяет подпись и открытый ключ по списку известных открытых ключей для Алисы.
  6. Теперь Алиса может безопасно выполнять команды и получать вывод.

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

Есть несколько примеров того, что может пойти не так, даже сейчас:

  • Наши криптографические алгоритмы могут быть небезопасными. Если есть фундаментальная уязвимость в алгоритме обмена ключами, алгоритме подписи ключа хоста или алгоритме шифрования, то Ева может использовать это слабое звено в цепи и получить доступ. Соединение безопасно, только если всё работает идеально. Современные соединения OpenSSH используют curve25519 для обмена ключами (или даже sntrup761x25519, который устойчив к квантовым компьютерам), ed25519 для подписи ключа хоста и ChaCha20Poly1305 или AES256-GCM для шифрования. Все они считаются безопасными сегодня, но это всегда может измениться в будущем (особенно с появлением квантовых компьютеров, которые могут взломать curve25519, ed25519 и AES128).
  • Ева может получить информацию из времени отправки сообщений и анализа объёма трафика. Наблюдая за тем, сколько трафика отправляется и когда, Ева может сделать выводы о некоторых свойствах соединения, например, какие команды может выполнять Алиса. Это частично смягчается шифрованием длины пакета, отправкой случайных игнорируемых сообщений и небольшим количеством случайного дополнения в каждом сообщении.
  • Плохое управление ключами с любой стороны. Если долгоживущий ключ хоста или закрытый ключ Алисы будут раскрыты, Ева сможет использовать это для перехвата сообщений или притвориться Алисой.

И на этом я желаю вам счастливого и безопасного удалённого выполнения кода!

Что почитать дальше

Если вы хотите узнать больше на эту тему, я могу порекомендовать следующие ресурсы:

Рейтинг
( 1 оценка, среднее 5 из 5 )
EvilSin225/ автор статьи
Понравилась статья? Поделиться с друзьями:
Компьютерные технологии
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: