
PoC и анализ для Kibana Prototype Pollution RCE (CVE-2019-7609).
Версии Kibana до 5.6.15 и 6.6.1 содержат уязвимость выполнения произвольного кода в визуализаторе Timelion. Злоумышленник с доступом к приложению Timelion может отправить запрос, который попытается выполнить JavaScript-код. Это может привести к выполнению произвольных команд с правами процесса Kibana на хост-системе.
CVSS:

Блог: https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/
Полезная нагрузка: https://github.com/mpgn/CVE-2019-7609
Настройка:
Использование:
После парсинга sheet:

После предварительной обработки sheet, получаем объект queries:

Затем мы перебираем массив queries (который преобразуется в массив с помощью метода values) и передаем query.function и query.arguments в invoke для вызова функции.
В функции invoke сначала получаем определение функции по её имени (query.function):

Это определение функции включает три типа функций: fn, originalFn и timelionFn. Затем она перебирает args (то есть query.arguments) и разрешает аргументы:

В данном случае аргумент в args — это объект с типом 'literal', поэтому просто возвращается литеральное значение:

На самом деле значение обёрнуто в promise.

Индексированный аргумент — это аргумент с объектом byName, содержащим внутри массив q:

После этого функция invoke вызывает functionDef.fn, которая является функцией класса Timelion, со всеми индексированными аргументами:

Функция fn вызывает originalFn, которая происходит из config.fn, где config — это tlConfig. И эта функция — fn класса Datasource extends Timelion:

Как видим, она вызывает другую originalFn с именем esFn, которая возвращает объект seriesList:

Эта функция используется для «извлечения данных из экземпляра Elasticsearch» путём вызова функции callWithRequest.
seriesList выглядит так в Datasource.fn:

А так — в Timelion.fn:

После предварительной обработки sheet цепочка выглядит так:

И код вызывает resolveChainList для разрешения списка цепочек:

Каждая цепочка сопоставляется с Promise, который возвращается из функции, вызывающей invoke с 'first' в качестве fnName и самой цепочкой в качестве args.
Определение функции снова получается по имени:

На этот раз resolveArguments получает args как массив и рекурсивно вызывает resolveArguments для каждого аргумента. Но у нас есть только один аргумент с индексом 0. И этот аргумент — объект с type == chain.
Поэтому в операторе switch вызывается функция invokeChain, и в неё передаётся объект цепочки:

Внутри invokeChain выполняется shift массива chain. Функция shift возвращает удалённый элемент, поэтому link становится объектом es:

Поскольку тип es не 'chain' (он 'function'), а !result истинно, код вызывает функцию invoke с 'first' в качестве fnName и объектом es в качестве args:
На этот раз процедура такая же, как описано выше, поэтому переходим к вызову invokeChain, где link — это объект props:

Снова код вызывает функцию invoke с 'first' в качестве fnName и объектом props в качестве args:

originalFn для props теперь — firstFn, а не fn, как у es, и принимает только один аргумент:

Эта оригинальная функция в конечном итоге вызовет уязвимую функцию unflatten. На самом деле сначала вызывается fn из Timelion. Затем fn вызывает originalFn для props:

firstFn для props опускает свойства inputSeries и global в объекте byName аргумента args:

Таким образом, data, передаваемая в unflatten, будет:
{
label.__proto__.env.AAA: "a"
}
Доказательство:

Используется props(label.__proto__.env.AAA="a"). data будет выглядеть так:

На первой итерации prop = '' не существует, поэтому присваивается cur[''] = {}. Это означает, что result также получит свойство с именем '' и значением пустой объект. После этого cur присваивается {}, который является возвращаемым значением предыдущего присваивания (cur и cur[prop] ссылаются на один и тот же объект):

Теперь prop станет 'label':

Как и раньше, result получает свойство 'label', а prop становится __proto__:

На третьей итерации cur становится своим прототипом, так как __proto__ всегда существует в любом объекте:

После этого prop становится env.
Затем, поскольку cur ссылается на Object.prototype, добавляется env в Object.prototype как пустой объект, и cur также ссылается на env внутри Object.prototype.env.

После этого цикл завершается, и cur[prop] = data[p] эквивалентно env[AAA] = 'a'.

Object.prototype загрязнён свойством env:

Функция
forkвозвращаетChildProcess, который можно использовать для вызоваspawn
Изменим порт при вызове функции fork на 9669 (другой порт), после чего можно нормально отлаживать основной процесс:

Симулируем загрязнённый Object.prototype и переходим к моменту перед циклом:

Как видим, env эквивалентно { AAA: 'a' } из-за прототипного загрязнения. Более конкретно, env присваивается options.env. А options не определяет это свойство, поэтому используется значение из его прототипа.
После цикла:

По какой-то причине, из-за недопустимых переменных окружения (AAA=a, env=[Object object]), canvas не может создать новый сокет:

Но дочерний процесс всё равно может быть создан:

При нормальном потоке создаётся новое WebSocket-соединение:

Теперь попробуем с полезной нагрузкой и перейдём в normalizeSpawnArguments:

Как видим, стек вызовов тот же.
Но env загрязнён:

Вуаля!
envPairs после цикла:

И мы получили обратный шелл:

После вызова normalizeSpawnArguments в функции spawn:

Как видим, envPairs содержит нашу полезную нагрузку. После этого spawn вызывает функцию spawn внутри internal/child_process.js:

Переменная options передаётся в this._handle.spawn:

Где this._handle — это дескриптор процесса:

From 3377f813a5d96ff466bdf7343ce161de24830ed4 Mon Sep 17 00:00:00 2001
From: ppisljar <[email protected]>
Date: Wed, 16 Jan 2019 06:01:20 -0800
Subject: [PATCH] fixes
---
.../core_plugins/timelion/server/series_functions/props.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/legacy/core_plugins/timelion/server/series_functions/props.js b/src/legacy/core_plugins/timelion/server/series_functions/props.js
index 81b74901d4db..80e9cafd6712 100644
--- a/src/legacy/core_plugins/timelion/server/series_functions/props.js
+++ b/src/legacy/core_plugins/timelion/server/series_functions/props.js
@@ -32,7 +32,7 @@ function unflatten(data) {
let prop = '';
let m;
while (m = regex.exec(p)) {
- cur = cur[prop] || (cur[prop] = (m[2] ? [] : {}));
+ cur = (cur.hasOwnProperty(prop) && cur[prop]) || (cur[prop] = (m[2] ? [] : {}));
prop = m[2] || m[1];
}
cur[prop] = data[p];