
PoC et analyse pour l'exécution de code à distance (RCE) par pollution de prototype dans Kibana (CVE-2019-7609).
Les versions de Kibana antérieures à 5.6.15 et 6.6.1 contiennent une faille d'exécution de code arbitraire dans le visualiseur Timelion. Un attaquant ayant accès à l'application Timelion pourrait envoyer une requête tentant d'exécuter du code Javascript. Cela pourrait éventuellement permettre à un attaquant d'exécuter des commandes arbitraires avec les permissions du processus Kibana sur le système hôte.
CVSS:

Blog: https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/
Payload: https://github.com/mpgn/CVE-2019-7609
Configuration :
Utilisation :
Après l'analyse de la feuille :

Après le prétraitement de la feuille, nous avons un objet queries :

Ensuite, nous parcourons le tableau queries (qui est converti en tableau en utilisant la méthode values) et passons à invoke les query.function et query.arguments pour invoquer la fonction.
Dans la fonction invoke, nous obtenons d'abord la définition de la fonction basée sur son nom (query.function) :

Cette définition de fonction inclut trois types de fonction : fn, originalFn et timelionFn. Ensuite, elle parcourt les args (qui sont query.arguments) et résout les arguments :

Dans ce cas, l'argument dans args est un objet de type 'literal' donc il retourne simplement la valeur littérale :

En réalité, la valeur est enveloppée dans une promesse.

L'argument indexé est un argument avec un objet byName qui contient un tableau q à l'intérieur :

Après cela, la fonction invoke appellera functionDef.fn, qui est une fonction de la classe Timelion, avec tous les arguments indexés :

La fonction fn appellera originalFn, qui provient de config.fn où config est tlConfig. Et cette fonction est fn de la classe Datasource extends Timelion :

Comme nous pouvons le voir, elle appelle une autre originalFn nommée esFn qui retourne un objet seriesList :

Cette fonction est utilisée pour "Extraire des données d'une instance Elasticsearch" en appelant la fonction callWithRequest.
Le seriesList ressemble à cela dans Datasource.fn :

Et il ressemble à cela dans Timelion.fn :

Après le prétraitement de la feuille, la liste des chaînes est :

Et le code appellera resolveChainList pour résoudre la liste des chaînes :

Chaque chaîne sera mappée à une Promise retournée par une fonction qui invoke avec 'first' comme fnName et la chaîne elle-même comme args.
La définition de fonction est à nouveau obtenue par le nom :

Cette fois, resolveArguments reçoit args comme un tableau et appellera resolveArguments récursivement pour résoudre chaque argument. Mais nous n'avons qu'un seul argument avec l'index 0. Et cet argument est un objet avec type == chain.
Donc, dans le switch case, il appellera la fonction invokeChain et lui passera l'objet chaîne :

À l'intérieur de la chaîne d'invocation, il shift le tableau chain. La fonction shift retournera l'élément supprimé, donc link sera l'objet es :

Parce que le type de es n'est pas 'chain' (c'est 'function') et que !result est truthy, le code appelle la fonction invoke avec 'first' comme fnName et l'objet es comme args :
Cette fois, la procédure est la même que ci-dessus, donc passons à l'appel de invokeChain avec link qui est l'objet props :

Encore une fois, le code appelle la fonction invoke avec 'first' comme fnName et l'objet props comme args :

Le originalFn de props est maintenant firstFn au lieu de fn comme es et n'accepte qu'un seul argument :

Cette fonction originale appellera éventuellement la fonction vulnérable unflatten. En réalité, elle appellera d'abord le fn de Timelion. Ensuite, fn appellera le originalFn de props :

Le firstFn de props omettra les propriétés inputSeries et global dans l'objet byName de args :

Donc les data passées à unflatten seront :
{
label.__proto__.env.AAA: "a"
}
Preuve :

Utilisez props(label.__proto__.env.AAA="a"). Les data ressembleront à ceci :

À la première itération, prop = '' n'existe pas, il assignera cur[''] = {}. Cela signifie que result aura également une propriété nommée '' avec un objet vide comme valeur. Ensuite, cur est assigné avec {}, qui est la valeur de retour de l'assignation précédente (cur et cur[prop] pointent tous deux vers le même objet) :

Maintenant, prop deviendra 'label' :

Comme avant, result aura la propriété 'label' et prop deviendra __proto__ :

Avec la troisième itération, cur deviendra son prototype car __proto__ existe toujours dans chaque objet :

Après cela, prop devient env.
Ensuite, comme cur pointe vers Object.prototype, il ajoutera env à Object.prototype en tant qu'objet vide et cur référence également env à l'intérieur de Object.prototype.env .

Après cela, la boucle se terminera et cur[prop] = data[p] sera équivalent à env[AAA] = 'a'.

Le Object.prototype est pollué avec env :

La fonction
forkretourne unChildProcessqui peut être utilisé pour invoquerspawn
Changez le port lors de l'invocation de la fonction fork en 9669 (un autre port) et ensuite nous pouvons déboguer le processus principal normalement :

Simulez le Object.prototype pollué et sautez avant la boucle :

Comme nous pouvons le voir, env est équivalent à { AAA: 'a' } à cause de la pollution du prototype. Plus précisément, env est assigné à options.env. Et options ne définit pas cette propriété, donc il utilisera la valeur de son prototype.
Après la boucle :

D'une manière ou d'une autre, avec les variables d'environnement invalides (AAA=a, env=[Object object]), canvas ne peut pas créer un nouveau socket :

Mais le processus enfant peut toujours être créé :

Avec un flux normal, il créera une nouvelle connexion WebSocket :

Essayez maintenant avec le payload et sautez dans normalizeSpawnArguments :

Comme nous pouvons le voir, la trace de la pile est la même.
Mais, env est pollué :

Voilà !
Les envPairs après la boucle :

Et nous avons obtenu le reverse shell :

Après avoir appelé normalizeSpawnArguments dans la fonction spawn :

Comme nous pouvons le voir, les envPairs contiennent notre payload. De plus, après cela, spawn appellera la fonction spawn à l'intérieur de internal/child_process.js :

La variable options sera passée à this._handle.spawn :

Où this._handle est un handle vers un processus :

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