
PoC und Analyse für Kibana Prototype Pollution RCE (CVE-2019-7609).
Kibana-Versionen vor 5.6.15 und 6.6.1 enthalten eine Schwachstelle zur Ausführung beliebigen Codes im Timelion-Visualizer. Ein Angreifer mit Zugriff auf die Timelion-Anwendung kann eine Anfrage senden, die versucht, JavaScript-Code auszuführen. Dies könnte dazu führen, dass ein Angreifer beliebige Befehle mit den Berechtigungen des Kibana-Prozesses auf dem Hostsystem ausführt.
CVSS:

Blog: https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/
Payload: https://github.com/mpgn/CVE-2019-7609
Einrichtung:
Verwendung:
Nach dem Parsen des Sheets:

Nach der Vorverarbeitung des Sheets haben wir ein queries-Objekt:

Dann iterieren wir über das queries-Array (das mit der values-Methode in ein Array umgewandelt wird) und übergeben query.function und query.arguments an invoke, um die Funktion aufzurufen.
In der invoke-Funktion holen wir zunächst die Definition der Funktion basierend auf ihrem Namen (query.function):

Diese Funktionsdefinition enthält drei Funktionstypen: fn, originalFn und timelionFn. Dann iteriert es über die args (welche query.arguments sind) und löst die Argumente auf:

In diesem Fall ist das Argument in args ein Objekt vom Typ 'literal', daher wird nur der Literalwert zurückgegeben:

Tatsächlich ist der Wert in ein Promise verpackt.

Das indizierte Argument ist ein Argument mit einem byName-Objekt, das ein q-Array enthält:

Danach ruft die invoke-Funktion functionDef.fn auf, eine Funktion der Timelion-Klasse, mit allen indizierten Argumenten:

Die fn-Funktion ruft originalFn auf, das von config.fn stammt, wobei config tlConfig ist. Und diese Funktion ist fn der Klasse Datasource extends Timelion:

Wie wir sehen können, ruft sie eine weitere originalFn namens esFn auf, die ein seriesList-Objekt zurückgibt:

Diese Funktion wird verwendet, um "Daten aus einer Elasticsearch-Instanz abzurufen", indem die callWithRequest-Funktion aufgerufen wird.
Das seriesList sieht in Datasource.fn so aus:

Und in Timelion.fn sieht es so aus:

Nach der Vorverarbeitung des Sheets ist die Chain-Liste:

Und der Code ruft resolveChainList auf, um die Chain-Liste aufzulösen:

Jede Chain wird einem Promise zugeordnet, das von einer Funktion zurückgegeben wird, die invoke mit 'first' als fnName und der Chain selbst als args aufruft.
Die Funktionsdefinition wird wieder anhand des Namens ermittelt:

Dieses Mal erhält resolveArguments args als Array und ruft resolveArguments rekursiv auf, um jedes Argument aufzulösen. Aber wir haben nur ein Argument mit Index 0. Und dieses Argument ist ein Objekt mit type == chain.
Im Switch-Case wird also die invokeChain-Funktion aufgerufen und das Chain-Objekt übergeben:

Innerhalb der invoke chain wird das chain-Array mit shift bearbeitet. Die shift-Funktion gibt das entfernte Element zurück, sodass link das es-Objekt ist:

Da der Typ von es nicht 'chain' ist (sondern 'function') und !result truthy ist, ruft der Code die invoke-Funktion mit 'first' als fnName und dem es-Objekt als args auf:

Dieses Mal ist der Ablauf derselbe wie oben, daher springen wir zum Aufruf von invokeChain mit link als props-Objekt:

Wieder ruft der Code die invoke-Funktion mit 'first' als fnName und dem props-Objekt als args auf:

Die originalFn von props ist jetzt firstFn anstelle von fn wie bei es und akzeptiert nur ein Argument:

Diese ursprüngliche Funktion wird schließlich die anfällige unflatten-Funktion aufrufen. Tatsächlich wird sie zuerst die fn von Timelion aufrufen. Dann ruft fn die originalFn von props auf:

Die firstFn von props wird die Eigenschaften inputSeries und global im byName-Objekt von args auslassen:

Die an unflatten übergebenen data werden also sein:
{
label.__proto__.env.AAA: "a"
}
Beweis:

Mit props(label.__proto__.env.AAA="a") sehen die data so aus:

Bei der ersten Iteration existiert prop = '' nicht, daher wird cur[''] = {} zugewiesen. Das bedeutet, dass result ebenfalls eine Eigenschaft namens '' mit einem leeren Objekt als Wert erhält. Danach wird cur mit {} belegt, dem Rückgabewert der vorherigen Zuweisung (cur und cur[prop] zeigen auf dasselbe Objekt):

Jetzt wird prop zu 'label':

Wie zuvor erhält result eine 'label'-Eigenschaft und prop wird zu __proto__:

Bei der dritten Iteration wird cur zu seinem Prototyp, da __proto__ in jedem Objekt existiert:

Danach wird prop zu env.
Da cur auf Object.prototype zeigt, wird env als leeres Objekt zu Object.prototype hinzugefügt und cur referenziert ebenfalls auf env innerhalb von Object.prototype.env.

Danach wird die Schleife beendet und cur[prop] = data[p] entspricht env[AAA] = 'a'.

Der Object.prototype ist mit env verschmutzt:

Die
fork-Funktion gibt einenChildProcesszurück, der verwendet werden kann, umspawnaufzurufen
Ändern Sie den Port beim Aufruf der fork-Funktion auf 9669 (ein anderer Port), und dann können wir den Hauptprozess normal debuggen:

Simulieren Sie den verschmutzten Object.prototype und springen Sie vor die Schleife:

Wie wir sehen können, ist env aufgrund der Prototype-Pollution gleichbedeutend mit { AAA: 'a' }. Genauer gesagt, wird env options.env zugewiesen. Und da options diese Eigenschaft nicht definiert, wird der Wert seines Prototyps verwendet.
Nach der Schleife:

Irgendwie kann Canvas mit den ungültigen Umgebungsvariablen (AAA=a, env=[Object object]) keinen neuen Socket erstellen:

Aber der Kindprozess kann trotzdem erstellt werden:

Beim normalen Ablauf wird eine neue WebSocket-Verbindung erstellt:

Versuchen Sie es nun mit dem Payload und springen Sie in die normalizeSpawnArguments-Funktion:

Wie wir sehen können, ist der Stack-Trace derselbe.
Aber das env ist verschmutzt:

Voilà!
Die envPairs nach der Schleife:

Und wir haben die Reverse Shell erhalten:

Nach dem Aufruf von normalizeSpawnArguments in der spawn-Funktion:

Wie wir sehen können, enthalten die envPairs unseren Payload. Außerdem ruft spawn danach die spawn-Funktion in internal/child_process.js auf:

Die Variable options wird an this._handle.spawn übergeben:

Wobei this._handle ein Handle auf einen Prozess ist:

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