Хакер - Электронная психобумага. Критическая уязвимость в Java позволяет подделывать электронные подписи и ключи
hacker_frei
Валентин Холмогоров
Содержание статьи
- Подписи ECDSA
- Немного технических деталей
- Почему уязвимость обнаружили только сейчас?
- Что теперь делать?
Всех, кто использует относительно новые версии Java-фреймворка Oracle, в минувшую среду ждал неприятный сюрприз. Исследователь из компании ForgeRock Нил Мэдден сообщил о критической уязвимости в Java, которая позволяет злоумышленникам легко подделывать сертификаты и подписи TLS, сообщения двухфакторной аутентификации и учетные данные авторизации. Эксперт опубликовал в сети подробное описание уязвимости, с основными тезисами которого мы сегодня хотим тебя познакомить.
Хронология событий
Нил Мэдден обнаружил эту ошибку в OpenJDK еще 11 ноября 2021 года и сразу же сообщил о ней в Oracle. 18 ноября разработчик подтвердил наличие проблемы и пообещал добавить исправление в следующее критическое обновление безопасности, которое вышло 19 апреля 2022 года. В этот же день ForgeRock опубликовал отчет с описанием уязвимости.
В популярном британском телесериале «Доктор Кто» есть повторяющийся сюжет: главный герой с успехом выпутывается из различных неприятностей, показывая окружающим совершенно пустой документ, изготовленный из специальной «психобумаги». Эта бумага заставляет смотрящего на нее человека видеть то, что хочет продемонстрировать ему владелец артефакта: пропуск, удостоверение полицейского, судебный ордер или что‑то иное. Схожим образом работает уязвимость, обнаруженная в нескольких недавних выпусках Java, вернее, в механизме широко используемого алгоритма с открытым ключом для создания цифровых подписей ECDSA. Этой уязвимости, получившей обозначение CVE-2022-21449, подвержены версии Java 15, 16, 17 и 18, вышедшие до критического обновления от апреля 2022 года. Кроме того, в официальном сообщении Oracle также упоминаются более старые версии Java, включая 7, 8 и 11. С другой стороны, в рекомендациях OpenJDK перечислены только версии 15, 17 и 18, затронутые этой конкретной уязвимостью.

С использованием CVE-2022-21449 злоумышленники могут легко подделать некоторые типы SSL-сертификатов и SSL-рукопожатий, что, в свою очередь, позволяет перехватывать и изменять сообщения. Кроме того, становится возможной подмена подписанных JSON Web Tokens (JWT), данных SAML, токенов идентификации OIDC и даже сообщений аутентификации WebAuthn. Фактически это и есть полный аналог киношной «психобумаги», только в электронной форме.
Серьезность этой проблемы трудно недооценить. Если ты используешь подписи ECDSA для любого из перечисленных механизмов безопасности, а на сервере установлена уязвимая версия Java, злоумышленник может без труда обойти эти механизмы. В реальности почти все устройства WebAuthn/FIDO (включая Yubikeys) используют подписи ECDSA, а многие поставщики OIDC — токены JWT, подписанные тем же методом.
Oracle присвоила этому CVSS оценку 7,5 балла, посчитав, что уязвимость не оказывает серьезного влияния на конфиденциальность или доступность данных, однако исследователи из ForgeRock оценили проблему в 10 баллов из‑за широкого спектра воздействий на различные функции в контексте управления доступом. Как же все‑таки работает уязвимость CVE-2022-21449? Чтобы разобраться, необходимо немного углубиться в теорию.
ПОДПИСИ ECDSA
ECDSA расшифровывается как алгоритм цифровой подписи на эллиптических кривых (Elliptic Curve Digital Signature Algorithm) и широко используется в качестве стандарта для подписи всех видов цифровых документов. По сравнению со старым стандартом RSA ключи и подписи на основе эллиптической криптографии имеют намного меньшие размеры в байтах, но при этом обеспечивают эквивалентную безопасность, в результате чего они применяются в тех случаях, когда размер имеет большое значение. Например, стандарт WebAuthn для двухфакторной аутентификации позволяет производителям устройств выбирать из широкого спектра алгоритмов создания подписи, но на практике почти все произведенные на сегодняшний день устройства поддерживают только ECDSA (заметным исключением является разве что Windows Hello, которая использует RSA, предположительно для совместимости со старым оборудованием TPM).
Не вдаваясь в технические детали, можно сказать, что подпись ECDSA состоит из двух значений, называемых r и s. Чтобы проверить такую подпись, верификатор решает уравнение, включающее значения r, s, открытый ключ подписавшего и хеш сообщения. Если две части уравнения равны, подпись считается действительной, в противном случае она отклоняется.
Одна часть уравнения должна быть равна r, а другая часть умножается на r и значение, полученное из s. Очевидно, было бы очень плохо, если бы r и s оказались равны 0, потому что тогда мы проверяли бы равенство 0 = 0 ⨉ [куча вещей], которое будет истинным независимо от значения «кучи вещей». Притом что эта самая «куча вещей» — важные данные, такие как сообщение и открытый ключ. Вот почему самая первая проверка в алгоритме ECDSA выполняется с целью удостовериться, что значения r и s >= 1.
Догадайся, какую проверку забыли в Java? Бинго: валидатор подписи ECDSA в Java не проверял, равны ли r или s нулю, поэтому ты при желании можешь создать подпись с нулевыми значениями этих параметров. Тогда Java примет такую подпись для любого сообщения или публичного ключа как действительную.
Вот интерактивный сеанс JShell, показывающий реализацию этой уязвимости, — здесь используется абсолютно пустая подпись, которая принимается в качестве действительной:
| Welcome to JShell -- Version 17.0.1
| For an introduction type: /help intro
jshell> import java.security.*
jshell> var keys = KeyPairGenerator.getInstance("EC").generateKeyPair()
keys ==> java.security.KeyPair@626b2d4a
jshell> var blankSignature = new byte[64]
blankSignature ==> byte[64] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... , 0, 0, 0, 0, 0, 0, 0, 0 }
jshell> var sig = Signature.getInstance("SHA256WithECDSAInP1363Format")
sig ==> Signature object: SHA256WithECDSAInP1363Format<not initialized>
jshell> sig.initVerify(keys.getPublic())
jshell> sig.update("Hello, World".getBytes())
jshell> sig.verify(blankSignature)
$8 ==> true
// Oops, that shouldn't have verified...
Квалификатор InP1363Format упрощает демонстрацию ошибки. Подписи в формате ASN.1 DER могут использоваться таким же образом: просто сначала нужно немного повозиться с кодировкой. Но обрати внимание, что JWT и другие стандарты применяют необработанный формат IEEE P1363.
НЕМНОГО ТЕХНИЧЕСКИХ ДЕТАЛЕЙ
Если не полениться и почитать в Википедии подробности о принципах работы ECDSA, можно обнаружить, что правая часть уравнения умножается не на s, а скорее на его обратный мультипликатор: s -1. Если ты немного разбираешься в математике, ты можешь задаться вопросом: разве вычисление этого обратного результата не приведет к делению на ноль? Но в криптографии на основе эллиптических кривых эта обратная величина вычисляется по модулю большого числа, n, а для кривых, обычно используемых в ECDSA, n является простым числом, поэтому мы можем использовать малую теорему Ферма для вычисления обратного модульного значения:
xn = x1 = x(mod n)
x(n – 1) = x 0 = 1(mod n)
x(n – 2) = x –1(mod n)
Это очень эффективный метод, и именно его использует Java. Однако это справедливо только в том случае, когда x не равен нулю, поскольку у нуля нет мультипликативной инверсии. Когда x равен нулю, 0(n – 2) = 0: мусор на входе, мусор на выходе.
Тот факт, что вычисления выполняются по модулю n, также причина удостовериться, что значения r и s < n . Поэтому, если r и s = n при n = 0 (mod n), мы получим тот же эффект, как если бы r и s были равны нулю.
Еще одна проверка, которая могла бы спасти Java, — это проверка того, что точка, вычисленная из r и s, не является «бесконечно удаленной точкой». Если r и s равны нулю, то результирующая точка фактически будет точкой в бесконечности. Но, как ты уже, наверное, догадался, Java не выполняет и эту проверку.
ПОЧЕМУ УЯЗВИМОСТЬ ОБНАРУЖИЛИ ТОЛЬКО СЕЙЧАС?
Широко известно, что Java уже давно поддерживает ECDSA. Всегда ли реализация этого алгоритма была уязвимой и почему о проблеме стало известно только сейчас?
Исследователи из ForgeRock считают, что это относительно недавняя ошибка, вызванная переписыванием кода EC с нативного C++ на Java, что произошло в версии Java 15. Хотя, по мнению специалистов, эта переделка дает преимущества с точки зрения безопасности памяти, похоже, в ее реализации не участвовали эксперты в области криптографии. Исходная версия на C++ не содержит этих ошибок, но переписанная реализация уже имеет уязвимость. Ни одна из отправившихся в релиз версий Java, по‑видимому, не была полностью покрыта тестами, ведь даже самое беглое прочтение спецификации ECDSA предполагает как минимум проверку на предмет отклонения недопустимых значений r и s. В общем, обнаружившие уязвимость исследователи не уверены, что в этом коде не скрываются и другие критические ошибки.
ЧТО ТЕПЕРЬ ДЕЛАТЬ?
Если ты используешь Java 15 или более позднюю версию, незамедлительно обнови ее или установи критический патч безопасности от Oracle.
Криптографический код очень сложно реализовать правильно, а алгоритмы подписи с открытым ключом —среди самых сложных. ECDSA сам по себе весьма ненадежный алгоритм, где даже небольшая погрешность в одном случайном значении может позволить полностью восстановить твой закрытый ключ.
С другой стороны, теперь существуют отличные ресурсы, такие как Project Wycheproof, которые предоставляют тестовые примеры для известных уязвимостей. После того как эксперты ForgeRock обнаружили эту ошибку, они обновили локальную копию Wycheproof для работы с Java 17 — и она сразу же обнаружила проблему. Хочется верить, что команда JDK сама начнет использовать набор тестов Wycheproof, чтобы в будущем подобные ошибки не попадали в паблик.
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei