Хакер - Электронная психобумага. Критическая уязвимость в Java позволяет подделывать электронные подписи и ключи

Хакер - Электронная психобумага. Критическая уязвимость в Java позволяет подделывать электронные подписи и ключи

hacker_frei

https://t.me/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. Что­бы про­верить такую под­пись, верифи­катор реша­ет урав­нение, вклю­чающее зна­чения rs, откры­тый ключ под­писав­шего и хеш сооб­щения. Если две час­ти урав­нения рав­ны, под­пись счи­тает­ся дей­стви­тель­ной, в про­тив­ном слу­чае она откло­няет­ся.

Од­на часть урав­нения дол­жна быть рав­на 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



Report Page