По следам SSH

e117dc71a6a44bbdbc068d1677cc9a8b.jpg

В 2015 году поднялась большая шумиха, когда по всему миру на различных узлах были обнаружены одинаковые SSH-отпечатки. Далее шума дело не пошло, но осадок остался. Попробуем разобраться, в чем основная опасность таких «дублей». Большая часть собранных данных актуальна для 2015 года.

Что такое отпечаток


SSH-отпечаток — это число, которое вычисляется от открытого ключа, который хранится по пути /etc/ssh в файле с разрешением pub. Когда вы впервые подключаетесь к узлу, вам предлагается его аутентифицировать. И в качестве валидатора выступает строка вида 56:ca:17:72:0b:d4:3c:fd:5e:23:fb:7b:9e:9a:c8:42 — MD5-сумма от открытого ключа.

The authenticity of host '192.168.100.124 (192.168.100.124)' can't be established.
RSA key fingerprint is 56:ca:17:72:0b:d4:3c:fd:5e:23:fb:7b:9e:9a:c8:42.
Are you sure you want to continue connecting (yes/no)?


Если вы впервые подключаетесь к узлу, то увидеть такое сообщение естественно. Но если вы уже аутентифицировали этот узел и подключались к нему прежде, то стоит задуматься: «Почему произошло изменение отпечатка?». Возможно, вы переустанавливали целевую систему или сгенерировали новый ключ? А может, вы подключаетесь совсем не к той машине, к которой собирались?…

Как считается отпечаток


Итак, SSH-отпечаток — это хеш-сумма. В нашем случае это MD5-сумма от открытой части RSA-ключа.

Открытая часть ключа:

root@ubuntu:/etc/ssh# cat /etc/ssh/ssh_host_rsa_key.pub 

ssh-rsa 

AAAAB3NzaC1yc2EAAAADAQABAAABAQCrID5HFOZiQlq6DDUCsLOG5xJFOMbxtqPTtgL0BfEyRVQ1AGD9kwSWnAU7bm/uFmfkfG5ff/8S02PKaQo26sYIWi8/NyOGMyLNnCLpMJkJ+CT12qrqpD+3Q749DpVzBBbCUaYiDNg7RbKxbbnSZUe9k69P4FE0itS4MQDFAnD0XY78aQuxNpIQUexTIP0b4QuIaShV0c6FXmpHHqr85uZ9t1cTdLtl3Kphv3yu6Z+bkGBd+c80pdV+islTUGa+YJse0rvi/qP8AU67KNXscAc4UDe1yaMG5Y3eUshvt3OTCXliYQKw3NIw/KzXbbY6s/sB49LAvDOal4FK6ZAA+HUP root@ubuntu


Декодируем строку AAAAB....A+HUP из base64 и подсчитаем MD5-сумму получившейся строки:

root@ubuntu:/etc/ssh# awk '{print $2}' ssh_host_rsa_key.pub | base64 -d | md5sum
56ca17720bd43cfd5e23fb7b9e9ac842 


Мы получили исходный отпечаток.

В трафике ключ передается так:

adca5dc291d04cedaafa52be32f34fbe.png

Вместо RSA могут использоваться и другие ключи, например ECDSA, ED25519. При помощи утилиты ssh-keyscan мы можем получить открытую часть ключа SSH сервера целевой машины.

root@ubuntu:/etc/ssh# ssh-keyscan -t ED25519 192.168.100.124
# 192.168.100.124 SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.6
192.168.100.124 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF8GXOsOnWBf1NY6Px6upViTXX0ZOw9txOEjwxMORafZ

root@ubuntu:/etc/ssh# ssh-keyscan -t RSA 192.168.100.124
# 192.168.100.124 SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.6
192.168.100.124 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrID5HFOZiQlq6DDUCsLOG5xJFOMbxtqPTtgL0BfEyRVQ1AGD9kwSWnAU7bm/uFmfkfG5ff/8S02PKaQo26sYIWi8/NyOGMyLNnCLpMJkJ+CT12qrqpD+3Q749DpVzBBbCUaYiDNg7RbKxbbnSZUe9k69P4FE0itS4MQDFAnD0XY78aQuxNpIQUexTIP0b4QuIaShV0c6FXmpHHqr85uZ9t1cTdLtl3Kphv3yu6Z+bkGBd+c80pdV+islTUGa+YJse0rvi/qP8AU67KNXscAc4UDe1yaMG5Y3eUshvt3OTCXliYQKw3NIw/KzXbbY6s/sB49LAvDOal4FK6ZAA+HUP


Также мы можем видеть баннер, который сообщает нам версию сервера, номер протокола и версию ОС: # 192.168.100.124 SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.6.

Поиск одинаковых отпечатков. Сравнение поисков


Сервис shodan.io уже собрал всю необходимую нам статистику. Shodan предлагает искать отпечатки так:

import shodan
api = shodan.Shodan(YOUR_API_KEY)
# Get the top 1,000 duplicated SSH fingerprints
results = api.count('port:22', facets=[('ssh.fingerprint', 1000)])
for facet in results['facets']['ssh.fingerprint']:
print '%s --> %s' % (facet['value'], facet['count'])


Во время анализа отпечатков сервис вел себя нестабильно. Долгое время отсутствовала возможность фильтрации фасетов по стране. Не работала конструкция вида api.count('port:22 country:RU', facets=[('ssh.fingerprint', 20)]). Как следствие, приходилось делать выборку через facets для конкретного отпечатка по топ-странам api.count('e2:40:24:40:b8:87:4e:41:1f:d4:68:69:67:b2:22:5d', facets=[('country', 20)]).

Сравните результаты вывода:

fa = api.count('e2:40:24:40:b8:87:4e:41:1f:d4:68:69:67:b2:22:5d', facets=[('country', 20)])
for i in range(len(fa['facets']['country'])):
        if fa['facets']['country'][i]['value']=='RU': print fa['facets']['country'][i]
{u'count': 60433, u'value': u'RU'}


И

api.count('port:22 country:RU', facets=[('ssh.fingerprint', 10)])['facets']['ssh.fingerprint'][0]
{u'count': 52929, u'value': u'e2:40:24:40:b8:87:4e:41:1f:d4:68:69:67:b2:22:5d'}


Заметная разница в 14%.

Скорее всего, она обусловлено тем, что сервис активно искал баннеры и заносил их в базу, но индексация базы происходила с задержкой.

Также можно искать отпечатки в лоб:

results = api.search('e2:40:24:40:b8:87:4e:41:1f:d4:68:69:67:b2:22:5d')
results['total']


Есть ограничение выборки в 100 записей на страницу, но можно выбирать результаты по страницам:

api.search('e2:40:24:40:b8:87:4e:41:1f:d4:68:69:67:b2:22:5d', page=2)


Интересно посмотреть на статистику распределения ключей по странам.

fp30 = {}
for i in api.count('port:22', facets=[('ssh.fingerprint', 30)])['facets']['ssh.fingerprint']:
        fp={}
        fp['count'] = i['count']
        fp['country']= api.count(i['value'], facets=[('country', 100)])['facets']['country']
        fp30[i['value']]=fp
print fp30


Сравним значения разных годов:

Топ-10 — 2015:

dc:14:de:8e:d7:c1:15:43:23:82:25:81:d2:59:e8:c0, 321014 
32:f9:38:a2:39:d0:c5:f5:ba:bd:b7:75:2b:00:f6:ab, 245499 
d0:db:8a:cb:74:c8:37:e4:9e:71:fc:7a:eb:d6:40:81, 161471 
34:47:0f:e9:1a:c2:eb:56:eb:cc:58:59:3a:02:80:b6, 149775 
df:17:d6:57:7a:37:00:7a:87:5e:4e:ed:2f:a3:d5:dd, 105345 
81:96:a6:8c:3a:75:f3:be:84:5e:cc:99:a7:ab:3e:d9, 97778 
7c:a8:25:21:13:a2:eb:00:a6:c1:76:ca:6b:48:6e:bf, 93686 
c2:77:c8:c5:72:17:e2:5b:4f:a2:4e:e3:04:0c:35:c9, 88393 
1c:1e:29:43:d2:0c:c1:75:40:05:30:03:d4:02:d7:9b, 87218
03:56:e6:52:ee:d2:da:f0:73:b5:df:3d:09:08:54:b7, 64379


Топ-2016:

e7:86:c7:22:b3:08:af:c7:11:fb:a5:ff:9a:ae:38:e4, 343048
34:47:0f:e9:1a:c2:eb:56:eb:cc:58:59:3a:02:80:b6, 138495
dc:14:de:8e:d7:c1:15:43:23:82:25:81:d2:59:e8:c0, 109869
32:f9:38:a2:39:d0:c5:f5:ba:bd:b7:75:2b:00:f6:ab, 46451
62:5e:b9:fd:3a:70:eb:37:99:e9:12:e3:d9:3f:4e:6c, 41578
d0:db:8a:cb:74:c8:37:e4:9e:71:fc:7a:eb:d6:40:81, 39126
7c:a8:25:21:13:a2:eb:00:a6:c1:76:ca:6b:48:6e:bf, 38816
8b:75:88:08:41:78:11:5b:49:68:11:42:64:12:6d:49, 34203
1c:1e:29:43:d2:0c:c1:75:40:05:30:03:d4:02:d7:9b, 32621
03:56:e6:52:ee:d2:da:f0:73:b5:df:3d:09:08:54:b7, 29249
c2:77:c8:c5:72:17:e2:5b:4f:a2:4e:e3:04:0c:35:c9, 28736
59:af:97:23:de:61:51:5a:43:16:c3:6c:47:5c:11:ee, 25110
7c:3e:bc:b9:4b:0d:29:91:ed:bd:6e:4c:6b:60:49:14, 22367


Как видим, некоторые отпечатки стали встречаться реже, а какие-то, наоборот, чаще.

Карта отпечатков


Далее выведем на экран ключи и их статистику. Статистику собираем по 30 самым частым странам. Она содержит название страны в формате iso alpha2 (2-буквенное обозначение) и долю совпадений в общем числе нахождений отпечатка.

for i in fp30:
        print i, fp30[i]['count']
        sum = fp30[i]['count']
        for j in fp30[i]['country']:
                if 100*j['count']/sum > 0: print '%s: %s' % (j['value'], 100.0*j['count']/sum)


Да, 146% процентов может получиться из-за того, что данные в базе неполностью индексированы.

За 2015 год:

dc:14:de:8e:d7:c1:15:43:23:82:25:81:d2:59:e8:c0 332493
ES: 90.0605953479
TW: 3.56833133558
US: 2.1252561631
http://chartsbin.com/view/32232


chartsbin.com/view/32232

32:f9:38:a2:39:d0:c5:f5:ba:bd:b7:75:2b:00:f6:ab 254856
CN: 54.5263608791
TW: 41.3041225361
DO: 1.22736474116
US: 1.18763860965

d0:db:8a:cb:74:c8:37:e4:9e:71:fc:7a:eb:d6:40:81 162800
US: 54.9035226422
JP: 45.0382223913

34:47:0f:e9:1a:c2:eb:56:eb:cc:58:59:3a:02:80:b6 151027
DE: 69.7611572028
US: 27.9946735249
ES: 1.41647682396

df:17:d6:57:7a:37:00:7a:87:5e:4e:ed:2f:a3:d5:dd 108057
CN: 99.7404030473


chartsbin.com/view/32227

81:96:a6:8c:3a:75:f3:be:84:5e:cc:99:a7:ab:3e:d9 101156
TW: 100.0

8b:75:88:08:41:78:11:5b:49:68:11:42:64:12:6d:49 75760
PL: 100.0


chartsbin.com/view/32225

57:94:42:63:a1:91:0b:58:a6:33:cb:db:fe:b5:83:38 39167
IN: 38.2145131455
AU: 9.01840676835
US: 8.73335961428
TR: 6.34381538648
AE: 4.14531340025
ZA: 3.3538526852
SA: 3.15977802711
MX: 3.0384813658
GB: 2.80498529278
FR: 2.56542438669
IR: 2.5199381387
IT: 2.3440579798
TH: 2.32889589714
DE: 2.31676623101
BR: 2.19243715317
MY: 1.98623282894
NG: 1.47678685144
KE: 1.46465718531
TW: 1.14625344937

chartsbin.com/view/32196
За 2016 год:

e7:86:c7:22:b3:08:af:c7:11:fb:a5:ff:9a:ae:38:e4 343048
US: 99.9988339824

34:47:0f:e9:1a:c2:eb:56:eb:cc:58:59:3a:02:80:b6 138495
DE: 54.827972129
US: 42.5546048594
GB: 1.33795443879
ES: 1.27946857287

dc:14:de:8e:d7:c1:15:43:23:82:25:81:d2:59:e8:c0 109869
ES: 88.2241578607
TW: 4.07485277922
US: 3.3376111551
DK: 1.1104133104
VC: 1.0594435191

32:f9:38:a2:39:d0:c5:f5:ba:bd:b7:75:2b:00:f6:ab 46451
CN: 49.5188478181
TW: 44.5932272717
DO: 1.59738218768
US: 1.22494671805

62:5e:b9:fd:3a:70:eb:37:99:e9:12:e3:d9:3f:4e:6c 41578
US: 84.3907835875
SG: 9.02881331473
NL: 6.58521333397


Можно заметить, что есть отпечатки, которые встречаются только в одной стране или почти только в одной (90%).

Например:

81:96:a6:8c:3a:75:f3:be:84:5e:cc:99:a7:ab:3e:d9 TW: 100.0%
8b:75:88:08:41:78:11:5b:49:68:11:42:64:12:6d:49 PL: 100.0%
df:17:d6:57:7a:37:00:7a:87:5e:4e:ed:2f:a3:d5:dd CN: 99.7404030473%
59:af:97:23:de:61:51:5a:43:16:c3:6c:47:5c:11:ee US: 99.9953928728%
c2:52:47:0f:8b:82:b9:3c:74:ee:64:b5:35:f4:c5:c3 MY: 99.7626425793%


Возьмем, например, Польшу:

8b:75:88:08:41:78:11:5b:49:68:11:42:64:12:6d:49 PL: 100.0%

Статистика по баннерам, в которых присутствует отпечаток:

[('SSH-2.0-OpenSSH_5.9p1 Debian-8netart1\r\n', 37188), ('SSH-2.0-OpenSSH_6.2p2 Ubuntu-7netart1\r\n', 10390), ('SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-15netart2\nKey type: ssh-rsa\nKey: AAAAB3NzaC1yc2EAAAADAQABAAABAQCnt2+LOdS1Gy/47UXMfHDYQERQQR5M4/CYsfT7IE3FYQ/m\nwJO6rLKLcUo+q4U+0iIH6uBSXG5HNa4569rg2eWH5lUiJHEL1pPIA9wKKZ+MpMoE9nkr1xaXxVK5\nqO1gUfaYCo+VYre2CJDe3HIJlUht3PITdxmQTwnL/tJHHBkR8xrgEpjF+9FjFKwdE7ZCNObqvhK0\nPio/318DyUiRK/JaIqggL0K9KzoGytq7uKSkECFMYCDTqPmdDerCEiT+C5Lxy6ZOdp4yTyxjOM7E\nsr0C/ePzPvT8rCLayz3GzBnEwZ4QKlOxbZHl/48LxtWlY/vROkiLTuU3kcpFqvo0Uc/3\nFingerprint: 8b:75:88:08:41:78:11:5b:49:68:11:42:64:12:6d:49', 3421), ('SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-15netart2\nKey type: ssh-rsa\nKey: AAAAB3NzaC1yc2EAAAADAQABAAABAQCnt2+L', 3421), ('SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-15netart2\r\n', 2)]


Судя по торговой марке NetArt, это, скорее всего, nazwa.pl — польский хостинг.
Статистика для отпечатка dc:14:de:8e:d7:c1:15:43:23:82:25:81:d2:59:e8:c0 показана в исходной статье. Это предустановленный ключ SSH-сервера Dropbear v0.46. Очень старый уязвимый SSH-сервер. Количество устройств с этим ключом до сих пор очень велико.

Статистика по России за 2015 год:

e2:40:24:40:b8:87:4e:41:1f:d4:68:69:67:b2:22:5d 50107 {'Dropbear sshd_0.46': 50107} 
-------------------------------------
OJSC Rostelecom 49794
OJSC Rostelecom, Vladimir branch 160
OJSC RTComm.RU 46
OJSC Bashinformsvyaz 32
CJSC ER-Telecom Holding 11

1c:1e:29:43:d2:0c:c1:75:40:05:30:03:d4:02:d7:9b 26286 {'Dropbear sshd_0.52': 26286} 
-------------------------------------
OJSC Rostelecom 19596
OJSC Bashinformsvyaz 1025
MTS OJSC 1024
CJSC Teleset-Service 645
VimpelCom 340

2d:7b:35:e5:33:66:d5:ee:0d:58:19:cb:ae:e7:90:ea 24036 {'Dropbear sshd_0.53.1': 23413, 'Dropbear sshd_0.28': 623}
-------------------------------------
National Cable Networks 14860
OJSC Rostelecom 8179
VimpelCom 214
Net By Net Holding LLC 101
CJSC ER-Telecom Holding 94

f5:50:8d:ca:f7:5a:07:41:08:81:65:2e:b3:a4:d6:48 14065 {'Dropbear sshd_2011.54': 14065}
-------------------------------------
Net By Net Holding LLC 13923
OJSC Central telegraph 73
Optilink Ltd 29
Web Plus ZAO 23
Iskratelecom CJSC 10


Выводы


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

Так чем опасны «дубли»? Допустим, злоумышленник скомпрометировал ключ, и теперь он знает соответствующий данному открытому ключу закрытый ключ. Вендорам оборудования и хостерам этот ключ уже известен, так как они причастны к его выпуску. В этом случае можно провести MitM-атаку, например ARP Spoofingили DNS Spoofing. Злоумышленник подменяет исходный сервер своим и ждет соединения, при этом жертва не получает сообщения о недоверенном сервере. Таким образом злоумышленник в состоянии узнать пароль жертвы.

Потенциальные жертвы подобной атаки — все пользователи предустановленного ПО. Например, готовых сборок, таких как Bitnami и TurnKey. Казалось бы, достаточно просто сменить пароль…, но не все так просто. Не все меняют предустановленные пароли, что же говорить о выпуске новых ключей?

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

Будьте внимательны, вовремя обновляйте ПО.

Автор: Артур Гарипов, Positive Technologies

© Habrahabr.ru