
За последнюю неделю в информационном поле инфобеза стали появляться новости о втором пришествии уязвимости Log4Shell, окрестившим себя Text4Shell. Первым об уязвимости сообщил Alvaro Muñoz, который рассказал о возможности удаленного выполнения произвольных скриптов в продуктах, использующих библиотеку Apache Commons Text.
Apache Commons Text — это open source компонент, используемый разработчиками для управления символьными строками. Уязвимость была выявлена в версиях 1.5-1.9 и связана с небезопасной реализацией функций интерполяции переменных. По данным сайта maven repository, библиотека Apache Commons Text используется в 2591 проекте, однако данная оценка не учитывает транзитивные зависимости библиотек.
Сама уязвимость была обнаружена еще в марте 2022 года, но команде Apache Commons потребовалось время на ее исправление и выпуск обновлений библиотеки.
Ссылки с первоначальной информации о уязвимости:
Уязвимости был присвоен идентификатор CVE-2022-42889 (CWE-94 – Code Injection), и определен достаточно высокий уровень риска CVSS 9.8.
В течение последующих дней после раскрытия информации об уязвимости стали появляться сомнения в критичности уязвимости, ссылаясь на невозможность эксплуатации в версиях JDK 15+ или по причине маловероятности попадания пользовательских данных в функцию интерполяции переменной. Однако при дальнейшем изучении уязвимости оказались открыты и другие векторы ее эксплуатации.
Перечень исследований, подвергавших сомнению серьезный уровень опасности уязвимости:
-
CVE-2022-42889: Keep Calm and Stop Saying «4Shell» (18.10.2022 вышло обновление статьи, в котором говорится, что и версии JDK 15+ тоже подвержены уязвимости при определенных условиях — о них расскажем чуть ниже);
-
Critical Apache Commons Text Flaw Compared to Log4Shell, But Not as Widespread (обзор статей с критикой серьезности уязвимости от Security Week);
-
Apache Commons Vulnerability: Patch but Don’t Panic (разъяснения о серьезности уязвимости от эксперта Dark Reading).
Наша команда PT Application Inspector решила определить уязвимые места в исходном тексте, оценить выпущенный патч от команды разработки и посоветовать шаги, которые помогут защититься от возможных атак.
Про причины уязвимости
В состав функциональных возможностей заложен механизм интерполяции переменных, то есть вставка в строку значений на основе обработки данных по шаблону. В качестве шаблона по умолчанию для данной библиотеки используется строка ${prefix:[options]:data}, где prefix определяет алгоритм обработки данных из options и data. Если у атакующего есть возможность задавать шаблон, он способен на следующее:
|
Атака |
Prefix |
Вектор атаки |
|
Удаленное выполнение кода |
script |
${script:javascript:java.lang.Runtime.getRuntime().exec(‘calc’)} |
|
Сбор информации через запрос к DNS-серверу атакующего |
dns |
${dns:address|ptsecurity.com} |
|
Раскрытие внутренней сети, через http (https)-запросы |
url |
${url:UTF-8:https://www.ptsecurity.com} |
|
Чтение произвольного файла |
file |
${file:UTF-8:/etc/passwd} |
|
Доступ к переменным окружения, содержащим критичную информацию |
env |
${env: AWS_ACCESS_KEY_ID} |
А что же творится внутри библиотеки
Чтобы разобраться с алгоритмом работы библиотеки, создадим простейшее веб-приложение, использующее уязвимый код приложения. Причем вектор попадает в уязвимую функцию из состава http-запроса параметра taint.
В качестве вектора атаки будем использовать самый критичный вариант (code injection):
${script:javascript:java.lang.Runtime.getRuntime().exec('calc')}
В результате атаки должна выполнится команда ОС и запустится программа «Калькулятор».
При анализе кода можно выделить два момента, которые выполняются при использовании библиотеки:
-
Создание экземпляра объекта StringSubstitutor путем вызова
StringSubstitutor.createInterpolator. -
Манипулирования строкой, которая задается из параметра http-запроса. Это осуществляется путем вызова
interpolator.replace(taint).
В первом случае при вызове функции StringSubstitutor.createInterpolator выполняется автоматическое заполнение таблицы связей между prefix и классом обработки вызова lookup().
Стек вызовов:
В функции addDefaultStringLookups класса StringLookupFactory в конечном виде формируется HashMap (stringLookupMap) следующего вида:
… "dns" -> {DnsStringLookup@785} "env" -> {FunctionStringLookup@787} … "script" -> {ScriptStringLookup@793} "url" -> {UrlStringLookup@795} "file" -> {FileStringLookup@799} …
Уязвимые префиксы (script, dns, url, file иenv) определены по умолчанию и доступны для использования.
Во втором случае для функции replace (replaceIn) класса StringSubstitutor выполняется вызов функций из стека:
Для данного стека можно выделить вызов функции lookup из класса ScriptStringLookup — это стало возможным из-за того, что для prefix=script установлена связь с классом ScriptStringLookup.
В зависимости от установленного класса в stringLookupMap для соответствующего значения prefix выполняется соответствующий метод lookup. Перечень критичных методов в зависимости от установленного префикса приведены ниже:
|
prefix |
Класс |
Критичный метод |
Ссылка на код |
|
script |
ScriptStringLookup |
scriptEngine.eval(script) |
|
|
dns |
DnsStringLookup |
InetAddress.getByName(subValue) |
|
|
url |
UrlStringLookup |
new URL(urlStr) |
|
|
file |
FileStringLookup |
Files.readAllBytes(Paths.get(fileName)) |
|
|
env |
StringLookupFactory |
System::getenv |
Для нашего примера вызывается scriptEngine.eval(script). В JDK до версии 15 scriptEngine ассоциируется со встроенным скриптовым движком Nashorn, который выполняет скрипт, заданный пользователем. В версия JDK 15+ движок Nashorn был удален. Однако, если в проекте используется JDK 15+ и установлены зависимости на другой скриптовый движок, например JEXL, то вектор изменится на: ${script:JEXL:».getClass().forName(‘java.lang.Runtime’).getRuntime().exec(‘calc’)}.
Таким образом, можно выделить три обязательных условия для эксплуатации уязвимости:
-
Должна быть определена связь (в
stringLookupMap) между prefix и соответствующим классом. С использованием функцииStringSubstitutor.createInterpolatorбудут по умолчанию установлены небезопасные префиксы:script,dns,url,file,envи соответствующие им классы. -
Должна быть вызвана функция
replace(replaceIn) классаStringSubstitutor, в которой произойдет вызов функцииlookup. -
При вызове функции
replace(replaceIn) пользовательские данные должны записываться в ее аргументsource.
Как разработчик исправил уязвимость
В патче к уязвимости было сделано следующее:
-
Изменен алгоритм заполнения связей
prefixи классов (stringLookupMap) в функцииaddDefaultStringLookupsклассаStringLookupFactory.Теперь HashMapstringLookupMap заполняется коллекциейdefaultStringLookupsиз экземпляра классаDefaultStringLookupsHolder. -
Добавлен класс
DefaultStringLookupsHolder,и в его конструкторе выполняется контроль ключа системного свойстваorg.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups.
a. Если ключ отсутствует, формируются связи по умолчанию с помощью вызова функции createDefaultStringLookups;
b. Если ключ присутствует, формируются связи на основе значений ключа с помощью вызова функции parserStringLoookups.
3. Для случая формирования связей по умолчанию (функция createDefaultStringLookups) не создаются связи для prefix: script, dns, url.
-
Для случая формирования связи на основе значений ключа
org.apache.commons.text.lookup.StringLookupFactory.defaultStringLookupsв функцииparserStringLoookupsпо значению ключа сформируется список связей междуprefixи классом. Значения должны соответствовать из перечня, заданного вenumDefaultStringLookup(например,BASE64_DECODER,SCRIPT).
Таким образом, внесенные изменения содержат следующее:
-
из списка связей между
prefixи классом, который определяется по умолчанию, исключены:script,dns,url; -
появился механизм определения списка связей между
prefixи классом через указания соответствующего перечняprefixв ключеorg.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups(например,SCRIPT,URL,DNS).
Следовательно, можно утверждать, что патч для версии 1.10.0 библиотеки Apache Commons Text оставляет по умолчанию небезопасные prefix: file, env. Для включения уязвимых режимов обработки интерполяции (script, dns, url) достаточно записать соответствующие значения в поле org.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups.
Это можно сделать несколькими способами:
-
в параметрах запуска приложения
java -Dorg.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups=SCRIPT,DNS,URL text4j.jar
-
установкой свойства внутри кода с помощью функции
System.setProperty.
Что делать дальше
Из результатов рассмотрения уязвимости и варианта исправления от разработчиков можно сделать следующий вывод:
-
библиотека Apache Commons Text остается уязвимой и после обновления до версии 1.10.0. Все зависит от системного свойства, указанного в окружения приложения;
-
эксплуатация уязвимости этой библиотеки главным образом зависит от попадания пользовательских входных данных на вход уязвимых функций
replace(replaceIn) классаStringSubstitutor. На текущий момент для пакетов, в которые включена библиотека Apache Commons Text, отсутствует публичная информация о том, что существует прямой канал передачи пользовательских данных в уязвимые функции, но расслабляться рано.
Следовательно, библиотека Apache Commons Text остается уязвимой, эксплуатация зависит от способов ее применения и отсутствуют гарантии в небезопасном использовании библиотеки внутри собственного продукта или внутри заимствованного пакета.
?Поэтому предлагаем подготовится к реагированию на такую уязвимость:
-
настроить правила фильтрации межсетевых экранов на наличие шаблона
${prefix:[options]:data} -
выполнить контроль на наличие в составе проекта продукта уязвимой библиотеки Apache Commons Text и проводить такой контроль динамически (например, с использованием утилиты).
-
если библиотека присутствует в составе проекта:
o обновиться до версии 1.10.0, в которой были изменены настройки по запуску небезопасного функционала по умолчанию;
o проконтролировать в настройках окружения наличие включенных уязвимых режимов (
scritp,dns,url,file,env);o провести статический анализ на возможность передачи входных пользовательских данных в аргумент source функций
replace(replaceIn) классаStringSubstitutor. Это можно выполнить автоматически с помощью обновленной версии PT Application Inspector или же с использованием правил SemGrep:
§ создание StringSubstitutor через createInterpolator:
rules: - id: text4shell_via_createInterpolator patterns: - pattern-either: - pattern: $INTERPOLATOR.replace(...); - pattern: $INTERPOLATOR.replaceIn(...); - pattern-inside: | import org.apache.commons.text.$PKG; ... $INTERPOLATOR = $PKG.createInterpolator(...); ... message: text4shell $INTERPOLATOR.replace call found languages: - java severity: WARNING
§ создание StringSubstitutor через конструктор:
rules: - id: text4shell_via_ctor patterns: - pattern-either: - pattern: $INTERPOLATOR.replace(...); - pattern: $INTERPOLATOR.replaceIn(...); - pattern-inside: | import org.apache.commons.text.$PKG; ... $INTERPOLATOR = new StringSubstitutor(...); ... message: text4shell $INTERPOLATOR.replace call found languages: - java severity: WARNING
o выполнить санитизацию/экранирование входных пользовательских данных перед попаданием их в аргумент source функции replace (replaceIn) класса StringSubstitutor.
Команда PT Application Inspector
Александр Болдырев, Алексей Новгородов, Максим Суслов
ссылка на оригинал статьи https://habr.com/ru/company/pt/blog/694720/
Добавить комментарий