Safari信息泄露漏洞分析

前言

Javascript中的數組和數組對象一直都是編程人員優化的主要目標,一般來說,數組只會包含一些基本類型數據,比如說32位整數或字符等等。因此,每個引擎都會對這些對象進行某些優化,並提升不同元素類型的訪問速度和密集型表示。

在JavaScriptCore中,JavaScript引擎是在WebKit中實現的,其中每一個存儲在對象中的元素都代表着一個IndexingType值,一個8位整數代表一套Flag組合,具體的參數定義可以在IndexingType.h中找到。接下來,引擎會檢測一個對象中indexing的類型,然後決定使用哪一條快速路徑,其中最重要的一種indexing類型就是ArrayWithUndecided,它表示的是所有元素均爲未定義(undefined),而且沒有存儲任何實際的值。在這種情況下,引擎爲了提升性能,會讓這些元素保持未初始化。

分析

下面,我們一起看一看舊版本中實現Array.prototype.concat的代碼(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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章