Mozilla 在2018年12月通过mfsa2018-29发布了火狐浏览器64位的新版本,该版本修复了几个严重的安全问题,其中包括 CVE-2018-18492,这个CVE是和select元素相关的一个use-after-free(UAF)漏洞。我们之前讨论过UAF这种漏洞,并且我们可以看到厂商已经实现全面的保护以尝试消除它们。但即使在今天,在web浏览器中发现和UAF相关的漏洞也不是太罕见,所以了解这些漏洞对于发现和修复这些错误是十分重要的。这篇博客主要展现了这个CVE中UAF漏洞的更多细节,以及为了解决这个漏洞而发布的补丁。
漏洞触发
以下一段poc代码可以用于触发这个漏洞:
在一个受该漏洞影响版本的火狐浏览器上运行这段代码,得到以下的崩溃信息和报错信息:
可以发现,当对一个填满0xe5e5e5e5的内存地址解引用时,产生了读取访问冲突。这个值是jemalloc用来“毒化”已释放内存的,所谓“毒化”就是为了方便内存诊断,使用一个可识别的模式来填充已释放内存。最好是这个用于填充的值不对应任何可访问的地址,如此可以导致,任何解引用已填充内存的尝试(比如use-after-free)都会立即产生特定的崩溃。
根源分析
Poc代码包括6行,我们一行一行来分析:
- 创建一个div元素
- 创建一个option元素
- 这个option元素被附加到div元素,现在该div元素是option元素的父元素
- 为该div元素添加一个事件监听器 DOMNodeRemoved,这意味着如果删除了这个option节点,就会调用我们放在这里的函数。
- 创建一个select元素
这里深入分析一下,在JavaScript语言,当创建一个select元素时,xul.dll!NS_NewHTMLSelectElement函数会为这个select元素分配一个0x118字节大小的对象:
可以看到,在最后跳转到了mozilla::dom::HTMLSelectElement::HTMLSelectElement函数执行,该函数内容如下
这个函数对新分配对象的各个字段进行了初始化,此外,还创建了一个0x38字节大小的另一个对象,该对象类型为HTMLOptionsCollection。默认情况下,每一个select元素都会有一个options集合。再来看最后一行。
- 第二步创建的option元素被移动到了select元素的options集合中。该操作会导致mozilla::dom::HTMLOptionsCollection::IndexedSetter函数被调用。下图是在IDA中看到的程序逻辑:
这里浏览器会做一些检查,例如,如果option的索引大于当前option集合的长度,options集合会调用mozilla::dom::HTMLSelectElement::SetLength函数来扩大集合。在上面的poc中,第六行设置的是[0],所以索引值为0。在上图的蓝色块部分执行了一个检查,如果要设置的索引不等于option集合的计数,则执行右分支。我们poc中索引值为0,option集合的计数也为0,所以会执行左分支,因此执行到了nsINode::ReplaceOrInsertBefore函数,可以在下图的红色块区域看到:
在 nsINode::ReplaceOrInsertBefore函数中,调用了nsContentUtils::MaybeFireNodeRemoved函数来通知要被删除对象的父亲对象,如果存在对该事件的监听:
我们在第4行设置了对div元素的DOMNodeRemoved事件监听器,于是这里的函数会被触发。在这个函数里,首先sel变量值会被设置为0,这将删除对select元素的最后一个引用。之后,该函数创建了一个巨大的数组缓冲区,这个操作会产生内存压力,导致垃圾回收程序开始工作。此时,select元素对象会被释放由于其现在不存在任何的引用,这块释放的内存会被填充0xe5e5e5e5。最后,函数调用alert来刷新挂起的异步任务,在nsContentUtils::MaybeFireNodeRemoved函数返回时,被释放的select对象会被用来读取一个指针,从而导致读取访问冲突:
有趣的是,如果执行的是右分支,还是会调用完全相同的函数(nsINode::ReplaceOrInsertBefore),但是在调用之前,AddRef函数会被调用以增加select对象的引用计数,因此,不会出现use-after-free的问题:
补丁
Mozilla通过d4f3e119ae841008c1be59e72ee0a058e3803cf3修复了该漏洞。主要变化是options集合中对select元素对象的弱引用被替换为了强引用:
总结
尽管UAF漏洞是一种众所周知的问题,但是对于大多数浏览器来说,它仍然是一个问题,就在几个月前,针对谷歌Chrome的攻击就使用了UAF漏洞。当然,UAF漏洞也存在与浏览器之外。Linux内核发布了一个补丁来解决由UAF漏洞引起的拒绝服务问题。了解UAF漏洞如何发生时检测他们的关键,与缓冲区溢出漏洞类似,我们不太可能在软件中看到UAF漏洞的终结,但是,适当的编码规范和安全开发实践可以帮助我们消除或至少是减小未来UAF漏洞产生的影响。
原文链接:
https://www.zerodayinitiative.com/blog/2019/7/1/the-left-branch-less-travelled-a-story-of-a-mozilla-firefox-use-after-free-vulnerability