1. Vulnerability Description
1.1 The Issue
MS Edge CDOMTextNode::get_data type confusion
特别构造的JavaScript脚本可以触发Microsoft Edge的type confusion,使得可以像访问字符串一样访问C++对象。 这可能导致信息泄露,例如允许攻击者确定指向其他对象或函数的指针的值。
1.2 Affect version
Microsoft Edge 20.10240.16384.0
1.3 Timeline
01/12/2016 Advisory disclosed
01/12/2016 +0 days Countermeasure disclosed
01/12/2016 +0 days SecurityTracker entry created
01/12/2016 +0 days VulnerabilityCenter entry assigned
01/13/2016 +1 days VulnerabilityCenter entry created
01/14/2016 +1 days VulDB entry created
01/17/2016 +3 days VulnerabilityCenter entry updated
01/19/2016 +2 days VulDB last update
2. Technical description and PoC
2.1 Description
在DOM树中将一个节点作为子节点加到另一个节点时,Edge首先从其父节点中删除该节点,触发DOMNodeRemoved事件,然后重新附加该节点作为另一个节点的最后一个子节点。
而在DOMNodeRemoved事件发生时,JavaScript的事件处理器可以更改DOM树,我们尝试在触发DOMNodeRemoved事件时向同一个父节点插入另一个文本子节点,这个操作在事件期间完成,因此该文本子节点在触发事件的节点之前作为子节点附加。
而在触发DOMNodeRemoved事件处理程序之前,代码似乎确定了节点应该被附加的位置,因此最开始插入的节点在父文本节点之前而不是之后被插入。
因为bug的存在,在完成所有这些操作后,DOM树已被破坏。这可以通过检查文本节点的.nextSibling属性是文本节点本身来确认, 即DOM树中有一个循环。
另一个效果是,读取文本节点的nodeValue将导致类型混淆。这里Edge访问文本节点中存储的文本数据时,实际访问的却是一个C++对象。这样可以让攻击者读取存储在这个C++对象中的数据,其中包含各种指针。
2.2 JavaScript PoC
Skylined给出了一个读取并显示DOM树对象的部分内容的PoC。该PoC已经在x64系统上进行了测试,允许攻击者绕过堆ASLR,读取堆指针。
读取的数据量可以由攻击者控制,并且可以读取分配给C++对象的内存之外的数据。攻击者可能能够使用一些堆的技巧将其他对象与C++DOM树对象中的有用信息放置在内存中,并从第二个对象读取数据。
exp如下
1 | <html> |
2.3 Code Analyze
上面这个利用流程,比较关键的几个函数是CDOMTextNode::get_data、CDOMTextNode::get_length
读取数据的函数是CDOMTextNode::get_data,代码如下
1 | __int64 __fastcall CDOMTextNode::get_data(CDOMTextNode *this, unsigned __int16 **a2) |
在函数中有一次调用CDOMTextNode::get_length(this_rdi, (__int32 *)&ui)
代码如下,其中a2的值是ui,也就是0,而CDOMTextNode::IsPositioned(this)返回值为真,即进入了第二个逻辑,在该逻辑中进行了长度的处理
获取长度后,则在get_data函数中,在memcpy_s(&(*v2)[v7], 2i64 * (signed int)(ui - v7), v11, 2i64 * v20)
这行代码利用memcpy_s不断读取内存中的值至长度到达之前get_length函数返回的值位置。
这里读取的是v11指向中保存地址指向的值,那我们再上溯,v11 = Tree::TextNode::Text(v10, 0, &v20);
,其中v10的值是Tree::TextNode::TextNodeFromDOMTextNode返回的一个结构体指针,而调用该函数的参数是rcx。
1 | __int64 __fastcall CDOMTextNode::get_length(CDOMTextNode *this, __int32 *a2) |
2.4 Dynamic Analysis
对get_data下断点后开始单步跟踪,发现这里get_length函数的返回值和节点oExistingChild的长度相关,也就是说,这里出现了一个bug,在本来应该读取textnode的长度的时候,返回了一个和oExistingChild长度相关的数值,即我们可以一定程度上控制读取的数据长度,当然,这里数据如果太长,在读取的时候会触发一个访问错误,导致进程崩溃无法继续读取。
最后结合动态调试和代码分析发现length是从结构体指针处偏移0x1c的位置指向的指针指向的位置中取出来,即first_struct->other_struct->length。而读取的地址位置则是结构体指针偏移0xC的位置。