
PoC e analisi per Kibana Prototype Pollution RCE (CVE-2019-7609).
Le versioni di Kibana precedenti alla 5.6.15 e alla 6.6.1 contengono una vulnerabilità di esecuzione di codice arbitrario nel visualizzatore Timelion. Un attaccante con accesso all'applicazione Timelion potrebbe inviare una richiesta che tenta di eseguire codice JavaScript. Ciò potrebbe potenzialmente consentire a un attaccante di eseguire comandi arbitrari con i permessi del processo Kibana sul sistema host.
CVSS:

Blog: https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/
Payload: https://github.com/mpgn/CVE-2019-7609
Configurazione:
Utilizzo:
Dopo il parsing del foglio:

Dopo la pre-elaborazione del foglio, abbiamo un oggetto queries:

Quindi, iteriamo sull'array queries (che viene convertito in un array usando il metodo values) e passiamo a invoke il query.function e query.arguments per invocare la funzione.
Nella funzione invoke, prima otteniamo la definizione della funzione in base al suo nome (query.function):

Questa definizione di funzione include tre tipi di funzione: fn, originalFn e timelionFn. Poi, itera sugli args (che sono query.arguments) e risolve gli argomenti:

In questo caso, l'argomento in args è un oggetto con tipo 'literal', quindi restituisce semplicemente il valore letterale:

In realtà, il valore è avvolto in una promise.

L'argomento indicizzato è un argomento con un oggetto byName che contiene un array q al suo interno:

Dopodiché, la funzione invoke chiamerà functionDef.fn, che è una funzione della classe Timelion, con tutti gli argomenti indicizzati:

La funzione fn chiamerà originalFn, che proviene da config.fn dove config è tlConfig. E quella funzione è fn della classe Datasource extends Timelion:

Come possiamo vedere, chiama un altro originalFn chiamato esFn che restituisce un oggetto seriesList:

Quella funzione è usata per "Estrarre dati da un'istanza Elasticsearch" chiamando la funzione callWithRequest.
Il seriesList appare così in Datasource.fn:

E appare così in Timelion.fn:

Dopo la pre-elaborazione del foglio, la lista delle catene è:

E il codice chiamerà resolveChainList per risolvere la lista delle catene:

Ogni catena viene mappata a una Promise restituita da una funzione invocata con 'first' come fnName e la catena stessa come args.
La definizione della funzione viene ottenuta di nuovo tramite il nome:

Questa volta, resolveArguments riceve args come array e chiamerà resolveArguments ricorsivamente per risolvere ogni argomento. Ma abbiamo un solo argomento con indice 0. E quell'argomento è un oggetto con type == chain.
Quindi, nel ramo switch, chiamerà la funzione invokeChain e passerà l'oggetto catena al suo interno:

All'interno della catena di invocazione, eseguirà shift sull'array chain. La funzione shift restituirà l'elemento rimosso, quindi link sarà l'oggetto es:

Poiché il tipo di es non è 'chain' (è 'function') e !result è truthy, il codice chiama la funzione invoke con 'first' come fnName e l'oggetto es come args:
Questa volta, la procedura è la stessa di sopra, quindi saltiamo alla chiamata di invokeChain in cui link è l'oggetto props:

Ancora una volta, il codice chiama la funzione invoke con 'first' come fnName e l'oggetto props come args:

L'originalFn di props ora è firstFn invece di fn come per es e accetta solo un argomento:

Quella funzione originale finirà per chiamare la vulnerabile funzione unflatten. In realtà, chiamerà prima la fn di Timelion. Poi fn chiamerà l'originalFn di props:

La firstFn di props ometterà le proprietà inputSeries e global nell'oggetto byName di args:

Quindi il valore data passato a unflatten sarà:
{
label.__proto__.env.AAA: "a"
}
Prova:

Usa props(label.__proto__.env.AAA="a"). Il data apparirà così:

Alla prima iterazione, prop = '' non esiste; assegnerà cur[''] = {}. Ciò significa che anche result avrà una proprietà chiamata '' con un oggetto vuoto come valore. Dopodiché, cur viene assegnato a {}, che è il valore di ritorno dell'assegnazione precedente (cur e cur[prop] puntano entrambi allo stesso oggetto):

Ora, prop diventerà 'label':

Come prima, result avrà la proprietà 'label' e prop diventerà __proto__:

Con la terza iterazione, cur diventerà il suo prototipo poiché __proto__ esiste sempre in ogni oggetto:

Dopodiché, prop diventa env.
Quindi, poiché cur punta a Object.prototype, aggiungerà env a Object.prototype come oggetto vuoto e cur fa riferimento anche a env all'interno di Object.prototype.env.

Dopodiché, il ciclo terminerà e cur[prop] = data[p] sarà equivalente a env[AAA] = 'a'.

Il Object.prototype viene contaminato con env:

La funzione
forkrestituisce unChildProcessche può essere usato per invocarespawn
Cambia la porta a 9669 (un'altra porta) quando invochi la funzione fork e poi possiamo eseguire il debug del processo principale normalmente:

Simula il Object.prototype contaminato e salta a prima del ciclo:

Come possiamo vedere, env è equivalente a { AAA: 'a' } a causa della prototype pollution. Più precisamente, env viene assegnato a options.env. E options non definisce quella proprietà, quindi utilizzerà il valore del suo prototipo.
Dopo il ciclo:

In qualche modo, con le variabili d'ambiente non valide (AAA=a, env=[Object object]), canvas non riesce a creare un nuovo socket:

Ma il processo figlio può comunque essere creato:

Con il flusso normale, creerà una nuova connessione WebSocket:

Ora prova con il payload ed entra in normalizeSpawnArguments:

Come possiamo vedere, lo stack trace è lo stesso.
Ma env è contaminato:

Voila!
Gli envPairs dopo il ciclo:

E abbiamo ottenuto la reverse shell:

Dopo aver chiamato normalizeSpawnArguments nella funzione spawn:

Come possiamo vedere, envPairs contiene il nostro payload. Inoltre, dopo di ciò, spawn chiamerà la funzione spawn all'interno di internal/child_process.js:

La variabile options verrà passata a this._handle.spawn:

Dove this._handle è un handle a un processo:

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