
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
セットアップ:
使用:
シートの解析後:

シートの事前処理後、queriesオブジェクトが得られます:

次に、queries配列(valuesメソッドを使用して配列に変換されます)をループし、invokeにquery.functionとquery.argumentsを渡して関数を呼び出します。
invoke関数では、まず関数名(query.function)に基づいて関数定義を取得します:

この関数定義には、fn、originalFn、timelionFnの3種類の関数が含まれています。次に、args(query.arguments)をループして引数を解決します:

この場合、args内の引数は'literal'型のオブジェクトであるため、リテラル値をそのまま返します:

実際には、値はPromiseでラップされています。

インデックス付き引数は、内部にq配列を持つbyNameオブジェクトを持つ引数です:

その後、invoke関数は、すべてのインデックス付き引数を使用してfunctionDef.fn(Timelionクラスの関数)を呼び出します:

fn関数はoriginalFnを呼び出します。これはconfig.fn(configはtlConfig)から来ており、その関数はDatasource extends Timelionクラスのfnです:

ご覧の通り、esFnという別のoriginalFnを呼び出しており、これはseriesListオブジェクトを返します:

この関数は、「Elasticsearchインスタンスからデータをプルする」ために使用され、callWithRequest関数を呼び出します。
seriesListはDatasource.fnでは次のようになります:

Timelion.fnでは次のようになります:

シートの事前処理後、チェーンリストは次のようになります:

そして、コードはresolveChainListを呼び出してチェーンリストを解決します:

各チェーンは、invokeに'first'をfnNameとして、チェーン自体をargsとして渡して返されたPromiseにマッピングされます。
関数定義は再び名前で取得されます:

今回は、resolveArgumentsはargsを配列として受け取り、各引数を解決するために再帰的にresolveArgumentsを呼び出します。ただし、インデックス0の引数は1つだけです。その引数はtype == chainのオブジェクトです。
そのため、switchケースでは、invokeChain関数を呼び出し、チェーンオブジェクトを渡します:

invoke chainの中では、chain配列をshiftします。shift関数は削除された要素を返すため、linkはesオブジェクトになります:

esのタイプは'chain'ではなく('function'です)、!resultはtruthyであるため、コードはinvoke関数を'first'をfnNameとして、esオブジェクトをargsとして呼び出します:
今回は上記と同じ手順なので、linkがpropsオブジェクトのinvokeChain呼び出しにスキップします:

再び、コードはinvoke関数を'first'をfnNameとして、propsオブジェクトをargsとして呼び出します:

propsのoriginalFnは、esのようにfnではなくfirstFnであり、1つの引数のみを受け入れます:

そのオリジナル関数は、最終的に脆弱なunflatten関数を呼び出します。実際には、最初にTimelionのfnを呼び出し、次にfnがpropsのoriginalFnを呼び出します:

propsのfirstFnは、argsのbyNameオブジェクトからinputSeriesとglobalプロパティを省略します:

そのため、unflattenに渡されるdataは次のようになります:
{
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__になります:

3回目の反復では、__proto__はすべてのオブジェクトに常に存在するため、curはそのプロトタイプになります:

その後、propはenvになります。
そして、curはObject.prototypeを指しているため、Object.prototypeにenvが空のオブジェクトとして追加され、curもObject.prototype.env内のenvを参照します。

その後、ループが終了し、cur[prop] = data[p]はenv[AAA] = 'a'と等価になります。

Object.prototypeがenvで汚染されました:

fork関数を呼び出す際のポートを9669(別のポート)に変更すると、メインプロセスを通常通りデバッグできます:

汚染されたObject.prototypeをシミュレートし、ループの前にジャンプします:

ご覧の通り、プロトタイプ汚染によりenvは{ AAA: 'a' }と等価です。具体的には、envはoptions.envに割り当てられます。そして、optionsはそのプロパティを定義していないため、プロトタイプの値を使用します。
ループ後:

何らかの理由で、無効な環境変数(AAA=a、env=[Object object])がある場合、canvasは新しいソケットを作成できません:

ただし、子プロセスはまだ作成できます:

通常のフローでは、新しいWebSocket接続が作成されます:

次にペイロードを試し、normalizeSpawnArgumentsにジャンプします:

ご覧の通り、スタックトレースは同じです。
しかし、envは汚染されています:

出来上がり!
ループ後のenvPairs:

そして、リバースシェルを取得しました:

spawn関数内でnormalizeSpawnArgumentsを呼び出した後:

ご覧の通り、envPairsにはペイロードが含まれています。また、その後spawnはinternal/child_process.js内のspawn関数を呼び出します:

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];