Safari信息泄露漏洞分析

前言

Javascript中的数组和数组对象一直都是编程人员优化的主要目标,一般来说,数组只会包含一些基本类型数据,比如说32位整数或字符等等。因此,每个引擎都会对这些对象进行某些优化,并提升不同元素类型的访问速度和密集型表示。

[[248537]]

在JavaScriptCore中,JavaScript引擎是在WebKit中实现的,其中每一个存储在对象中的元素都代表着一个IndexingType值,一个8位整数代表一套Flag组合,具体的参数定义可以在IndexingType.h中找到。接下来,引擎会检测一个对象中indexing的类型,然后决定使用哪一条快速路径,其中最重要的一种indexing类型就是ArrayWithUndecided,它表示的是所有元素均为未定义(undefined),而且没有存储任何实际的值。在这种情况下,引擎为了提升性能,会让这些元素保持未初始化。

分析

下面,我们一起看一看旧版本中实现Array.prototype.concat的代码(ArrayPrototype.cpp):

  1. EncodedJSValueJSC_HOST_CALL arrayProtoPrivateFuncConcatMemcpy(ExecState* exec)  
  2. {  
  3.     ...  
  4.     unsigned resultSize =checkedResultSize.unsafeGet();  
  5.     IndexingType firstType =firstArray->indexingType();  
  6.     IndexingType secondType =secondArray->indexingType();  
  7.     IndexingType type =firstArray->mergeIndexingTypeForCopying(secondType); // [[ 1 ]]  
  8.     if (type == NonArray ||!firstArray->canFastCopy(vm, secondArray) || resultSize >=MIN_SPARSE_ARRAY_INDEX) {  
  9.         ...  
  10.     }  
  11.     JSGlobalObject* lexicalGlobalObject =exec->lexicalGlobalObject();  
  12.     Structure* resultStructure =lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(type);  
  13.     if(UNLIKELY(hasAnyArrayStorage(resultStructure->indexingType())))  
  14.         return JSValue::encode(jsNull());  
  15.    ASSERT(!lexicalGlobalObject->isHavingABadTime());  
  16.     ObjectInitializationScopeinitializationScope(vm);  
  17.     JSArray* result =JSArray::tryCreateUninitializedRestricted(initializationScope, resultStructure,resultSize);  
  18.     if (UNLIKELY(!result)) {  
  19.         throwOutOfMemoryError(exec, scope);  
  20.         return encodedJSValue();  
  21.     }  
  22.     if (type == ArrayWithDouble) {  
  23.         [[ 2 ]]  
  24.         double* buffer =result->butterfly()->contiguousDouble().data();  
  25.         memcpy(buffer,firstButterfly->contiguousDouble().data(), sizeof(JSValue) *firstArraySize);  
  26.         memcpy(buffer + firstArraySize,secondButterfly->contiguousDouble().data(), sizeof(JSValue) *secondArraySize);  
  27.     } else if (type != ArrayWithUndecided) {  
  28. ... 

这个函数主要用来判断结果数组[[1]]的indexing类型,我们可以看到,如果indexing类型为ArrayWithDouble,它将会选择[[2]]作为快速路径。接下来,我们看一看:

mergeIndexingTypeForCopying的实现代码,这个函数主要负责在Array.prototype.concat被调用时,判断结果数组的indexing类型:

  1. inlineIndexingType JSArray::mergeIndexingTypeForCopying(IndexingType other)  
  2. {  
  3.     IndexingType type = indexingType();  
  4.     if (!(type & IsArray && other& IsArray))  
  5.         return NonArray;  
  6.     if (hasAnyArrayStorage(type) ||hasAnyArrayStorage(other))  
  7.         return NonArray;  
  8.     if (type == ArrayWithUndecided)  
  9.         return other; [[ 3 ]]  
  10. ... 

我们可以看到在这种情况下,有一个输入数组的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。

  1. ArrayValuesallocateJSArray(LValue publicLength, LValue vectorLength, LValue structure,LValue indexingType, bool shouldInitializeElements = true, boolshouldLargeArraySizeCreateArrayStorage = true)  
  2. {  
  3.     [ ... ]  
  4.     initializeArrayElements(  
  5.        indexingType,  
  6.        shouldInitializeElements ?m_out.int32Zero : publicLength, vectorLength,  
  7.        butterfly);  
  8. ...  
  9. voidinitializeArrayElements(LValue indexingType, LValue begin, LValue end, LValuebutterfly)  
  10. {  
  11.     if (begin == end)  
  12.         return;    
  13.     if (indexingType->hasInt32()) {  
  14.         IndexingType rawIndexingType =static_cast<IndexingType>(indexingType->asInt32());  
  15.         if (hasUndecided(rawIndexingType))  
  16.             return;  // [[ 4 ]] 

语句new Array(n)在被FTL JIT编译时将会触发[[4]],然后返回一个indexing类型为ArrayWithUndecided的数组,其中就包含未初始化的元素。

漏洞利用

清楚了之前所介绍的漏洞原理之后,想必触发这个漏洞也并非难事:我们可以不断重复调用一个使用new Array()方法来创建数组的函数,然后调用concat方法将这个数组和一个只包含double类型数据的数组进行拼接。在调用够足够次数之后,FTL编译器将会对其进行编译。

这份【漏洞利用代码】可以利用这个漏洞来泄漏一个目标对象的内存地址,实现机制是通过我们所创建的对象进行内存喷射,在触发这个漏洞之后,我们就能够从代码所返回的数组中找到目标对象的地址了。

总结

这个漏洞目前已经在iOS 12和macOS Mojave的最新版本(Safari)中修复了,该漏洞的CVE编号为CVE-2018-4358。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/151992.html<

赞 (0)
管理的头像管理
上一篇2025-03-13 19:28
下一篇 2025-03-13 19:30

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注