Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2022-3602 | Kitploit
Инструменты/GitHubGitHub/colmmacc/cve-2022-3602
Анализ уязвимостейЭксплуатацияКриптографияАнализ Бинарных ФайловСтатьи и ИсследованияОбучение и Образование
GitHubcolmmacc/cve-2022-3602

CVE-2022-3602

Репозиторий
169303 лет назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE−2022-3602

Что это?

Этот документ и репозиторий представляют собой разбор CVE−2022-3602, проблемы переполнения буфера в реализации punycode в OpenSSL. Это «анти-PoC» (проблема, судя по всему, не эксплуатируема), предназначенный для тех, кто поддерживает собственные сборки OpenSSL, и для разработчиков компиляторов.

В том же релизе есть отдельная CVE, CVE-2022-3786, которая также приводит к переполнению буфера, но в этом случае атакующий не может контролировать содержимое. Здесь нет воспроизведения для этой проблемы, но она может привести к отказу в обслуживании из-за краха.

Краши и переполнения буфера никогда не бывают хорошими, и если вы используете OpenSSL 3.0.x, благоразумно обновиться как можно скорее.

Пожалуйста, сообщайте о любых ошибках или пропусках через GitHub issues или pull-requests.

В чем проблема?

В функции ossl_punycode_decode есть ошибка off-by-one при обработке декодирования punycode, которая приводит к переполнению в 4 байта. Эта проблема возникает только тогда, когда OpenSSL обрабатывает цепочку сертификатов, и требует двух условий. Во-первых, сертификат CA или промежуточного удостоверяющего центра в цепочке должен содержать поле name-constraint, использующее punycode.

root@kitploit:~
nameConstraints = permitted;email:xn-maccrthaigh-n7a.com

Во-вторых, конечный сертификат должен содержать поле otherName в SubjectAlternateName (SAN), указывающее строку SmtpUTF8Mailbox.

root@kitploit:~
otherName = 1.3.6.1.5.5.7.8.9;UTF8:[email protected]

При срабатывании punycode из поля nameConstraints, но не punycode из поля otherName, будет обработан уязвимым парсером punycode в OpenSSL.

Насколько легко вызвать эту проблему?

Дэвид Бенджамин (David Benjamin) и Мэтт Касвелл (Matt Caswell) установили, что проверка nameConstraint происходит после обычной проверки цепочки сертификатов и проверки подписи. Для большинства приложений это означает, что проблема не может быть вызвана самоподписанным сертификатом или недействительной цепочкой.

Обратите внимание, что приложения s_client и s_server из openssl предназначены для отладки и не прекращают обработку, когда цепочка недействительна.

Доверенный CA или промежуточный сертификат должен содержать вредоносную полезную нагрузку, а также должен подписать конечный сертификат, который вызывает проблему.

Могут быть среды, в которых недоверенные стороны являются CA или промежуточными удостоверяющими центрами, например, хостинг-сервис, поддерживающий частные CA, предоставленные клиентом, но это не распространено.

Приводит ли проблема к удаленному выполнению кода?

Для многих приложений ответ будет «нет» из-за того, как компилятор разместил стек, и из-за наличия других защит, таких как стековые канарейки / стековые куки, выравнивание, PIE, FORTIFY_SOURCE.

Проблема действительно приводит к переполнению 32 бит в стеке. Этого недостаточно для непосредственного выполнения шелл-кода, но может быть достаточно, чтобы изменить поток управления приложением. Например, переход на шелл-код, встроенный в цепочку сертификатов X509, может быть возможен, если эти данные также скопированы в стек в исполняемую область.

На всех протестированных мной платформах Linux переполнение происходит в выравнивание и безвредно. Теоретически компилятор может разместить переменные так, что переполнение произойдет в одну из других переменных функции ossl_a2ulabel.

В зависимости от инлайнинга полный список присутствующих переменных:

root@kitploit:~
outptr, inptr, size, result, tmpptr, delta, seed, utfsize

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

Я приложил tarball с инструментами, которые можно использовать для создания воспроизведений и переполнений с максимально возможным контролем над всеми четырьмя байтами. Эталонная строка воспроизведения (xn--ww90271...aaaa) переполняет четыре байта значениями 0xFF 0x0F 0x0F 0x0F. Если это не приводит к краху приложения, возможно (вероятно?), это приложение не уязвимо.

Как я могу воспроизвести эту проблему?

Скрипт run-poc можно использовать для генерации вредоносной цепочки сертификатов. Вредоносный сертификат CA генерируется из ca.cnf, а вызывающий проблему конечный сертификат — из leaf.cnf.

Сертификат CA использует следующую эталонную полезную нагрузку:

xn--ww902716aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Python-скрипт можно использовать для генерации других punycode-строк для других полезных нагрузок.

При выполнении run-poc будет запущен клиент и сервер openssl, и будет предпринята попытка эксплуатировать проблему десять раз.

Уязвимый OpenSSL, скорее всего, упадет. Это не значит, что версия OpenSSL уязвима к RCE, поскольку стековые канарейки и защита стековых куки также обычно вызывают (более безопасный) крах приложения. Также обратите внимание, что это никак не меняет серьезность другой CVE в том же релизе.

Как работает эта проблема?

Удивительно тонко получить почти полный контроль над всеми четырьмя байтами переполнения, и для этого требуется эксплуатировать декодер punycode в OpenSSL с помощью нестандартного / недействительного punycode. В приложенном tarball есть скрипт, который может сконструировать строку, учитывающую эту тонкость. Ниже приводится объяснение того, как это работает.

Подготовка

Проблема безопасности в ossl_punycode_decode()

root@kitploit:~
int ossl_punycode_decode(const char *pEncoded, const size_t enc_len,
                         unsigned int *pDecoded, unsigned int *pout_length)

ossl_punycode_decode вызывается из ossl_a2ulabel. Буфер pEncoded — это буфер более или менее произвольного размера, поступающий из цепочки сертификатов X509. Это часть, которая идет после любого xn-- в поле nameConstraint. См. [reproduction] для того, как воспроизвести такую цепочку сертификатов.

pDecoded — это массив unsigned int размером LABEL_BUF_SIZE. LABEL_BUF_SIZE равен 512, и на большинстве платформ unsigned int имеет ширину 4 байта. Таким образом, на большинстве платформ pDecoded имеет длину 2048 байт.

Сцена

Внутри ossl_punycode_decode() суть проблемы — следующая неверная проверка длины:

root@kitploit:~
 if (written_out > max_out)

max_out соответствует *pout_length, который всегда равен 512. А written_out отслеживает, сколько unsigned int было записано в pDecoded. Поскольку written_out увеличивается позже, только после записи, эта ошибочная проверка позволяет записать 513 unsigned int в pDecoded. Конечный результат выглядит примерно так ...

root@kitploit:~
pDecoded = [ ... , 'X , 'Y' , 'Z' ] 'P'
// Indices         509   510   511

Здесь, по соглашению C, индексы начинаются с нуля, поэтому слот номер 511 — это 512-й элемент массива. 'P' — это четырехбайтовая полезная нагрузка, которая была записана за пределами выделенного в стеке пространства для буфера buf в ossl_a2ulabel(), на который указывает pDecoded.

Четыре байта — это небольшое переполнение, которого недостаточно для NOP-слайда или непосредственного выполнения шелл-кода, но достаточно, чтобы изменить поток управления приложением. Например, переход на шелл-код, встроенный в цепочку сертификатов x509, может быть возможен, в зависимости от того, как эти данные (или скопированные фрагменты этих данных) хранятся и является ли эта память исполняемой. Однако для потенциального атакующего есть еще больше трудностей.

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

Во-вторых, есть только один путь к ossl_punycode_decode(), и этот путь использует буфер в стеке. Это делает маловероятным использование проблемы для одновременных четырехбайтовых переполнений в разных областях памяти.

Декодирование Punycode

Punycode-строки в основном имеют две формы. Одна — xn--c1yn36f (點看), другая — xn--maccrthaigh-n7a (maccárthaigh). Часть, которая идет после последнего разделителя -, представляет собой 36-ричное bootstring-кодирование любых кодовых точек Unicode, которые не являются обычным базовым ascii, вместе с позицией в строке для их вставки. Важно сейчас то, что процесс декодирования в ossl_punycode_decode() производит два значения. Одно — 'n', которое является unsigned int значением кодовой точки для вставки, а другое — 'i', которое является позицией в буфере для ее вставки.

Запись может происходить двумя разными способами. Если i находится где-то в середине строки, то есть memmove(), который сначала «освобождает место», копируя все вправо на один слот:

root@kitploit:~
memmove(pDecoded + i + 1, pDecoded + i,
       (written_out - i) * sizeof *pDecoded);

а затем записывает n в только что освобожденное место:

root@kitploit:~
 pDecoded[i] = n;

если i находится в конце строки, то memmove() не имеет эффекта, потому что последний параметр будет равен 0. Другая строка становится простым добавлением в конец.

Теперь мы рассмотрим три различных способа получить полезную нагрузку 'P' в позицию переполнения и почему возникают ограничения.

Метод 1 — переполнение через ascii

Самый простой способ вызвать переполнение — создать punycode-строку, содержащую 511 ascii-символов и два не-ascii символа. Punycode-кодирование строки длиной 513 символов, такой как "ÁÁAAAAAAAA...AAA", подойдет. В этом случае произойдет следующее: когда written_out равно 510, у нас будет буфер, расположенный так ...

root@kitploit:~
pDecoded = [ 'A' , 'A' ,  ... , 'A' , 'A',     ]
// Indices    0     1     ...   509   510  511

это просто базовые ascii-символы, которые были скопированы. Затем мы разбираем punycode bootstring и вставляем 'Á' в позицию 0. Хотя это может быть любая позиция от 0 до 511 включительно.

root@kitploit:~
pDecoded = [ 'Á' , 'A' ,  ... , 'A' , 'A', 'A' ]
// Indices    0     1     ...   509   510  511

затем мы повторяем это:

root@kitploit:~
pDecoded = [ 'Á' , 'Á' ,  ... , 'A' , 'A', 'A' ] 'A'
// Indices    0     1     ...   509   510  511   512

это приведет к тому, что обычный ascii 'A' переполнится, поскольку он «сдвигается». Четырехбайтовая полезная нагрузка в этом случае становится 0x00 0x00 0x00 0x41. Как мы увидим, из-за того, как работает punycode, это единственный способ выразить любое значение с последним байтом в диапазоне ascii.

Нам нужно использовать два не-ascii символа, потому что есть правильная проверка границ для количества базовых символов, поэтому их должно быть меньше 512.

Дополнительное ограничение, заключающееся в том, что значение последнего байта не может быть 46, возникает потому, что ossl_punycode_decode() вызывается для части строки, которая предшествует литеральному символу .. Punycode предназначен для доменных меток, в которых не может быть точек.

Метод 2 — прямое переполнение не-ascii символом

Следующий самый простой способ вызвать переполнение — создать строку из 513 символов с не-ascii символом в самом конце. Что-то вроде "AAAAAAAAAA...AAÁ". В этом случае для наших последних двух шагов мы будем иметь:

pDecoded = [ 'A' , 'A' , ... , 'A' , 'A' ] // Indices 0 1 ... 510 511

и

root@kitploit:~
pDecoded = [ 'A' , 'A' ,  ... , 'A' , 'A' ] 'Á'
// Indices    0     1     ...   510   511

не-ascii символ пойдет прямо в позицию переполнения. Парсер punycode в OpenSSL не требует, чтобы значение переполнения здесь было действительно допустимым символом Unicode. Это более или менее бинарный процесс декодирования. Но нюансы декодирования punycode означают, что метод 2 не так гибок, как может показаться на первый взгляд.

В punycode значения n и i оба кодируются как одно целое переменной длины, которое затем кодируется в ascii с использованием base36. Может показаться невозможным закодировать два несвязанных числа как одно целое, но хитрый трюк punycode заключается в использовании длины строки (на данный момент) как скрытого поля.

Например, предположим, у нас есть punycode-строка с 4 базовыми символами и одним не базовым, например AAÁAA. Сначала она будет представлена только базовыми символами ... AAAA. Значение Unicode для 'Á' равно 225, а его позиция в строке — 2. Фокус в том, чтобы умножить значение на длину плюс один, а затем добавить позицию. Таким образом, получается ((225 * (4 +1)) + 2), что равно 1127, и именно так это кодируется (в base36 переменной длины).

Для декодирования нужно идти в обратном направлении. 1127 / 5 равно 225, а 1127 % 5 равно 2. Вот так из одного числа получаются два. Но заметьте, что чем длиннее становится строка, тем более ограниченным становится максимальное значение, иначе кратное не поместится в unsigned int. В общем случае, если строка имеет длину M символов, то вы теряете log M бит ширины значения.

К тому времени, когда вы обрабатываете 512-е целое, вы теряете 9 бит ширины. Используя метод 2, самое высокое значение, которое, казалось бы, 32-битная полезная нагрузка может иметь, на самом деле равно 2^23. Даже не полных три байта. Метод 2 неоптимален.

Метод 3 — заполнение (stuffing)

Чтобы вернуть контроль над 4 байтами, самый эффективный способ — повторять символ полезной нагрузки снова и снова. Пока я упустил две другие важные детали того, как обрабатывается punycode.

Первая деталь: не-ascii символы кодируются не в порядке строки, а в порядке возрастания значения. Строка "ÉÁ" в итоге будет закодирована как "Á на позиции 1, É на позиции 0", потому что Á имеет меньшее значение (225), чем É (233).

Вторая деталь: не-ascii символы кодируются не как их буквальные значения, а как дельта относительно последнего декодированного значения. Поскольку у первого значения нет предыдущего значения, относительно которого можно было бы вычислить дельту, есть жестко заданная начальная точка 128.

Эти маленькие нюансы делают punycode очень эффективным по пространству, но также означают, что не-ascii символ просто не может быть декодирован в значение меньше 128. Наименьшая дельта равна 0, и нет способа выразить отрицательную дельту. Поэтому, если вам нужно число меньше 128, вы должны использовать метод 1.

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

root@kitploit:~
 [ 'P', 'P', ... 'P', 'P', 'P' ]
    0    1       510  511  512

которая будет декодирована OpenSSL как ...

root@kitploit:~
pDecoded = [ 'P', 'P', ... 'P', 'P' ] 'P'
              0    1       510  511   512

с P в позиции переполнения и способным представлять любое значение от 128 до (2^32 - 1).

Все это требует нестандартного punycode-энкодера, и я включил скрипт, который может создать полезную нагрузку, используя метод 1 или метод 3 по мере необходимости.

Мини-FAQ:

Помимо обновления OpenSSL, есть ли другие меры смягчения?

В большинстве сред цепочки сертификатов передаются в открытом виде, и вредоносная цепочка может быть заблокирована отклонением TCP-соединений, которые содержат DER-закодированный NID 1.3.6.1.5.5.7.8.9 в поле OtherName в SubjectAlternateName.

К сожалению, это поле может быть произвольно разбито между двумя или более пакетами, и для блокировки действительно нужен какой-то механизм сопоставления с образцом с сохранением состояния. Сертификаты также могут быть сжаты, но OpenSSL 3.0.x в настоящее время не поддерживает сжатие сертификатов.

Кроме того, в TLS1.3 цепочки клиентских сертификатов шифруются в канале, а более ранние версии TLS поддерживают шифрованные цепочки сертификатов при повторном согласовании существующего соединения. Иногда это используется для аутентификации по сертификату, инициированной сервером. В таких случаях сетевой фильтр не будет эффективен.

Как я могу узнать, использую ли я openssl 3 в статически слинкованном двоичном файле?

root@kitploit:~
 readelf -a [binary] | grep -i ossl_punycode_decode

будет искать уязвимую функцию в статически слинкованном двоичном файле. Только OpenSSL >= 3.0 содержит эту функцию.

Скачать инструмент