
PoC وتحليل لـ Kibana Prototype Pollution RCE (CVE-2019-7609).
إصدارات 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. ثم، يقوم بالتكرار على args (وهي query.arguments) ويحل الوسائط:

في هذه الحالة، الوسيط في args هو كائن من النوع 'literal' لذلك يقوم فقط بإرجاع القيمة الحرفية:

في الواقع، القيمة مغلفة في وعد (Promise).

الوسيط المفهرس هو وسيط يحتوي على كائن byName بداخله مصفوفة q:

بعد ذلك، ستقوم دالة invoke باستدعاء functionDef.fn، وهي دالة من فئة Timelion، مع جميع الوسائط المفهرسة:

ستستدعي دالة fn الدالة originalFn، التي تأتي من config.fn حيث config هو tlConfig. وهذه الدالة هي fn من فئة Datasource extends Timelion:

كما نرى، تستدعي دالة originalFn أخرى تُسمى esFn والتي تُرجع كائن seriesList:

تُستخدم هذه الدالة لـ "سحب البيانات من مثيل elasticsearch" عن طريق استدعاء دالة callWithRequest.
يبدو seriesList هكذا في Datasource.fn:

ويبدو هكذا في Timelion.fn:

بعد معالجة الورقة مسبقًا، قائمة السلسلة (chain) هي:

وسوف يستدعي الكود resolveChainList لحل قائمة السلسلة:

سيتم ربط كل سلسلة بوعد (Promise) يتم إرجاعه من دالة تستدعي invoke مع 'first' كـ fnName والسلسلة نفسها كـ args.
يتم الحصول على تعريف الدالة مرة أخرى بالاسم:

هذه المرة، تتلقى resolveArguments args كمصفوفة وستقوم باستدعاء resolveArguments بشكل متكرر لحل كل وسيط. لكن لدينا وسيط واحد فقط بالفهرس 0. وهذا الوسيط هو كائن به type == chain.
لذا، في حالة التبديل (switch)، ستستدعي دالة invokeChain وتمرر كائن السلسلة إليها:

داخل استدعاء السلسلة، ستقوم بـ shift على مصفوفة chain. دالة shift ستعيد العنصر الذي تمت إزالته لذا سيكون link هو كائن es:

لأن نوع es ليس 'chain' (إنه 'function') و !result هو صحيح (truthy)، يستدعي الكود دالة invoke مع 'first' كـ fnName وكائن es كـ args:
هذه المرة، الإجراء هو نفسه كما سبق لذا نتخطى إلى استدعاء invokeChain مع link هو كائن props:

مرة أخرى، يستدعي الكود دالة invoke مع 'first' كـ fnName وكائن props كـ args:

originalFn لـ props الآن هي firstFn بدلاً من fn مثل es وتقبل وسيطًا واحدًا فقط:

تلك الدالة الأصلية ستستدعي في النهاية الدالة الضعيفة unflatten. في الواقع، ستستدعي أولاً دالة fn الخاصة بـ Timelion. ثم fn ستستدعي originalFn الخاصة بـ props:

firstFn لـ props ستتجاهل الخصائص inputSeries و global في كائن byName الخاص بـ args:

لذا فإن data التي تم تمريرها إلى unflatten ستكون:
{
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__:

مع التكرار الثالث، ستصبح cur هي النموذج الأولي (prototype) الخاص بها لأن __proto__ موجود دائمًا في كل كائن:

بعد ذلك، تصبح prop هي env.
ثم، نظرًا لأن cur يشير إلى Object.prototype، فسيتم إضافة env إلى Object.prototype ككائن فارغ و cur تشير أيضًا إلى env داخل Object.prototype.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:

كما نرى، تتبع الاستدعاءات (stack trace) هو نفسه.
لكن، env ملوث:

ها هو!
envPairs بعد الحلقة:

وحصلنا على الصدفة العكسية (reverse shell):

بعد استدعاء normalizeSpawnArguments في دالة spawn:

كما نرى، envPairs يحتوي على حمولتنا. أيضًا، بعد ذلك، ستستدعي spawn دالة spawn داخل internal/child_process.js:

سيتم تمرير المتغير options إلى this._handle.spawn:

حيث this._handle هو مقبض (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];