За последнюю неделю в сфере инфобеза стали появляться новости о втором пришествии уязвимости 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 октября 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
.
Как разработчик исправил уязвимость
В патче к уязвимости было сделано следующее:
1.Изменен алгоритм заполнения связей prefix
и классов (stringLookupMap
) в функции addDefaultStringLookups
класса StringLookupFactory.
Теперь HashMap stringLookupMa
p заполняется коллекцией defaultStringLookups
из экземпляра класса DefaultStringLookupsHolder
.
2.Добавлен класс DefaultStringLookupsHolder
,и в его конструкторе выполняется контроль ключа системного свойства org.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups
:
a. если ключ отсутствует, формируются связи по умолчанию с помощью вызова функции createDefaultStringLookups
;
b. если ключ присутствует, формируются связи на основе значений ключа с помощью вызова функции parserStringLoookups
.
3.Для случая формирования связей по умолчанию (функция createDefaultStringLookups
) не создаются связи для prefix
: script
, dns
, url
.
4.Для случая формирования связи на основе значений ключа org.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups
в функции parserStringLoookups
по значению ключа сформируется список связей между prefix
и классом. Значения должны соответствовать перечню, заданному в enum
DefaultStringLookup
(например, 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 и проводить такой контроль динамически (например, с использованием утилиты).
Если библиотека присутствует в составе проекта:
обновиться до версии 1.10.0, в которой были изменены настройки по запуску небезопасного функционала по умолчанию;
проконтролировать в настройках окружения наличие включенных уязвимых режимов (
script
,dns
,url,
file
,env
);провести статический анализ на возможность передачи входных пользовательских данных в аргумент source функций
replace
(replaceIn
) классаStringSubstitutor
. Это можно выполнить автоматически с помощью обновленного PT Application Inspector (версия 4.1.1) или же с использованием правил SemGrep;выполнить санитизацию/экранирование входных пользовательских данных перед попаданием их в аргумент
source
функцииreplace
(replaceIn
) классаStringSubstitutor
.
Правило 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
Правило SemGrep — созданиеStringSubstitutor
через конструктор:
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
Команда PT Application Inspector
Александр Болдырев, Алексей Новгородов, Максим Суслов