[Перевод] Гитхаб вас сдаст: идентификация пользователей SSH-серверов

7d0c184058e3d82d64b8ea6b8be8e342

Недавно в своих ежедневных чтениях я наткнулся на явление, о котором думал уже много лет: феномен утечки информации людей, использующих 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-аккаунтом или для поиска других серверов, администрируемых тем же человеком. Советы те же: использовать разные публичные ключи для разных целей.

© Habrahabr.ru