
क्रोम रेंडरर 1day RCE एसिंक स्टैक ट्रेस में टाइप कन्फ्यूज़न के माध्यम से (v8ctf सबमिशन)
यह भेद्यता एक दूरस्थ हमलावर को क्रोम रेंडरर प्रक्रिया के अंदर मनमाना कोड निष्पादित करने की अनुमति देती है।
एशिंक स्टैक ट्रेस हैंडलिंग कोड में अपर्याप्त प्रकार जाँच थी।
यह FunctionContext और NativeContext के बीच टाइप कन्फ्यूज़न की ओर ले जाता है, जिससे JSGlobalProxy->hash मान तक अवैध पहुँच होती है।
हीप स्प्रेइंग के साथ, हमलावर एक नकली एशिंक स्टैक फ्रेम इंजेक्ट करने और fakeobj प्रिमिटिव का निर्माण करने में सक्षम था।
fakeobj प्रिमिटिव का उपयोग करके, हमलावर क्रोम रेंडरर प्रक्रिया में मनमाना कोड निष्पादन प्राप्त करने में सक्षम था।
आप हमारे TyphoonCon 2024 स्लाइड्स देख सकते हैं।
एशिंक्रोनस जावास्क्रिप्ट में सबसे महत्वपूर्ण विशेषताओं में से एक है। अतीत में, एशिंक्रोनस कोड को त्रुटि स्टैक के साथ डीबग करना कठिन था क्योंकि एशिंक फ़ंक्शन त्रुटि स्टैक में कैप्चर नहीं किए जाते थे। निलंबित एशिंक फ़ंक्शन कॉल स्टैक में नहीं बल्कि इवेंट लूप के कॉलबैक कतार में संग्रहीत होते हैं, इसलिए त्रुटि स्टैक में एशिंक फ़ंक्शन शामिल नहीं होता है। इस समस्या को हल करने के लिए, V8 त्रुटि स्टैक में एशिंक फ़ंक्शन को कैप्चर करने के लिए "async stack trace" सुविधा (V8 v7.3 से डिफ़ॉल्ट रूप से) प्रदान करता है। ([v8 blog], [v8 docs])
"Promise.all Resolve Element Closure" Promise.all फ़ंक्शन में इनपुट प्रॉमिसेज़ को हल करने के लिए एक सहायक फ़ंक्शन है।
Promise.all फ़ंक्शन प्रॉमिसेज़ की एक सरणी लेता है और एक प्रॉमिस लौटाता है जो सभी इनपुट प्रॉमिसेज़ हल होने पर हल होता है।
"Promise.all Resolve Element Closure" Promise.all फ़ंक्शन में प्रत्येक इनपुट प्रॉमिस का एक रिज़ॉल्व हैंडलर है।
फ़ंक्शन की भूमिका इनपुट प्रॉमिस को हल करना और परिणाम सरणी में पूर्ति मान को संग्रहीत करना है।
फ़ंक्शन के बारे में 2 बिंदु ध्यान देने योग्य हैं:
FunctionContext होता है, और इसे कॉल करने के बाद इसमें NativeContext होता है। (v8 कोड)बग वर्ग: FunctionContext और NativeContext के बीच टाइप कन्फ्यूज़न
भेद्यता विवरण:
भेद्यता को पहले से निष्पादित "Promise.all Resolve Element Closure" फ़ंक्शन या समान आंतरिक बिल्ट-इन फ़ंक्शन के साथ एक एशिंक स्टैक ट्रेस कैप्चर करके ट्रिगर किया जा सकता है। इस एक्सप्लॉइट में, मैंने "Promise.all Resolve Element Closure" फ़ंक्शन का उपयोग उदाहरण के रूप में किया।
जब जावास्क्रिप्ट कोड में कोई त्रुटि उत्पन्न होती है, V8 स्टैक से त्रुटि स्टैक कैप्चर करता है और वर्तमान माइक्रोटास्क से एशिंक स्टैक फ्रेम जोड़ता है [1]।
CallSiteBuilder builder(isolate, mode, limit, caller);
VisitStack(isolate, &builder);
// If --async-stack-traces are enabled and the "current microtask" is a
// PromiseReactionJobTask, we try to enrich the stack trace with async
// frames.
if (v8_flags.async_stack_traces) {
CaptureAsyncStackTrace(isolate, &builder);
}
CaptureAsyncStackTrace फ़ंक्शन [[2]] प्रॉमिस श्रृंखला को देखता है और एशिंक कॉल प्रकार (जैसे, await, Promise.all, Promise.any) के अनुसार एशिंक स्टैक फ्रेम जोड़ता है।
नीचे CaptureAsyncStackTrace फ़ंक्शन का स्निपेट है जो Promise.all मामले को संभालता है:
} else if (IsBuiltinFunction(isolate, reaction->fulfill_handler(),
Builtin::kPromiseAllResolveElementClosure)) {
Handle<JSFunction> function(JSFunction::cast(reaction->fulfill_handler()),
isolate);
Handle<Context> context(function->context(), isolate);
Handle<JSFunction> combinator(context->native_context()->promise_all(),
isolate);
builder->AppendPromiseCombinatorFrame(function, combinator);
// Now peak into the Promise.all() resolve element context to
// find the promise capability that's being resolved when all
// the concurrent promises resolve.
int const index =
PromiseBuiltins::kPromiseAllResolveElementCapabilitySlot;
Handle<PromiseCapability> capability(
PromiseCapability::cast(context->get(index)), isolate);
if (!IsJSPromise(capability->promise())) return;
promise = handle(JSPromise::cast(capability->promise()), isolate);
} else if (
प्रॉमिस श्रृंखला को देखते समय, यदि reaction->fulfill_handler "Promise.all Resolve Element Closure" बिल्ट-इन फ़ंक्शन है, तो यह त्रुटि स्टैक में एशिंक प्रॉमिस कॉम्बिनेटर फ्रेम जोड़ता है।
फिर, यह function->context->capability->promise तक पहुँचकर अगले प्रॉमिस पर जाता है।
समस्या यह है कि फ़ंक्शन मान लेता है कि "Promise.all Resolve Element Closure" फ़ंक्शन अभी तक निष्पादित नहीं हुआ है।
यदि "Promise.all Resolve Element Closure" फ़ंक्शन पहले ही निष्पादित हो चुका है, तो संदर्भ FunctionContext से NativeContext में बदल जाता है।
यह CaptureAsyncStackTrace फ़ंक्शन में FunctionContext और NativeContext के बीच टाइप कन्फ्यूज़न की ओर ले जाता है।
PoC बनाना:
भेद्यता को ट्रिगर करने की रणनीति इस प्रकार है:
FunctionContext से NativeContext में बदलने के लिए "Promise.all Resolve Element Closure" फ़ंक्शन को स्पष्ट रूप से कॉल करें।मैंने JS स्क्रिप्ट स्तर पर "Promise.all Resolve Element Closure" फ़ंक्शन प्राप्त करने के लिए Promise.all के लिए सिंक्रोनस प्रॉमिस रिज़ॉल्विंग पैटर्न का उपयोग किया।
मैंने test262 परीक्षण मामलों से पैटर्न उधार लिया।
फ़ंक्शन को स्पष्ट रूप से कॉल करने के बाद, भेद्यता को ट्रिगर करने के लिए, मैंने एक नई प्रॉमिस श्रृंखला तैयार करने और आंतरिक बिल्ट-इन फ़ंक्शन को एक प्रॉमिस के फ़ुलफ़िल हैंडलर के रूप में सेट करने के लिए [शून्य-लागत एशिंक स्टैक ट्रेस दस्तावेज़][v8 docs] में नमूना कोड का उपयोग किया।
अंत में, जब त्रुटि उत्पन्न होती है, तो एशिंक स्टैक ट्रेस पहले से निष्पादित "Promise.all Resolve Element Closure" फ़ंक्शन के साथ फ़ुलफ़िल हैंडलर के रूप में कैप्चर किया जाता है, जिससे FunctionContext और NativeContext के बीच टाइप कन्फ्यूज़न होता है।
यहाँ PoC कोड है: poc.js
(एक्सप्लॉइट प्रिमिटिव, एक्सप्लॉइट रणनीति, एक्सप्लॉइट तकनीक और एक्सप्लॉइट प्रवाह की शर्तें यहाँ परिभाषित हैं।)
एक्सप्लॉइट प्रिमिटिव: fakeobj प्रिमिटिव
एक्सप्लॉइट रणनीति: टाइप कन्फ्यूज़न बग से fakeobj प्रिमिटिव बनाने के लिए, मैंने निम्नलिखित रणनीति का उपयोग किया: