Firefox UAF漏洞分析

 

0x00 前言

2018年12月,Mozila通过mfsa2018-29发布了Firefox 64,修复了多个安全问题,其中就包含CVE-2018-18492,这是一个与select元素有关的UAF(use-after-free,释放后重用)漏洞,最早由Nils发现并报告。之前我们已经讨论过UAF问题,也看到厂商采取了多种保护措施想消除这些问题。但直到目前为止,我们还是经常能在web浏览器中发现与UAF相关的漏洞,因此理解这些问题对查找及修复漏洞来说至关重要。在本文中,我们将与大家分享这个UAF漏洞以及相关补丁的具体细节。

 

0x01 触发漏洞

我们可以使用如下PoC触发该漏洞:

图1. PoC

在存在漏洞Firefox版本中运行该PoC,我们可以看到crash时的栈跟踪状态如下:

图2. 崩溃时的栈跟踪状态

从图中可知,当解析填充0xe5e5e5e5的某个内存地址时,会出现read access violation(读取访问冲突)。0xe5e5e5e5jemalloc用来“污染”已释放内存的一个值。这里的“污染”指的是以一种可识别模式来填充已被释放的内存,这样便于分析。正常情况下,我们选择的填充模式不要对应于可访问的地址,这样任何尝试从已填充内存中解析值的操作(比如UAF)都会导致crash。

 

0x02 漏洞分析

PoC包含6行代码,让我们来逐行分析:

  • 创建一个div元素
  • 创建一个option元素
  • option元素附加到div元素,现在这个div元素是option元素的父节点
  • DOMNodeRemoved事件监听器添加到div元素中。这意味着如果option节点被移除,就会调用我们在这里添加的函数
  • 创建一个select元素

这里再深入分析一下:

当使用JavaScript创建select元素时,xul.dll!NS_NewHTMLSelectElement函数就会获得控制权,该函数会为这个select元素分配大小为0x188字节的一个对象:

图3. xul.dll!NS_NewHTMLSelectElement函数

如上所示,在代码底部会跳转至mozilla::dom::HTMLSelectElement::HTMLSelectElement函数。

图4. mozilla::dom::HTMLSelectElement::HTMLSelectElement函数

在该函数中会初始化新分配对象的各种字段。需要注意的是,这里也会分配大小为0x38字节的另一个对象,将其初始化为HTMLOptionsCollection对象。因此,每个Select元素默认情况下都会对应一个options collection(选项集合)。让我们看一下PoC的最后一行。

  • 将第二行创建的option元素移动到select元素的options collection中

在JavaScript中执行该操作将导致mozilla::dom::HTMLOptionsCollection::IndexedSetter函数被调用(大家可以在图2的栈跟踪状态中看到该函数被调用)。

图5. IDA中观察到的程序逻辑

这里浏览器会执行一些检查操作。比如,如果选项索引值大于当前options collection的长度值,那么就会调用mozilla::dom::HTMLSelectElement::SetLength函数来扩充options collection容量。在PoC中,这个长度值为为0(参考图1第6行的[0])。然后浏览器会进入图5的红色方框处的检测逻辑。如果待设置的索引值不等于options collection的选项数,那么就会进入右分支。在PoC中,我们使用的索引值为0,而选项数也为0,因此会执行左分支。因此浏览器会调用nsINode::ReplaceOrInsertBefore函数,如下红框所示:

图6. 调用nsINode::ReplaceOrInsertBefore函数

nsINode::ReplaceOrInsertBefore函数中,浏览器会调用nsContentUtils::MaybeFireNodeRemoved函数,通知父节点关于子节点移除的相关事件(如果父节点在监听这类事件)。

图7. 调用nsContentUtils::MaybeFireNodeRemoved函数

由于我们在PoC第4行(参考图1)中,在div元素上设置了DOMNodeRemoved事件监听器,因此这里浏览器就会触发我们设置的函数。在这个函数中,第一个sel变量会被设置为0,该操作会移除对select元素的最后一个引用。接下来,函数会创建一个巨大的数组缓冲区,这会给内存带来较大负担,触发垃圾回收机制(garbage collector)。此时会释放select元素对象,因为已经没有关于该对象的任何引用。现在被释放的内存已被0xe5e5e5e5填充。最后,函数会调用alertflush挂起的异步任务。从nsContentUtils::MaybeFireNodeRemoved函数返回后,被释放的select对象会被用来读取指针,触发read access violation:

图8. 触发Read Access Violation

这里需要注意的是,如果浏览器执行的是右分支,那么还是会调用同一个函数(nsINode::ReplaceOrInsertBefore),但在调用该函数之前,会使用AddRef函数来增加select对象的引用计数。如果是这样操作,就不会出现UAF问题:

图9. 通过AddRef函数避免出现UAF问题

 

0x03 补丁分析

Mozilla通过d4f3e119ae841008c1be59e72ee0a058e3803cf3改动修复了这个漏洞。这里最主要的改动是修改options collection中对select元素的弱引用逻辑,替换成强引用:

图10. 补丁细节

 

0x04 总结

虽然UAF是大家都比较熟悉的一个问题,但在许多浏览器中依然存在。在几个月之前,有攻击者成功利用了Google Chrome浏览器中的UAF漏洞。UAF漏洞也不局限于浏览器,Linux内核之前也推出过一个补丁,用来修复由UAF导致的拒绝服务问题。澄清UAF触发原理是检测这类漏洞的关键所在。与缓冲区溢出问题一样,我们认为软件中无法完全避免UAF。然而,通过正确的编程以及安全的开发实践,在未来我们可以进一步消除、或者缓解UAF所带来的影响。

大家可以关注@hosselot以及我们的官方推特了解最新的漏洞利用技术及安全补丁情况。

(完)