概述
在刚刚过去的微软补丁日,微软发布了Internet Explorer(IE)的更新,以解决CVE-2018-8460漏洞。该漏洞影响所有Windows版本中的IE 11,属于微软描述的通用“内存损坏”类别漏洞,但实际上是源于CSS机制的Double-Free漏洞,最终导致允许远程代码执行。这一漏洞非常典型,值得我们进行深入分析。
微软漏洞通告请参见: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8460 。
漏洞详情
该漏洞存在于处理程序对属性cssText写入的代码中。这一DOM属性允许开发人员获取或设置CSS属性字符串,在单次操作中检索或替换样式对象的全部内容。
在为DOM中元素样式设置cssText属性期间,IE将根据需要,触发DOMAttributeModified事件。如果攻击者对DOMAttributeModified事件进行操作,并将其用于重新输入cssText属性设置时,将会出现问题。具体来说,该机制中没有考虑在执行过程中可能发生的情况,如果在此期间重新进入,将会对样式对象的内容进行额外的更改。在实际中,当cssText的(外部)调用完成后,元素的CStyleAttrArray处于损坏状态,其中的两个元素包含指向内存中相同CSS值字符串的指针:
CStyleAttrArray中的字符串指针复制过程,实际上不应该发生。最终,当这个CStyleAttrArray被销毁时,析构函数将会在每个指针上调用HeapFree。由于列表中有一个重复的指针,因此HeapFree将使用这个指针调用该值两次,就形成了Double-Free漏洞。需要注意的是,由于分配是在Windows进程堆上进行的,因此MemGC就不再适用。
以下是触发Double-Free漏洞的PoC:
https://www.thezdi.com/s/CVE-2018-8460-PoC-s34g.txt
实现漏洞利用
要实现Double-Free类型的漏洞利用,实际上特别具有挑战性。到目前为止,Windows堆管理器已经经过加固,可以自动检测到HeapFree的参数是已经释放的堆块,并且会立即关闭进程,从而防止进一步损坏。
然而,实际上,确实存在Double-Free类型漏洞的利用方法。如果我们有办法,在第一次HeapFree发生和第二次HeapFree发生之间的时间里,在该地址引起一次新的分配,就可以完全绕过上述缓解措施。
在这种情况下,第二次HeapFree发生时,堆管理器会将相应的块视为已经分配的内存,而不是已释放的内存。因此,就不会发现这一过程存在任何问题,然后将继续释放分配(第二次),从而进一步产生可以实现漏洞利用的条件。
为了实现上述操作,我们采取如下措施:
1、触发第一次HeapFree,释放位于地址A的对象X。
2、引起一次新的分配,新对象Y将位于地址A的位置。实际上,可能需要喷射(Spray)许多对象,才能确保其中有一个对象是在地址A的位置。
3、触发第二次HeapFree,由于此时Y位于A位置,所以将会释放对象Y。
4、引起一次新的分配,新对象Z是位于地址A的第三个对象。和上面一样,可能需要进行喷射(Spray)。
5、使用对象Y的触发代码。该代码将会对对象Z进行操作,而对象Z目前位于地址A的位置。这样一来,我们就可以利用这一漏洞。
如果能够实现上述步骤,所造成的结果是非常具有破坏性的,因为攻击者可以任意控制对象Y和对象Z的类型。攻击者很可能利用这一方式,进行信息获取或控制流劫持,从而导致完整的远程代码执行,无需与其他任何漏洞组合使用。
但是,反观上述步骤,我们发现其中的步骤2最难实现。在实际中,如何能保证步骤2成功呢?我们首先来看看CAttrArray::Free中的代码,其中发生了两次释放:
简而言之,我们有一个循环。在每次循环期间,都会将数据从第一个数组元素复制到临时结构(v13)中,调用memmove将其余元素向下移动一个位置(数组元素1,经过移动后就是数组元素0,以此类推),然后在已删除的数组元素中找到的指针上调用ProcessHeapFree。照此循环,直至数组为空。实际上,循环中还包含一些其他代码(此处未体现),这些代码是为了响应其他类型的数组元素而执行的。根据推测,由于其他代码具有较高的复杂性,因此必须要使用这种低效的O(n^2)算法来清除数组。
由于包含重复指针的两个数组元素在数组中彼此相邻,因此在对ProcessHeapFree的两次调用之间执行的代码非常少。那么,我们如何能保证在两次释放之间产生分配呢?
请注意,在对ProcessHeapFree的连续调用之间,将会调用memmove。假设我们可以将这个memmove变成一个耗时较长的操作,就会在两次对ProcessHeapFree的调用之间赢得时间延迟。如果我们使用第二个执行线程,在第一个线程清除数组的同时快速连续调用HeapAlloc,那么就可以赢得竞态条件(Race Condition),并在两次ProcessHeapFree调用之间执行所需的分配。
只有当攻击者能够控制memmove的长度,使该长度变得非常大时,这种方法才可行。当我进一步研究这一特定漏洞时,所获得的结果令我感到非常惊讶。要增加memmove的大小,只需要向元素中添加额外的自定义样式属性,这样一来,CStyleAttrArray的大小将会增加。举例来说,攻击者可以添加名为-ms-xyz1、-ms-xyz2之类的样式。这一点,已经在上面的PoC中有所说明。此外,我还发现了IE中存在对样式属性数量的限制,最多只能有100万个样式。由于每个属性在CStyleAttrArray中由16字节元素表示,因此我们最多也只能将memmove扩大到16MB,但这并不足以产生足够的时间延迟。实际上,我们可以通过一些额外的技巧来实现漏洞利用,例如:使用大量的分配线程,提高赢得竞态条件的几率。总而言之,这种技术在理论上可行,但在具体实践过程中能够可靠利用,还有待观察。
在这种情况下,攻击者可以寻找一种修改PoC的方式,使CStyleAttrArray中的重复指针不再位于相邻的元素中,从而提高成功几率。这样一来,在两次调用ProcessHeapFree之间,就有更多的代码被执行,也就赢得了更长的时间。当然,将产生回调的代码添加到脚本中是最理想的方案,这样我们就能轻松执行分配,无需再进行竞态。
总结
可能大家还会发现,这一漏洞针对服务器是“中危”,但针对客户端是“高危”。其原因在于,IE默认在服务器上开启了增强安全配置(Enhanced Security Configuration,ESC)。如果服务器上禁用了此功能,那么漏洞级别也应该相应提升。微软在“漏洞利用指数”中,给这个漏洞评了1,这意味着在接下来的一个月中,可能会出现对该漏洞的实际利用。该漏洞由ca0nguyen提交给ZDI(ZDI-18-1137)。在对这个漏洞的研究过程中,我发现IE存在一个OOB写入漏洞,这可能是微软之所以将该漏洞归类于“内存损坏”的原因。这两个漏洞都是通过CVE-2018-8460实现的修复,因此需要提醒用户,务必及时更新补丁。
发表评论
您还未登录,请先登录。
登录