CVE-2018-8460: Exposing a Double Free in Internet Explorer for Code Execution

October 18, 2018 | Simon Zuckerbraun

This past Patch Tuesday, Microsoft released an update for Internet Explorer (IE) to address CVE-2018-8460. The vulnerability exists in IE 11 on all supported versions of Windows. It falls into the generic “Memory Corruption” category as described by Microsoft, but it’s actually a double free in the CSS mechanism that could allow remote code execution. It ends up being a fascinating case that deserves a further look.

The Vulnerability

The vulnerability lies in the code that handles writes to the property cssText. This DOM property allows a developer to retrieve or replace the entire contents of a style object in a single operation by getting or setting a string of CSS attributes.

During assignment to the cssText property for the style of an element in the DOM, IE will fire the DOMAttributeModified event as appropriate. The trouble begins when an attacker handles the DOMAttributeModified event and uses it as an opportunity to reenter the property setter for cssText. The code path that handles writes to cssText was not written to anticipate such a reentrance. Specifically, it does not anticipate that possibility that midway through its execution, reentrance will occur making additional changes to the contents of the style object. The net effect is that when the (outer) invocation of cssText completes, the element’s CStyleAttrArray is left in a corrupted state, with two of its elements containing pointers to the same CSS value string in memory:

This duplication of a string pointer within CStyleAttrArray isn’t supposed to happen. Ultimately, when this CStyleAttrArray is destroyed, the destructor will call HeapFree on each of the pointers. Since there is a duplicate pointer in the list, HeapFree will be called twice with this pointer value – a double free. Note that the allocation is made on the Windows process heap, so MemGC does not apply.

Here’s the PoC for triggering the double-free:

Towards Exploitation

Double-frees can be particularly challenging to exploit. By now, the Windows heap manager has been hardened to automatically detect that the parameter to HeapFree is an already-freed heap chunk, and prevent further damage by immediately shutting down the process.

Nevertheless, methods do exist to exploit double-frees. In particular, the mitigation described above can be completely bypassed if there is a way to cause a new allocation at that address in between the time the first HeapFree occurs and the time the second HeapFree occurs. In that case, when the second HeapFree happens, the heap manager will see the chunk as allocated memory and not as freed memory. Because of this, it will not detect that anything is amiss. It will then proceed to free the (second) allocation, potentially producing an exploitable condition further along.

To summarize and clarify the above paragraph, a strategy for exploiting a double-free is as follows:

       1 - Trigger the first HeapFree, freeing an object X residing at address A.
       2 - Cause a new allocation to take place, allocating a new object Y that will reside at address A. In practice, it may be necessary to spray many objects, in order to help ensure that one of them will reside at A.
       3 - Trigger the second HeapFree. This will free object Y, since Y resides at A. This free of Y is premature, as there may still be outstanding pointers to Y elsewhere.
       4 - Cause a new allocation to take place, allocating yet a third object that will reside at address A. We will call this object Z. (As above, a spray may be needed.)
       5 - Trigger code that uses object Y. This code will instead operate on object Z which is the current occupant of address A. We have arrived at an exploitable condition.

If these steps can be achieved, the result is rather devastating, since the attacker has a very great deal of control of the types of objects Y and Z. Most likely, this can be used for both information leaks and control flow hijack, leading to full RCE without the need to chain with any other vulnerabilities.

In Step 2 above, producing a reliable allocation in between the two frees is likely to be the hardest step to achieve. How might it be done in our case? Let us have a look at the code in CAttrArray::Free, where the two frees take place:

Put simply, we have a loop. During each iteration, it copies data from the first array element into a temporary structure (v13), calls memmove to shift the remaining elements down one position (so that array element 1 is now element 0, and so forth), and then calls ProcessHeapFree on the pointer that was found within the removed array element. The loop continues until the array is empty. Note that the loop also contains some other code, not shown here, which is executed in response to array elements of other types. Presumably, it is the complexity of that other code that made it necessary to use this inefficient O(n^2) algorithm for clearing the array.

Since the two array elements containing the duplicate pointers are adjacent to one another in the array, there is very little code that executes in between the two calls to ProcessHeapFree. How, then, can we produce an allocation in between the two frees?

Notice that in between successive calls to ProcessHeapFree, there will be a call to memmove. Suppose that we can make this memmove into a lengthy operation. That will introduce a time delay in between the two calls to ProcessHeapFree. If we then use a second thread of execution to make calls to HeapAlloc in rapid succession at approximately the same time the first thread is clearing the array, we may be able to win the race condition and perform our desired allocation in between the two ProcessHeapFree calls.

This approach is only feasible if the attacker can control the length of the memmove to make it very large. When I investigated this particular bug, the results surprised me: increasing the size of the memmove is simply a matter of adding additional custom style attributes to the element, thereby increasing the size of the CStyleAttrArray. For example, you can add styles named -ms-xyz1, -ms-xyz2, and so forth. This is illustrated in the PoC above. However, I found that IE imposes an arbitrary limit of 1 million style attributes. Since each attribute is represented in the CStyleAttrArray by a 16-byte element, the maximum memmove will be only 16MB. That does not amount to much of a delay. It may still be possible to use this approach by throwing in some additional tricks, such as using a large number of allocation threads to pump up the chances of winning the race condition. In short, this technique is sound in general, but it remains to be seen if it can be used in practice to exploit this particular vulnerability.

Perhaps a more promising approach in this case would be to find a way to modify the PoC so that the duplicate pointers in the CStyleAttrArray are not in adjacent elements, so that in between the two calls to ProcessHeapFree more lengthy code can run. It would be even better to add code that produces a callback into script so that we can perform an allocation without having to win a race at all.

Conclusion

You’ll also notice this is listed at Moderate on servers but Critical for clients. This is because IE running in Enhanced Security Configuration (ESC) on servers by default. Disabling this essentially turns a server into a client and ups the severity level accordingly. Microsoft gave this bug a 1 on the Exploit Index, which means exploitation of this bug is likely to occur within the next 30 days. This bug was submitted to the ZDI program by ca0nguyen (ZDI-18-1137). In investigating that case, I discovered an OOB write in IE, which may be one reason why Microsoft gave the whole thing the “Memory Corruption” label. Both were addressed by this CVE. If you haven’t installed the patch, you should definitely put that on your list of things to do.

As always, I can be found on Twitter at @HexKitchen, and follow the team for the latest in exploit techniques and security patches.