Четыре шага в защите SSH

Secure Shell можно найти повсюду. С момента выпуска в 1995 году, SSH  получил широкое распространение как мощный протокол удаленного доступа для Linux.

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

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

SSH Host Keys как защита от MiM

SSH — один из самых распространенных протоколов, обеспечивающий безопасное зашифрованное соединение, например для подключения к удаленным машинам, обмена файлами, организации защищенных туннелей, удаленного управления без ручной авторизации и так далее. Он был создан для замены многих нешифрованных протоколов, таких как telnet, FTP, RSH и подобных. Ведь известно, что одна из проблем этих старых протоколов (помимо того, что они все данные передают в открытом виде) — возможность организации man-in-the-middle атак. Хакер с доступом во внутреннюю сеть может перехватывать пакеты, записывать их и лишь потом передавать по назначению. Более того, нападающий может изменять информацию в пакетах — например написать rm -r вместо ls -l, или передать трояна в ходе FTP скачки.

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

  1. Клиент (браузер) соединяется с сервером (HTTPS-сервер на порту 443).
  2. Сервер отдает публичный ключ (X509 сертификат).
  3. Клиент определяет, имеет ли сервер доступ к приватному ключу, ассоциированному с публичным.
  4. Броузер проверяет подпись ключа сервер через так называемый Certificate Authority (Verisign, Thawte или другие).
  5. Броузер проверяет, что сертификат относится именно к тому серверу, к которому вы коннектитесь (CN параметр).

Все эти шаги делает и SSH, за исключением соединения м Центром Сертификации в пункте 4, в нем применяется несколько другая схема.

SSH Host Keys в действии

Посмотрим как работает SSH при присоединении к машине, ранее невиданной нашим компьютером.

$ ssh ssh-server.example.com
The authenticity of host 'ssh-server.example.com (12.18.429.21)' can't be established.
RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.
Are you sure you want to continue connecting (yes/no)?

Скажем «Да» и установка связи продолжится:

$ ssh ssh-server.example.com
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ssh-server.example.com,12.18.429.21' (RSA) to the list of known hosts.
Password: (enter password)
ssh-server.example.com $

Сервер предоставил клиенту host key в процессе установки связи. Так как мы никогда не соединялись с этой машиной и SSH не имеет стороннего Центра Сертификации, придется с ключами управляться вручную. Клиент показывает «отпечаток» ключа , легко читаемую строчку из цифр — как с ней оперировать мы рассмотрим дальше. Если указать, что fingerprint ключа правильный, тогда клиент SSH продолжит соединения, позволит ввести пароль и войти в систему.

В нашем примере мы ответили «Да» и клиент сохранил ключ удаленной машины в $HOME/.ssh/known_hosts. В дальнейшем именно этот файл и будет служить вашим персональным Центром Авторизации — в нем хранится список SSH серверов, которые вы признали правильными. Посмотрим на последнюю строчку в этом файле:

$ tail -1 $HOME/.ssh/known_hosts
ssh-server.example.com,12.18.429.21 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA0
6jFqviLMMJ/GaJNhGx/P6Z7+4aJIfUqcVjTGQ
asS1daDYejcfOAWK0juoD+zS3BsGKKYKPA
5Gc5M8v+3NHLbPn1yTpDBgl6UzA0iiMPCbw
nOLx61MrBTk+/qJI9kyDaJf4LEY6Chx4IJP0ZN
5NmAlCtXQsca3jwFAF72mqPbF8=

(Не обращайте внимание на переносы :))

Каждая запись в known_hosts это одна строчка с тремя или больше пробелами, отделяющими поля:

  1. Один или больше имен серверов или IP серверов (в нашем случае записался адрес и имя, так что можно будет коннектится по тому и по другому).
  2. Тип ключа.
  3. Сам публичный ключ.
  4. Некие комментарии (в данном примере не присутствуют).

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

$ ssh ssh-server.example.com
Password: (enter password)

Очевидно, что в этот раз клиент не просил подтверждения ключа, так как тот уже хранится в $HOME/.ssh/known_hosts. Отмечу, что SSH проверяет его наличие в нескольких местах:

  1. Глобальный файл известных хостов, обычно /etc/ssh/ssh_known_hosts. Его положение можно изменить через GlobalKnownHostsFile в /etc/ssh/ssh_config.
  2. Юзерский файл $HOME/.ssh/known_hosts, можно менять через UserKnownHostsFile.

Верификация ключа хоста

Выше я показал как коннектится к хосту и принимать его ключ. Однако, откуда узнать правильный ли ключ попал к нам в руки? Если вы соединились с сервером, а атакующий перехватил и перенаправил через себя SSH соединение (естественно это возможно только на стадии установки первого соединения), он может подставить свой ключ. Лучший путь верифицировать ключ — прибегнуть к сторонней информации. Например, владелец ключа может выложить его отпечаток на защищенной SSL странице. В одном месте где я работал, ключ распространялся на пластиковой карточке с экстренными телефонами, так что достаточно было просто открыть бумажник сидя в инет-кафе. Если невозможно проверить ключ из внешних источников, то его можно проверить залогинившись. Публичный ключ обычно лежит в /etc/ssh/, так что зайдя на сервер можно просмотреть его контрольную сумму (используя ssh-keygen).

Вернемся к нашему соединению и посмотрим как это делается:

$ ssh ssh-server.example.com
The authenticity of host 'ssh-server.example.com (12.18.429.21)' can't be established.
RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.
Are you sure you want to continue connecting (yes/no)? yes

Warning: Permanently added 'ssh-server.example.com,12.18.429.21' (RSA) to the list of known hosts.

Password: (enter password)

ssh-server.example.com $ cd /etc/ssh
ssh-server.example.com $ ls *.pub
ssh_host_dsa_key.pub ssh_host_rsa_key.pub ssh_host_key.pub

ssh-server.example.com $ ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key.pub
1024 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d ssh_host_rsa_key.pub

Видно, что отпечатки совпали и мы зашли на нужный сервер.

Изменение ключей

SSH может тремя разными способами реагировать на неправильный или измененный ключ. Параметр, который отвечает за это называется  StrictHostKeyChecking.

Самый небезопасный способ такой:

StrictHostKeyChecking=no

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

Другой вариант выглядит так:

StrictHostKeyChecking=ask

Именно так настраивается SSH по умолчанию. Если у вас нету ключа для сервера, то клиент покажет fingerprint и попросит подтвердить его подлинность, это мы уже проходили в первой части статьи. Если же вы соединяетесь с сервером и ключи не совпадают, то программа не даст зайти на удаленный компьютер и укажет где найти конфликтующую запись в known_hosts:

$ ssh -o stricthostkeychecking=ask ssh-server.example.com
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)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
23:00:20:83:de:02:95:f1:e3:34:be:57:3f:cf:2c:e7.
Please contact your system administrator.
Add correct host key in /home/xahria/.ssh/known_hosts to get rid of this message.
Offending key in /home/xahria/.ssh/known_hosts:8
RSA host key for localhost has changed and you have requested strict checking.
Host key verification failed.

Ну и последний вариант этой опции:

StrictHostKeyChecking=yes

Это, понятно, самый защищенный, но в то же время и не дружественный для владельца вариант. Если ключа нет, SSH просто запретит соединение с сервером:

$ ssh -o 'StrictHostKeyChecking=yes' ssh-server.example.com
No RSA host key is known for localhost and you have requested strict checking.
Host key verification failed.

Если ключ етсь, но он не соответствует полученному, то события будут разворачиваться по варианту «ask».

Почему меняются ключи?

На то возможно несколько причин. Помимо хакерской активности возможны и вполне мирные объяснения:

  • Клиент или сервер изменились и теперь они работают по SSHv2, а не по SSHv1
  • Сервер переустановили с прежним именем, но ключи потерялись и естественно были сгенерированны заново.
  • Машина обрела другое имя или IP адрес.

Виды ключей

SSH включает в себя два протокола — SSHv2 и SSHv1. Более старый SSHv1 использует алгоритм RSA, в то время как SSHv2 поддерживает RSA и DSA. Сервер SSH может использовать любой из типов ключей: SSHv1 RSA, SSHv2 RSA или SSHv2 DSA. В терминологии OpenSSH это rsa1, rsa и dsa. SSH Host Keys создаются командой ssh-keygen. Вероятнее всего, так как у вас на машине уже есть SSH, клиент или сервер уже сгенерировали его во время инсталляции. Конфиг sshd_config из /etc/ssh показывает какие хост ключи грузятся при запуске:

# Which protocol(s) should we support?
Protocol 2,1

# HostKey for protocol version 1
HostKey /etc/ssh/ssh_host_key

# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key

 

Ключи можно создать так:

# ssh-keygen -t rsa /etc/ssh/ssh_host_rsa_key
# ssh-keygen -t dsa /etc/ssh/ssh_host_dsa_key
# ssh-keygen -t rsa1 /etc/ssh/ssh_host_key

 

На самом деле ssh-keygen создает два файла для каждого ключа. Первый содержит закрытый и открытый ключи, второй — только открытый. В данном примере первой командой мы создадим сразу /etc/ssh/ssh_host_rsa_key и /etc/ssh/ssh_host_rsa_key.pub. Ясно, что открытый ключ можно выставить на обозрение публики, а закрытый хранить в особо надежном месте (кстати говоря, ssh-keygen сам устанавливает правильные права). Если открытый ключ потеряется, то его можно восстановить:

$ ls -1 /etc/ssh/ssh_host_rsa_key*
/etc/ssh/ssh_host_rsa_key
/etc/ssh/ssh_host_rsa_key.pub

$ cat /etc/ssh/ssh_host_rsa_key.pub
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEApCyGZbDdzrRdszzQUZI+siu3
/mUI57nmjzKwHS7M27AoMZNJ6yIDTn5J3/MVCDJAeyB53LvIFFD9Kzp6P9
fhNhPm8+b0joJ5Wrn+YfUnt2moI3lkAzQUZI+siu3/mUI57nmjzKwH

$ ssh-keygen -y -f /etc/ssh/ssh_host_rsa_key
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEApCyGZbDdzrRdszzQUZI+siu3
/mUI57nmjzKwHS7M27AoMZNJ6yIDTn5J3/MVCDJAeyB53LvIFFD9Kzp6P9
fhNhPm8+b0joJ5Wrn+YfUnt2moI3lkAzQUZI+siu3/mUI57nmjzKwH

 

Типсы

Есть несколько советов, которые могут облегчить вам жизнь в мире SSH.

  • Создайте глобальный файл known_hosts (/etc/ssh/ssh_known_hosts) со всеми ключами с серверов к которым коннектятся юзеры с вашей машины. Это облегчит управление и модификацию ключей. Храните все три типа ключей для всех серверов.
  • Учитывайте различные имена для серверов.
  • Вот скрипт, который коннектится к серваку и записывает все три ключа. Помните, что он их не проверяет, а лишь получает что дают:
    #!/bin/sh
    #
    # add-known-hosts
    # Add all possible SSH keys for the specified hosts to the file
    # specified. It's your responsibility to be sure that the keys
    # found are, in fact, valid.
    #
    # Copyright 2003, Brian Hatch 
    # Released under the GPL
    
    KNOWN_HOSTS=./ssh_known_hosts
    SSH_ARGS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=$KNOWN_HOSTS"
    
    if [ $# -lt 1 ] ; then
    echo "Usage: $0 hostname [hostname ...]" >&2
    exit 1;
    fi
    
    for host in "$@"
    do
    ssh $host $SSH_ARGS -1 echo ''
    ssh $host $SSH_ARGS -o'HostKeyAlgorithms=ssh-rsa' echo ''
    ssh $host $SSH_ARGS -o'HostKeyAlgorithms=ssh-dss' echo ''
    done

 

Подробнее о ключах

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

Теперь расскажу о настройке. Вначале консольный вариант. В комплект пакета OpenSSH поставляется бинарник ssh-keygen, который собственно и занимается генерацией ключей. От тебя требуется указать в параметре -t тип ключа. Их 3 — rsa1, rsa и dsa. Последние два предназначены для второй версии OpenSSH, поэтому их будет юзать гораздо безопаснее. Но стоит упомянуть и об rsa1. После запуска ssh-keygen, ты увидишь примерно следующее:

[forb@ruhost4 forb]$ ssh-keygen -t rsa1 -C 'mykey'
Generating public/private rsa1 key pair.
Enter file in which to save the key (/home/users/forb/.ssh/identity): testkey
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in testkey.
Your public key has been saved in testkey.pub.
The key fingerprint is:
8c:b3:1f:67:ab:01:8c:ae:60:a1:98:39:32:a5:6c:61 mykey

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

scp testkey.pub user@other.server.net:.ssh/authorized_keys
cp testkey ~/.ssh/identity

То бишь, залить публичный ключ в ~/.ssh/authorized_keys на удаленный сервер и скопировать приватный ключ в свой .ssh каталог. На этом все. Первый протокол должен работать по ключам без пароля. Если ключ находится в другом месте используй параметр -i к /usr/bin/ssh, значением которого будет местонахождение приватного ключа.

Для генерации к примеру dsa ключа для второго протокола, используй тип dsa. Вывод будет примерно такой же. Только файлы нужно будет назвать другим именем. На удаленной машине authorized_keys не изменится (несмотря на man, в котором почему-то говорится, что файл должен называться authorized_keys2, но заработало у меня только с authorized_keys). Приватный же ключ сохраняй в ~/.ssh/id_dsa. Затем попробуй коннектится. В удачном случае будет примерно следующее:

ssh other.server.net
Authenticating with public key "dsa-key-20030209"
Last login: Wed Feb 12 18:09:16 2003 from far.server.net

А теперь о ключевых фразах. Если вдруг ты сочтешь небезопасным сохранять открытый доступ к твоему серверу по ключам, ставь фразу на ключ. Она задается в ssh-keygen (смотри выше). Тогда, при соединении, ssh будет спрашивать тебя фразу на данный ключ, и только в случае ее совпадении пропустит тебя.

Если что-то не получается — man ssh-keygen, man ssh, а также параметр -v к /usr/bin/ssh (verbose output — покажет детально, что в данный момент делает бинарник).

В виндовой ssh тоже все просто. Рассмотрим программу PuTTY. С ней в комплекте поставляется exe-шник puttygen.exe, который является полным аналогом ssh-keygen. Выбираем тип ключа, жмем кнопку Generate, водим мышкой по экрану, чтобы рандомайзер смог сгенерировать код, затем сохраняем последовательно публичный и приватный ключ. Внимание: из за кривоватости сохранения публичных ключей, которые у меня не распознала удаленная система, скопируй его из окошка вверху и занеси в ~/.ssh/authorized_keys на сервере. Приватный можешь сохранять как есть. Затем укажи путь к ключу в PuTTY во вкладке SSH->auth и логинься в систему.

Внимание, к ключам справедливы следующие правила:

  1. Ключей в authorized_keys может быть несколько, просто дописывай их в файл, если там уже что-то есть.
  2. Вход по ключам является независимым методом, то есть у пользователя может вообще не быть пароля, но по ключам он сможет авторизоваться.

И ещё 5 шагов, обеспечивающих повышенную безопасность SSH

1. Отключаем root логин.

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

Найдем /etc/ssh/sshd_config (возможно он находиться в другом каталоге, это зависит от дистрибутива). В нем определим место PermitRootLogin и заменим значение на «no»:

PermitRootLogin no

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

2. Запрещаем интерактивный логон.

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

Прочитав все выше написанное и реализовав на практике, мы в результате получим ключи для авторизации на сервере. Убедившись, что все работает,  можно запретить интерактивный ввод:

PasswordAuthentication no 
ChallengeResponseAuthentication no

 

3. Черный список DenyHosts

Используя этот Python-скрипт администратор может автоматические вносить хосты при неудачном логине в черный список, баня их навечно. Простейший способ установки:

europa ~ # emerge -pv denyhosts
These are the packages that would be merged, in order:
Calculating dependencies... done!
[ebuild R ] app-admin/denyhosts-2.5 0 kB
Total size of downloads: 0 kB
europa ~ # emerge denyhosts

 

Документации по программе не очень много (если чего — есть, например FAQ), однако все опции конфигурирования нормально описаны в конфигурационном файле.

europa $ nano -w /etc/denyhosts.conf

Не думаю, что конфигурирование DenyHosts вызовет особые проблемы — достаточно внимательно прочитать конфиг.

После конфигурирования можно запустить программу демоном или через шедулер. В Gentoo демоном:

rc-update add denyhosts default

Через cron, скажем каждые 10 минут:

python /usr/bin/denyhosts -c /etc/denyhosts.conf

Вся радость DenyHost не только в блокировании хостов, пытающихся пробиться к вашему SSH серверу, но и в том, что можно синхронизировать свой «черный список» с серверами DenyHost. Таким образом создается коллективный список хостов, содержащий всех нападающих. Он предотвратит нападение в самом корне.

4. Изменяем номер порта.

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

В конфиге стоит поменять:

Port 22

Вместо или вместе с изменением порта можно использовать «стук в порты» (port knocking). Это еще больше защитит наш SSH сервер.