
PoC y análisis para Kibana Prototype Pollution RCE (CVE-2019-7609).
Las versiones de Kibana anteriores a 5.6.15 y 6.6.1 contienen una falla de ejecución de código arbitrario en el visualizador Timelion. Un atacante con acceso a la aplicación Timelion podría enviar una solicitud que intente ejecutar código JavaScript. Esto podría permitir que un atacante ejecute comandos arbitrarios con los permisos del proceso de Kibana en el sistema host.
CVSS:

Blog: https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/
Payload: https://github.com/mpgn/CVE-2019-7609
Configuración:
Uso:
Después de analizar la hoja:

Después del preprocesamiento de la hoja, tenemos un objeto queries:

Luego, recorremos el array queries (que se convierte en un array mediante el método values) y pasamos a invoke el query.function y query.arguments para invocar la función.
En la función invoke, primero obtenemos la definición de la función según su nombre (query.function):

Esta definición de función incluye tres tipos de función: fn, originalFn y timelionFn. Luego, recorre los args (que son query.arguments) y resuelve los argumentos:

En este caso, el argumento en args es un objeto con tipo 'literal', por lo que simplemente devuelve el valor literal:

En realidad, el valor está envuelto en una promesa.

El argumento indexado es un argumento con un objeto byName que tiene un array q en su interior:

Después, la función invoke llamará a functionDef.fn, que es una función de la clase Timelion, con todos los argumentos indexados:

La función fn llamará a originalFn, que proviene de config.fn, donde config es tlConfig. Y esa función es fn de la clase Datasource extends Timelion:

Como podemos ver, llama a otro originalFn llamado esFn, que devuelve un objeto seriesList:

Esa función se usa para "extraer datos de una instancia de Elasticsearch" llamando a la función callWithRequest.
El seriesList se ve así en Datasource.fn:

Y se ve así en Timelion.fn:

Después del preprocesamiento de la hoja, la lista de cadenas es:

Y el código llamará a resolveChainList para resolver la lista de cadenas:

Cada cadena se asignará a una Promise que devuelve una función que invoke con 'first' como fnName y la propia cadena como args.
La definición de la función se obtiene nuevamente por el nombre:

Esta vez, resolveArguments recibe args como un array y llama a resolveArguments recursivamente para resolver cada argumento. Pero solo tenemos un argumento con índice 0. Y ese argumento es un objeto con type == chain.
Entonces, en el caso del switch, llamará a la función invokeChain y le pasará el objeto de la cadena:

Dentro de la cadena de invocación, se aplicará shift al array chain. La función shift devolverá el elemento eliminado, por lo que link será el objeto es:

Debido a que el tipo de es no es 'chain' (es 'function') y !result es verdadero, el código llama a la función invoke con 'first' como fnName y el objeto es como args:
Esta vez, el procedimiento es el mismo que el anterior, así que pasamos a la llamada de invokeChain con link como objeto props:

De nuevo, el código llama a la función invoke con 'first' como fnName y el objeto props como args:

El originalFn de props ahora es firstFn en lugar de fn como es, y solo acepta un argumento:

Esa función original eventualmente llamará a la vulnerable función unflatten. En realidad, primero llamará al fn de Timelion. Luego, fn llamará al originalFn de props:

El firstFn de props omitirá las propiedades inputSeries y global en el objeto byName de args:

Por lo tanto, el data pasado a unflatten será:
{
label.__proto__.env.AAA: "a"
}
Prueba:

Usa props(label.__proto__.env.AAA="a"). El data se verá así:

En la primera iteración, prop = '' no existe, por lo que asignará cur[''] = {}. Esto significa que result también tendrá una propiedad llamada '' con un objeto vacío como valor. Después de eso, cur se asigna con {}, que es el valor de retorno de la asignación anterior (cur y cur[prop] apuntan al mismo objeto):

Ahora, prop se convertirá en 'label':

Igual que antes, result tendrá la propiedad 'label' y prop se convertirá en __proto__:

Con la tercera iteración, cur se convertirá en su prototipo, ya que __proto__ siempre existe en todos los objetos:

Después de eso, prop se convierte en env.
Entonces, dado que cur apunta a Object.prototype, agregará env a Object.prototype como un objeto vacío y cur también hará referencia a env dentro de Object.prototype.env.

Después de eso, el bucle saldrá y cur[prop] = data[p] será equivalente a env[AAA] = 'a'.

El Object.prototype está contaminado con env:

La función
forkdevuelve unChildProcessque se puede usar para invocarspawn
Al invocar la función fork, cambiamos el puerto a 9669 (otro puerto) y entonces podemos depurar el proceso principal con normalidad:

Simulamos el Object.prototype contaminado y saltamos a justo antes del bucle:

Como podemos ver, env es equivalente a { AAA: 'a' } debido a la contaminación de prototipos. Más específicamente, env se asigna a options.env. Y options no define esa propiedad, por lo que usará el valor de su prototipo.
Después del bucle:

De algún modo, con las variables de entorno no válidas (AAA=a, env=[Object object]), canvas no puede crear un nuevo socket:

Pero el proceso hijo aún puede crearse:

Con el flujo normal, se creará una nueva conexión WebSocket:

Ahora probemos con el payload y saltemos a normalizeSpawnArguments:

Como podemos ver, la traza de pila es la misma.
Pero el env está contaminado:

¡Voilà!
Los envPairs después del bucle:

Y obtuvimos la reverse shell:

Después de llamar a normalizeSpawnArguments en la función spawn:

Como podemos ver, envPairs contiene nuestro payload. Además, después de eso, spawn llamará a la función spawn dentro de internal/child_process.js:

La variable options se pasará a this._handle.spawn:

Donde this._handle es un manejador de un proceso:

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