Safari資訊洩露漏洞分析
前言
Javascript中的陣列和陣列物件一直都是程式設計人員優化的主要目標,一般來說,陣列只會包含一些基本型別資料,比如說32位整數或字元等等。因此,每個引擎都會對這些物件進行某些優化,並提升不同元素型別的訪問速度和密集型表示。
在JavaScriptCore中,JavaScript引擎是在WebKit中實現的,其中每一個儲存在物件中的元素都代表著一個IndexingType值,一個8位整數代表一套Flag組合,具體的引數定義可以在IndexingType.h中找到。接下來,引擎會檢測一個物件中indexing的型別,然後決定使用哪一條快速路徑,其中最重要的一種indexing型別就是ArrayWithUndecided,它表示的是所有元素均為未定義(undefined),而且沒有儲存任何實際的值。在這種情況下,引擎為了提升效能,會讓這些元素保持未初始化。
分析
下面,我們一起看一看舊版本中實現Array.prototype.concat的程式碼( Prototype.cpp#L1296" rel="nofollow,noindex" target="_blank">ArrayPrototype.cpp ):
EncodedJSValueJSC_HOST_CALL arrayProtoPrivateFuncConcatMemcpy(ExecState* exec) { ... unsigned resultSize =checkedResultSize.unsafeGet(); IndexingType firstType =firstArray->indexingType(); IndexingType secondType =secondArray->indexingType(); IndexingType type =firstArray->mergeIndexingTypeForCopying(secondType); // [[ 1 ]] if (type == NonArray ||!firstArray->canFastCopy(vm, secondArray) || resultSize >=MIN_SPARSE_ARRAY_INDEX) { ... } JSGlobalObject* lexicalGlobalObject =exec->lexicalGlobalObject(); Structure* resultStructure =lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(type); if(UNLIKELY(hasAnyArrayStorage(resultStructure->indexingType()))) return JSValue::encode(jsNull()); ASSERT(!lexicalGlobalObject->isHavingABadTime()); ObjectInitializationScopeinitializationScope(vm); JSArray* result =JSArray::tryCreateUninitializedRestricted(initializationScope, resultStructure,resultSize); if (UNLIKELY(!result)) { throwOutOfMemoryError(exec, scope); return encodedJSValue(); } if (type == ArrayWithDouble) { [[ 2 ]] double* buffer =result->butterfly()->contiguousDouble().data(); memcpy(buffer,firstButterfly->contiguousDouble().data(), sizeof(JSValue) *firstArraySize); memcpy(buffer + firstArraySize,secondButterfly->contiguousDouble().data(), sizeof(JSValue) *secondArraySize); } else if (type != ArrayWithUndecided) { ...
這個函式主要用來判斷結果陣列[[1]]的indexing型別,我們可以看到,如果indexing型別為ArrayWithDouble,它將會選擇[[2]]作為快速路徑。接下來,我們看一看:
mergeIndexingTypeForCopying的實現程式碼,這個函式主要負責在Array.prototype.concat被呼叫時,判斷結果陣列的indexing型別:
inlineIndexingType JSArray::mergeIndexingTypeForCopying(IndexingType other) { IndexingType type = indexingType(); if (!(type & IsArray && other& IsArray)) return NonArray; if (hasAnyArrayStorage(type) ||hasAnyArrayStorage(other)) return NonArray; if (type == ArrayWithUndecided) return other; [[ 3 ]] ...
我們可以看到在這種情況下,有一個輸入陣列的indexing型別為ArrayWithUndecided,結果indexing型別將會是另一個數組的indexing型別。因此,如果我們我們用一個indexing型別為ArrayWithUndecided的陣列和另一個indexing型別為ArrayWithDouble的陣列去呼叫Array.prototype.concat方法的話,我們將會按照快速路徑[[2]]執行,並將兩個陣列進行拼接。
這段程式碼並不能保證這兩個“butterfly”(JavaScript引擎攻擊技術裡的一種概念,詳情請參考【 這篇文章】 )在程式碼呼叫memcpy之前能夠正確初始化。這也就意味著,如果我們能夠找到一條允許我們建立一個未初始化陣列並將其傳遞給Array.prototype.concat的程式碼路徑,那我們就能夠在堆記憶體中擁有一個包含了未初始化值的陣列物件了,而且它的indexing型別還不是ArrayWithUndecided。從某種程度上來說,這個安全問題跟lokihardt在2017年報告的一個 舊漏洞 有些相似,只不過利用方式不同。
在建立這種陣列物件時,可以利用NewArrayWithSize DFG JIT的操作碼來實現,在對FTLLowerDFGToB3.cpp中FTL所實現的allocateJSArray操作碼進行分析之後,我們可以看到這個陣列將會包含未初始化的值。引擎根本不需要對陣列進行初始化,因為這個陣列的indexing型別為ArrayWithUndecided。
ArrayValuesallocateJSArray(LValue publicLength, LValue vectorLength, LValue structure,LValue indexingType, bool shouldInitializeElements = true, boolshouldLargeArraySizeCreateArrayStorage = true) { [ ... ] initializeArrayElements( indexingType, shouldInitializeElements ?m_out.int32Zero : publicLength, vectorLength, butterfly); ... voidinitializeArrayElements(LValue indexingType, LValue begin, LValue end, LValuebutterfly) { if (begin == end) return; if (indexingType->hasInt32()) { IndexingType rawIndexingType =static_cast<IndexingType>(indexingType->asInt32()); if (hasUndecided(rawIndexingType)) return;// [[ 4 ]]
語句new Array(n)在被FTL JIT編譯時將會觸發[[4]],然後返回一個indexing型別為ArrayWithUndecided的陣列,其中就包含未初始化的元素。
漏洞利用
清楚了之前所介紹的漏洞原理之後,想必觸發這個漏洞也並非難事:我們可以不斷重複呼叫一個使用new Array()方法來建立陣列的函式,然後呼叫concat方法將這個陣列和一個只包含double型別資料的陣列進行拼接。在呼叫夠足夠次數之後,FTL編譯器將會對其進行編譯。
這份【 漏洞利用程式碼 】可以利用這個漏洞來洩漏一個目標物件的記憶體地址,實現機制是通過我們所建立的物件進行記憶體噴射,在觸發這個漏洞之後,我們就能夠從程式碼所返回的陣列中找到目標物件的地址了。
總結
這個漏洞目前已經在iOS 12和macOS Mojave的最新版本(Safari)中修復了,該漏洞的CVE編號為 CVE-2018-4358 。
* 參考來源: phoenhex ,FB小編Alpha_h4ck編譯,轉載請註明來自FreeBuf.COM