[Перевод] Гитхаб вас сдаст: идентификация пользователей SSH-серверов
Недавно в своих ежедневных чтениях я наткнулся на явление, о котором думал уже много лет: феномен утечки информации людей, использующих SSH. Этот тип утечки информации не является новым явлением. Я давно предупреждал об этой опасности своих друзей, использующих SSH, но мой голос услышали лишь несколько человек. Поэтому я решил дать пояснения по этому поводу, потому что я считаю, что необходимо понимать этот риск в ИТ-сообществе, особенно в нынешней ситуации. Я буду исходить из предположения, что у вас, дорогой читатель, есть опыт работы с SSH.
Асимметричные ключи SSH
Протокол SSH использует асимметричную криптографию. Короче говоря, для шифрования связи и клиент и сервер каждый имеют открытый ключ и закрытый ключ, при установлении соединения они обмениваются друг с другом только открытым ключом, чтобы информация каждой стороны шифровалась с его помощью, а после обмена данные будут расшифрованы с помощью закрытого ключа. SSH использует тот же метод и для так называемой «авторизации по ключам». Существует распространенное мнение, что этот метод более безопасен, чем аутентификация по паролю, и это, безусловно, правда.
Чтобы подключиться, клиент должен сначала отправить открытый ключ на хост на другой стороне, которым обычно является SSH-сервер или Git.
Github, Gitlab и открытые ключи
В предыдущем абзаце я говорил о подключении к серверам Git через SSH. GitHub — один из нескольких известных сервисов Git, у которого много пользователей из нашей страны. GitHub позволяет пользователям работать с Git, используя публичные ключи SSH, но проблема в том, что такие сервера, как GitHub, публикуют ключи своих пользователей… в открытом виде. Вы можете увидеть один аспект этого увлекательного, но вредного действия GitHub, перейдя по ссылке ниже и подставив вместо «username» свое имя пользователя:
https://github.com/username.keys
Злоумышленники или работники силовых органов, имеющие какие-то намерения против своих граждан, могут просто просканировать эти SSH-ключи и создать базу данных аккаунтов и ключей, а дальше устанавливать совпадения с открытыми ключами, обнаруженными в других местах, и использовать их для идентификации граждан. Например, вы можете проверить наличие вашего ключа SSH на GitHub с помощью команды (ssh git@github.com
), и если такой ключ существует, вы увидите свое имя пользователя GitHub в ответе сервера.
Короче говоря, если у вас действительно есть SSH-ключ, зарегистрированный в GitHub, вам нужно принять за данность, что все ваши SSH-ключи GitHub вполне возможно уже просканированы и хранятся в какой-то базе данных.
Опасность сопоставления открытых ключей
Чтобы понять, как утечка открытого ключа пользователей SSH ставит под угрозу конфиденциальность, рассмотрим следующий эксперимент.
У меня есть два ключа SSH и сервер, который принимает от меня только один ключ. Теперь я пытаюсь подключиться к серверу с обоими ключами, а затем сравниваю логи клиента (с флагом verbose). Обратите внимание, что я удалил некоторые строки вывода из-за большого количества логов.
$ ssh -v -o "IdentitiesOnly=yes" -i ~/.ssh/id_ed25519 root@10.2.10.5
OpenSSH_9.1p1, OpenSSL 3.0.7 1 Nov 2022
debug1: Connecting to 10.2.10.5 [10.2.10.5] port 22.
debug1: Connection established.
debug1: Authenticating to 10.2.10.5:22 as 'root'
debug1: Host '10.2.10.5' is known and matches the ED25519 host key.
debug1: Will attempt key: /home/mark/.ssh/id_ed25519
debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: publickey
debug1: Offering public key: /home/mark/.ssh/id_ed25519
debug1: Server accepts key: /home/mark/.ssh/id_ed25519
Authenticated to 10.2.10.5 ([10.2.10.5]:22) using "publickey".
$ ssh -v -o "IdentitiesOnly=yes" -i ~/.ssh/id_rsa root@10.2.10.5
OpenSSH_9.1p1, OpenSSL 3.0.7 1 Nov 2022
debug1: Connecting to 10.2.10.5 [10.2.10.5] port 22.
debug1: Connection established.
debug1: Authenticating to 10.2.10.5:22 as 'root'
debug1: Host '10.2.10.5' is known and matches the ED25519 host key.
debug1: Will attempt key: /home/mark/.ssh/id_rsa
debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: publickey
debug1: Offering public key: /home/mark/.ssh/id_rsa
debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: keyboard-interactive
(root@10.2.10.5) Password:
Приведенный выше пример показывает, что в первом случае клиент предлагает серверу свой открытый ключ, сервер его принимает. Но во втором логе публичный ключ не принимается. Но вопрос в том, реально ли с помощью одного только публичного ключа, но не имея приватного, определить, разрешено пользователю, обладающим этим ключу подключение к серверу или нет? Обязательно ли для такой проверки иметь и приватный ключ? Кажется, что да, но давайте проверим доказательство этой гипотезы.
Для проверки я написал короткий код и попробовал использовать открытый ключ на целевом сервере, без использования приватного.
package main
import (
"fmt"
"io"
"golang.org/x/crypto/ssh"
)
const (
username = "root"
server = "10.2.10.5:22"
publicKey = "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
)
func main() {
parsed, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKey))
if err != nil {
panic(err)
}
signer := &DummySigner{PubKey: parsed}
authMethod := []ssh.AuthMethod{ssh.PublicKeysCallback(
func() ([]ssh.Signer, error) {
return []ssh.Signer{signer}, nil
},
)}
config := &ssh.ClientConfig{
User: username,
Auth: authMethod,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
_, _ = ssh.Dial("tcp", server, config)
if signer.Accepted {
fmt.Println("Public key was accepted by server")
return
}
fmt.Println("Public key was rejected by server")
}
type DummySigner struct {
PubKey ssh.PublicKey
Accepted bool
}
func (signer *DummySigner) PublicKey() ssh.PublicKey {
return signer.PubKey
}
func (signer *DummySigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
signer.Accepted = true
return &ssh.Signature{Format: signer.PubKey.Type()}, nil
}
Результат:
Public key was accepted by server
Как и ожидалось, все сработало.
Что в итоге?
Мы увидели, что с помощью нескольких строк кода можно протестировать, разрешен ли человеку с определенным публичным SSH-ключом (даже не имея его приватного ключа) доступ на сервер. Соответственно, тестирование того или иного ключа на большом количестве IP-адресов не должно стать проблемой. С помощью этого метода, используя данные из открытых источников, можно легко получить связки «юзернеймы — открытые ключи», а потом идентифицировать сервера, которые этим юзерам принадлежат.
Речь идет не о взломе сервера или учетной записи GitHub, а о сборе общедоступной информации о людях и организациях. Поэтому имейте в виду, что раз ваш публичный ключ светится в открытом доступе где-нибудь на Github, и вы установили этот же ключ для пользователя root или угадываемого имени пользователя для входа в систему на вашем сервере, то сопоставить эти два факта будет довольно легко. Я рекомендую использовать два разных публичных ключа для действий, связанных с Git, и для вашего сервера, и не публиковать нигде даже публичные ключи, используемые на сервере.
Использованная литература:
https://words.filippo.io/dispatches/whoami-updated
https://www.agwa.name/blog/post/whoarethey
От переводчика: теоретически, перехват публичных ключей при SSH-подключении так же довольно легко сделать простой MitM-прослушкой, после чего точно так же использовать их для сопоставления с Github-аккаунтом или для поиска других серверов, администрируемых тем же человеком. Советы те же: использовать разные публичные ключи для разных целей.