Автор:enze
Редактор:Liz
Введение
Недавно в широко используемой криптобиблиотеке elliptic в экосистеме JavaScript была обнаружена серьезная уязвимость в безопасности (GHSA-vjh7-7g9h-fjfh). Атакующий может, сконструировав определенный вход, извлечь закрытый ключ, подписав всего лишь один раз, полностью контролируя цифровые активы жертвы или удостоверяющие данные личности.
Основная причина этой уязвимости заключается в недостатках обработки нестандартных входных данных в библиотеке elliptic, что приводит к возможному повторению случайного числа k в ECDSA подписи. Поскольку безопасность алгоритма ECDSA крайне зависит от уникальности k, как только k повторяется, закрытый ключ можно будет непосредственно вывести, что приведет к необратимому риску безопасности.
В данной статье будет проанализирована природа уязвимости, ее причины и последствия, а также предложены рекомендации по исправлению, чтобы помочь разработчикам снизить риски безопасности.
Контекст уязвимости
Введение в библиотеку elliptic
Elliptic - это широко используемая библиотека криптографии на эллиптических кривых в экосистеме JavaScript, поддерживающая различные кривые (например, secp256k1, ed25519). Эта библиотека используется множеством криптовалютных кошельков, систем аутентификации, приложений Web3 и других.
Область воздействия
Затронутые версии:elliptic <= 6.6.0
Затронутые кривые:secp256k1, ed25519 и другие
Сценарии воздействия:все приложения, которые принимают внешние входные данные для ECDSA подписей (особенно системы, которые принимают внешние входные данные для подписей)
Если приложение зависит от elliptic для ECDSA подписей и позволяет пользователю предоставлять непроверенные сообщения в качестве содержимого подписи, то оно, скорее всего, будет подвержено этой уязвимости.
Принцип уязвимости
Случайное число k в ECDSA подписи
Представьте, что у вас есть уникальная печать (закрытый ключ), и каждый раз, когда вы подписываете документы, вам нужно использовать уникальную печать (случайное число k). Оставленный след будет генерировать два числа r и s, что и является результатом подписи.
В этом процессе есть несколько ключевых моментов:
Печать (закрытый ключ) всегда неизменна - это представляет вашу личность.
Каждая подпись должна использовать совершенно новую печать (случайное число k никогда не должно повторяться).
Даже если подписать один и тот же документ, результаты подписи (r, s) должны быть разными.
Каждый может проверить, что след был оставлен вашей печатью (публичный ключ проверяет подпись).
Но никто не может вывести вашу печать (закрытый ключ) из следа, при условии, что k безопасно.
Чтобы обеспечить безопасность k, ECDSA использует стандарт RFC 6979, применяя детерминированный генератор случайных чисел (HMAC_DRBG или HKDF) для вычисления k. При генерации k используются закрытый ключ d и сообщение m в качестве семени, чтобы гарантировать фиксированное значение k при одинаковых входных данных. Процесс вычисления следующий:

Ключевые моменты:
1. k должно быть случайным и уникальным, иначе это приведет к утечке закрытого ключа.
2. Если k одинаково при двух подписьах, атакующий может просто восстановить закрытый ключ d с помощью двух подписей (r, s1) и (r, s2).
Математическое обоснование:
Предположим, что пользователь подписал два разных сообщения m1 и m2, но случайное число k оказалось одинаковым, тогда результат подписи будет следующим:

Атакующий может вычислить разницу между s1 и s2:

Поскольку m1, m2, s1 и s2 известны, атакующий может вычислить k:

Затем подставьте k в исходное уравнение подписи, чтобы решить для закрытого ключа d:

Анализ уязвимости
Библиотека elliptic должна использовать HMAC_DRBG (детерминированный генератор случайных чисел), чтобы гарантировать уникальность k для каждой подписи. Однако из-за ошибок в обработке входных данных в некоторых случаях разные сообщения могут генерировать одно и то же k, что приводит к утечке закрытого ключа.
Код генерации k
В методе sign() библиотеки elliptic k генерируется с помощью псевдослучайного генератора HMAC_DRBG, соответствующий код приведен ниже:
elliptic/lib/elliptic/ec/index.js

Входные параметры HMAC_DRBG в основном включают:
entropy (энтропия) → здесь используется закрытый ключ bkey, чтобы гарантировать, что HMAC_DRBG обладает достаточной случайностью.
nonce (случайное число-семя) → вычисляется из msg, влияя на окончательное значение k.
Поскольку HMAC_DRBG является детерминированным, одинаковые entropy и nonce создадут одинаковое k. Если nonce одинаково в разных процессах подписи, то k также будет одинаковым, что приведет к утечке закрытого ключа.
Причина уязвимости:Преобразование BN приводит к повторному использованию nonce
В реализации elliptic msg преобразуется в BN (Big Number), затем nonce вычисляется с помощью msg.toArray():

Проблемные точки:
msg является экземпляром BN, но nonce - это массив.
Разные экземпляры BN могут генерировать один и тот же массив после преобразования.
Это означает, что можно сгенерировать один и тот же nonce для разных сообщений, что в конечном итоге приведет к повторному использованию k.
Если k повторяется для двух разных сообщений msg1 и msg2, то можно восстановить закрытый ключ d с помощью вышеупомянутой математической формулы.
Атакующий может сконструировать специальное сообщение msg, чтобы его nonce оказался одинаковым, что приведет к повторному использованию k. Достаточно получить пару (r, s1) и (r, s2), чтобы восстановить закрытый ключ d с помощью математических расчетов. Более того, атакующий может создать такое злонамеренное msg для любых известных пар сообщений/подписей, что означает, что, подговорив жертву подписать одно злонамеренное сообщение, атакующий может полностью восстановить закрытый ключ и подделать любую подпись.
Эксплуатация уязвимости (PoC)
Библиотека elliptic позволяет использовать шестнадцатеричные строки в качестве одного из типов входных данных, и при подписывании msg преобразуется в экземпляр BN, а затем в массив. Если два разных сообщения после преобразования генерируют один и тот же массив, nonce также будет одинаковым, что в конечном итоге приведет к повторному использованию k.
Пример кода:

Вывод выполнения:

msg1 = "message" и msg2 = "-message" после преобразования в BN генерируют одно и то же nonce (дизайн elliptic принимает шестнадцатеричные строки как один из возможных типов входных данных). Поскольку nonce одинаков, это приводит к тому, что HMAC_DRBG генерирует одно и то же k. Из результатов подписи видно, что значение r полностью идентично, что прямо указывает на повторное использование k.
Полный процесс атаки:
1. Смоделируйте жертву, подписывающую нормальное сообщение, получая сообщение msg0 и подпись sig0.
2. Атакующий создает злонамеренное сообщение msg1 и подговаривает жертву на подпись, получая sig1.
3. Используя уязвимость повторного использования k, вычислите k из (r, s1) и (r, s2).
4. Дальнейшее вычисление закрытого ключа d с использованием k, атака успешна.
Пример кода:

Вывод выполнения:

Рекомендации по исправлению
1. Обновите elliptic до версии 6.6.1+, проблема была официально исправлена.
2. Избегайте прямой подписи непроверенных сообщений, убедитесь, что msg прошел стандартизацию.
3. Заменить возможный затронутый закрытый ключ.
Резюме
Эта уязвимость в elliptic выявила крайнюю чувствительность ECDSA подписей к случайным числам k. Как только k будет повторно использовано, закрытый ключ можно будет непосредственно восстановить, и атакующий сможет полностью контролировать активы и идентичность пользователя. Разработчики должны немедленно исправить уязвимость и строго регулируют обработку входных данных, чтобы обеспечить безопасность ECDSA подписей. Наконец, благодарим кошелек Rabby за предоставление информации о уязвимости.
Ссылки
[1] https://github.com/indutny/elliptic/security/advisories/GHSA-vjh7-7g9h-fjfh
[2] https://paulmillr.com/posts/deterministic-signatures
