CVE-2019-1367: JScript Use-after-Free in Internet Explorer

Posted by Maddie Stone, Ivan Fratric, & Clément Lecigne (2020-07-27)

Disclosure or Patch Date: 23 September 2019
Product: Microsoft Internet Explorer
Affected Versions: For Windows 10 1903, KB4515384 and previous
First Patched Version: For Windows 10 1903, KB4524147 
Issue/Bug Report: N/A
Patch CL: N/A
Bug-Introducing CL: N/A
Proof-of-Concept: Minimal POC is included below.
Exploit Sample: be8fdfce55ea701e19ab5dd90ce4104ff11ee3b4890b292c46567d9670b63b82
Access to the exploit sample? Yes
Reporter: Clément Lecigne of Google’s Threat Analysis Group

Bug Class: use-after-free (UAF)
Vulnerability Details:
The vulnerability is a member of the use-after-free bug class in JScript where variables (represented by the VAR structure) aren’t properly tracked by the garbage collector. In this case, the arguments object is not tracked by the garbage collector during the Array.sort callback. Thus, during the Array.sort callback, it is possible to assign a variable to the arguments object, have it garbage-collected (as long as it is not referenced anywhere else) and still access it later, causing the use-after-free.

How do you think you would have found this bug? Based on the blogpost from Google TAG, this bug was exploited by the same actor who exploited CVE-2018-8653. CVE-2018-8653 is a use-after-free in isPrototypeOf callback when the this variable is not tracked by the garbage collector. Therefore it’s likely that the actor found this bug by performing variant analysis on CVE-2018-8653, looking for vulnerabilities in the same bug class.

(Historical/present/future) context of bug: 
  • Jan 2018 - P0 researcher identifies bug class: JScript variables (in VAR structure) not tracked by garbage collector. Finds numerous bugs (P0 1504, P0 1505, P0 1506, P0 1587)  through fuzzer, which is open-sourced.
  • Dec 2018 - First use of an exploit exploiting this bug class is found by Google’s Threat Analysis Group (TAG): CVE-2018-8653. Bug couldn’t be found by the open-sourced fuzzer because the vulnerability uses features not supported by fuzzer at the time.
  • Sept 2019 - CVE-2019-1367 (this bug) is discovered by Google TAG. 
  • Oct 2019 - Google TAG discovers fix for CVE-2019-1367 is incomplete. Project Zero discovers trivial variant. 
  • Nov 2019 - The incomplete patch and the variant are patched as CVE-2020-1429.
  • Jan 2020 - TAG discovers another in-the-wild exploit sample (CVE-2020-0674) using a trivial variant of CVE-2019-1367/CVE-2019-1429. Microsoft issues advisory. It is used as the sandbox escape with CVE-2019-17026 on Firefox. 
  • Feb 2020 - Microsoft patches the exploited 0-day as CVE-2020-0674. 
  • CVE-2018-8653, CVE-2019-1367, CVE-2019-1429, and CVE-2020-0674 all used the sample exploitation method. 

Is the exploit method known? Yes
Exploit method: 
Given the UAF bug described above, the exploit frees the VAR structures (always allocated as a block of 100 VARs) and replaces the freed memory with a controllable object property name, which is a technique first demonstrated in P0 1587 and also used in CVE-2018-8653. This gives an attacker a powerful primitive: the ability to fake JScript variables. 

From there, the exploit constructs a fake RegExp object, which is used to achieve arbitrary memory read-write. The exploit directly replaces regexp.code with their custom compiled RegExp bytecode giving them a write-what-where primitive. The exploit uses regexp.source (unused bSTR) for the read primitive.

The same exploit is used twice: Once to achieve code execution inside the browser, and the second time to escape the browser sandbox using the WPAD service (which runs as a privileged process). This is also a publicly known exploitation technique described here.

Areas/approach for variant analysis:
We took two approaches to this variant analysis: manual analysis and a fuzzer. In the manual analysis approach, we manually attempted to free function arguments in every JScript callback that we knew. In the fuzzer approach, we ran another fuzzing sessions with a modified JScript.dll to more easily check this bug class. The modifications included:
  1. Freed VARs are modified so that accessing them would cause an immediate crash, and
  2. Freed VARs are never allocated again
Found variants: 
  • P0 1947 (CVE-2019-1429): Use-after-free where members of the arguments object aren’t tracked by the garbage collector during the toJSON callback.

Structural improvements:
  • Bug classes should be fixed comprehensively, not just fixing each vulnerability individually.
  • Quality and complete patches need to be prioritized. CVE-2019-1367 was not fixed the first time and the trivial variant also wasn’t patched. This gave the attackers another opportunity to exploit the bug against the users, which they did. Sharing proposed patches with the reporter could help identify these issues earlier. 
  • JScript and Internet Explorer are now considered “legacy” software. Remove them from being accessible by default in the Windows operating system to reduce the attack surface.

Potential detection methods for similar 0-days: 
  • Look for any scripts that want to use JScript as their JS engine outside of a local intranet.
  • Look for scripts that use the Enumerator object due to that being Microsoft specific and one of the known methods for exploiting the UAF to get remote code execution.
  • Look for scripts that attempt to trigger CollectGarbage.

Other references: 

Appendix: Minimal Proof-of-Concept
<!-- saved from url=(0014)about:internet -->
<meta http-equiv="X-UA-Compatible" content="IE=8"></meta>
<script language="Jscript.Encode">

    var spray = new Array();

    function F() {
        // 2. Create a bunch of objects
        for (var i = 0; i < 20000; i++) spray[i] = new Object();

        // 3. Store a reference to one of them in the arguments array
        //    The arguments array isn't tracked by garbage collector
        arguments[0] = spray[5000];

        // 4. Delete the objects and call the garbage collector
        //    All JSCript variables get reclaimed... 
        for (var i = 0; i < 20000; i++) spray[i] = 1;
        CollectGarbage();

        // 5. But we still have reference to one of them in the
        //    arguments array
        alert(arguments[0]);
    }

    // 1. Call sort with a custom callback
    [1,2].sort(F);

</script>

No comments:

Post a Comment