Недавно злоумышленники использовали npm-пакет eslint-scope для кражи npm-токенов из домашних каталогов пользователей. В свете этого события мы занялись проверкой других подобных уязвимостей и задумались над тем, как снизить риски и последствия таких инцидентов.
У большинства из нас под рукой есть RSA SSH-ключ. Такой ключ наделяет владельца самыми разными привилегиями: как правило, он используется для доступа к production среде или в GitHub. В отличие от nmp-токенов SSH-ключи зашифрованы, и поэтому принято считать, что ничего страшного не произойдет, даже если они попадут не в те руки. Но так ли это на самом деле? Давайте узнаем.
user@work /tmp $ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): mykey
...
user@work /tmp $ head -n 5 mykey
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,CB973D5520E952B8D5A6B86716C6223F
+5ZVNE65kl8kwZ808e4+Y7Pr8IFstgoArpZJ/bkOs7rB9eAfYrx2CLBqLATk1RT/
Этот ключ зашифрован, о чем гласит одна из первых строк файла. Кроме того, в начале нет MII — base64-ключа кодирования, используемого в RSA. И, конечно же, в глаза бросается AES! Это ведь хорошо, так? И CBC, на первый взгляд, со случайным вектором инициализации. Кода аутентификации (MAC’а) нет. Ну и ладно, зато не будет никакой padding oracle атаки, верно?
Узнать, что на деле означает содержимое DEK-Info, не так-то просто. Поиск по ключевому слову «DEK-Info» в репозитории openssh-portable показывает только примеры ключей. Но суть здесь в том, что AES-ключ есть не что иное, как простой MD5-хеш (пароль || вектор инициализации [:8]). И это скверно, ведь передовые практики хранения паролей гласят, что пароли в чистом виде в силу своей низкой энтропии представляют собой плохой материал для шифрования. И, чтобы сделать его лучше, нужна затратная функция вроде Argon2. Но MD5, в отличие от последнего, легко поддается вычислению.
Единственный положительный момент в этой схеме в том, что соль помещается после пароля, поэтому вычислить промежуточное состояние MD5(IV[8:]) и подобрать пароли на его основании не получится. Но это слабое утешение, особенно в эпоху, когда нам доступны машины, выполняющие миллиарды вызовов MD5 в секунду, — больше, чем можно придумать паролей.
У вас может возникнуть вопрос, как OpenSSH дожил до такого. Увы, ответ прост: инструмент командной строки OpenSSL изначально использовал эту схему по умолчанию, и она попросту стала нормой.
В итоге справедливым становится замечание, что стандартные, зашифрованные паролем ключи ничем не лучше обычных незашифрованных просто потому, что механизм шифрования неэффективен. Однако мы высказались бы еще смелее — они хуже. И аргументировать это просто.
Для хранения пароля от SSH-ключа многие едва ли будут пользоваться менеджером паролей. Скорее пользователь просто запомнит его. И, поскольку это одна из выученных на память комбинаций, велика вероятность, что пользователь уже применял ее где-либо еще. Возможно, она даже совпадает с пользовательским паролем от устройства. Угадать его вполне реально (слишком ненадежная у него формирующая функция), и если пароль станет известен, можно проверить его наверняка по публичному ключу.
К самой паре RSA-ключей никаких претензий нет: весь вопрос лишь в методах симметричного шифрования приватного ключа. Провести описанную выше атаку, зная один лишь открытый ключ, нельзя.
Как можно исправить ситуацию?
В OpenSSH предусмотрен новый формат ключей, которым следует пользоваться. Под новым имеется в виду введенный в 2013 году. Этот формат использует bcrypt_pbkdf, по существу представляющий собой bcrypt с фиксированной сложностью, реализованный в рамках стандарта PBKDF2.
Удобно то, что вы автоматически получаете ключ нового формата при генерации Ed25519 ключей, поскольку старый формат SSH-ключа не поддерживает более новые типы ключей. Это довольно странно, ведь на самом деле нам вовсе не нужно, чтобы формат ключа определял, как работает Ed25519-сериализация, поскольку Ed25519 сам по себе задает работу сериализации. Но если уж нужна хорошая формирующая функция, то можно не заморачиваться с такими мелочами. В итоге один из ответов — ssh-keygen -t ed25519.
Если по соображениям совместимости необходимо придерживаться RSA, можно воспользоваться ssh-keygen -o. Таким образом, можно получить новый формат даже для старых типов ключей. Обновить старые ключи можно с помощью команды ssh-keygen -p -o -f имя ключа. Если ваши ключи живут на Yubikey или смарт-картах, то там эти поправки уже учтены.
Так или иначе, мы стремимся к более оптимальному выходу. С одной стороны, есть хороший пример aws-vault, в которой информация об учетных данных была перемещена с диска в связки ключей. Есть и иной подход: перемещение разработки в разделенные окружения. И, наконец, большинству стартапов следует рассмотреть отказ от длительного хранения SSH-ключей и переход на центр SSH-сертификации с ограниченным временем хранения ключей вкупе с системой единого входа. К сожалению, в случае с GitHub такой подход невозможен.
P. S. Трудно проверить эту информацию в авторитетном источнике, но, если нам не изменяет память, версионный параметр в приватных ключах OpenSSH формата PEM влияет только на способ шифрования. Однако это не играет никакой роли: проблема заключается в формирующей ключ функции, и, думаем, это еще один аргумент против обсуждения протоколов по частям. На эту тему будет отдельный пост в нашем блоге.
И напоследок — ссылка на полный ключ. Это на случай, если сегодня вы настроены что-нибудь взломать.
Комментарии (20)
lassana
11.08.2018 01:19+1Это уже было
в Сипсонах: habr.com/post/181320/#comment_9852056 Стоит всё же отметить, что даже в 2018 не все приложения поддерживают новый формат приватного ключа.
Sly_tom_cat
11.08.2018 01:22+2Автор мог бы пояснить чем именно «слабость» MD5 делает «Шифрование ключа по умолчанию в OpenSSH хуже его отсутствия»?
Ну можете вы быстро считать MD5 и что?
Вам же пароль надо брутфорсить, а нужную (для правильной расшифровки) MD5 вы же не можете получить.
Что вам толку от такого каким хешем пароль в ключ преобразуется? Вам же пароль надо подбирать.
И тут уже не важно как быстро считается хеш. Если вы получили шифрованный приватный ключ, то дальше вам нужно брутфорсить пароль и медленнее он в ключ преобразовывается или быстрее — роль играет незначительную. Ну будете вы три дня пароль подбирать вместо пары часов — велика ли разница?
Но вот заявлять, что отсутствие шифрования секретного ключа лучше чем шифрование (не важно какое) это чистейшей воды профанация.powerman
11.08.2018 02:07Ну будете вы три дня пароль подбирать вместо пары часов — велика ли разница?
На самом деле разница довольно велика — у Argon2 скорость порядка 100/сек, а у MD5 порядка 10-100 миллиардов/сек. Это разница в 9 порядков, превращающая 1-секундный взлом MD5 в 30 лет.
kITerE
11.08.2018 09:38Автор мог бы пояснить чем именно «слабость» MD5 делает «Шифрование ключа по умолчанию в OpenSSH хуже его отсутствия»?
По тексту автор утверждает, что есть хороший шанс восстановить пароль шифрования в открытом виде:
И, поскольку это одна из выученных на память комбинаций, велика вероятность, что пользователь уже применял ее где-либо еще. Возможно, она даже совпадает с пользовательским паролем от устройства. Угадать его вполне реально (слишком ненадежная у него формирующая функция), и если пароль станет известен, можно проверить его наверняка по публичному ключу.
Что приведет к тому, что будет скомпрометирован не только ключ SSH, о и другие аккаунты, которые имеют тот же пароль.
firk
11.08.2018 10:02+1Если у вас утянули с компа ключ — то скомпрометирован весь комп, следует подозревать наличие на нём кейлоггера и прочего подобного софта, который так же украл у вас и все пароли. Так что разницы почти нет.
powerman
11.08.2018 13:01В этом есть доля истины, но на практике не зря используют многоуровневые защиты — если пробили один уровень это даёт какой-то доступ, но вовсе не обязательно полный. Если каким-то образом получили возможность читать любые файлы юзера (включая файлы в ~/.ssh/), это ещё не означает возможность запустить кейлоггер.
arheops
11.08.2018 15:20Разница есть, поскольку не все пароли вы можете/захотите поменять после компроментации ключа
И это еще не рассматриваем вариант, когда вы не знаете о краже.
Taras-proger
12.08.2018 08:57А зачем запоминать пароль от зашифрованного ключа? Не проще ли запомнить сразу сам ключ? Вот представьте себе: у вас на лестничной площадке стоит сейф, в котором вы храните ключ от квартиры, сейф открывается другим ключом и этот ключ вы носите с собой. Нафига?
dartraiden
В качестве решения: ключ можно положить в KeePass (где шифрование реализовано на должном уровне, в том числе использутеся Argon2), а плагин KeeFox будет выступать в качестве агента. И автоматически расшифровывать ключ, что избавляет от необходимости делать пароль лёгким для запоминания. Убивается сразу два зайца: пароль от ключа можно сделать сложным, а сам ключ надёжно защищён, даже если при шифровании ключа и использовался MD5-хэш, и хранится в нестандартном месте.
У тебя не украдут ключ, если он не лежит в /home/.ssh/
powerman
Мне никогда не нравилась идея открыть доступ к KeePass сторонним приложениям (вроде KeeFox). Начиная с того, что они работают с KeePass по сети (пусть даже через localhost или unix-сокет, но это значит что любое приложение запущенное на этой машине тоже может подключиться), и заканчивая тем, что пользователь теряет контроль над тем, когда и какие именно пароли запрашиваются из KeePass.
Я предпочитаю чтобы плагин в браузере просто изменял заголовок окна браузера так, чтобы KeePass мог сам автоматически подобрать подходящую запись и передать пароль в окно браузера самостоятельно. Да тоже не фонтан, текущая страница может попробовать подменить заголовок окна именно в тот момент, когда я активирую горячей кнопкой KeePass (чтобы получить пароль от другого сайта), плюс иногда передаваемые KeePass символы пароля попадают не в строку ввода пароля, а куда попало. Но, всё-равно, как по мне этот способ безопаснее.
aim
но если говорить о ssh сам keepassxc умеет быть ssh-agent.
powerman
Ну, как я понял из доки, не столько быть ssh-agent, сколько управлять им — добавлять в него ключи из базы и удалять их по таймауту/закрытию базы. В принципе, это выглядит неплохой идеей (правда, только для десктопа).
dartraiden
Извиняюсь, вместо KeeFox там должно быть KeeAgent. KeeFox это о другом.
Daniyar94
Зашифровать зашифрованный ключ который защищён паролем
We need to go deeper
herrjemand
Приватный ассиметричный ключ который симметрично зашифрован с использованием в качестве ключа пароль.
Password -> AES(RSAPrivKey) -> encrypted key
dartraiden
Поправка, вместо KeeFox читать KeeAgent (как обычно, думаешь о другом и на автомате это пишешь...)