CVE-2018-3855
An exploitable stack-based buffer overflow exists in the DOC-to-HTML conversion functionality of the Hyland Perceptive Document Filters version 11.4.0.2647. A crafted .doc document can lead to a stack-based buffer, resulting in direct code execution.
Perceptive Document Filters 11.4.0.2647 - x86/x64 Windows/Linux Perceptive Document Filters 11.2.0.1732 - x86/x64 Windows/Linux
https://www.hyland.com/en/perceptive#docfilters
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-121: Stack-based Buffer Overflow
This vulnerability is present in the Hyland Document filter conversion, which is used for big data, eDiscovery, DLP, email archival, content management, business intelligence and intelligent capture services.
It can convert common formats, such as Microsoft’s document formats into more usable and easily viewed formats.
There is a vulnerability in the conversion process of a .doc document to HTML. A specially crafted .doc file can lead to a stack-based buffer overflow and remote code execution.
Let’s investigate this vulnerability. After we attempt to convert a malicious DOC using the Hyland library, we see the following state:
icewall@ubuntu:$ ./isys_doc2text --html -o /tmp/a ./storage/2fa87ae8d1beba2b25940dca4088afde^C
isys_doc2text 11.2.0.1732 Copyright (c) 1988-2015 Perceptive Software
[00000000] File type: Text (UTF8) (74); Capabilities: 0001 - /tmp/a
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>
<p>[1] File type: Microsoft Word (25); Capabilities: 15 - ./storage/2fa87ae8d1beba2b25940dca4088afde
<br />
</p>
</body>
</html>[00000000] Returned 201 characters
[00000000] File type: Microsoft Word (25); Capabilities: 000f - ./storage/2fa87ae8d1beba2b25940dca4088afde
Program received signal SIGSEGV, Segmentation fault.
0xf5603824 in intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*) () from ./libISYSreadershd.so
(rr) bt 10
#0 0xf5603824 in intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*) () from ./libISYSreadershd.so
#1 0xffffffff in ?? ()
#2 0xffffffff in ?? ()
#3 0xffffffff in ?? ()
#4 0xffffffff in ?? ()
#5 0xffffffff in ?? ()
#6 0xffffffff in ?? ()
#7 0xffffffff in ?? ()
#8 0xffffffff in ?? ()
#9 0xffffffff in ?? ()
(More stack frames follow...)
gdb-peda$ context
[----------------------------------registers-----------------------------------]
EAX: 0xffffffff
EBX: 0xf5a86aec --> 0x99e30c
ECX: 0xffffff01
EDX: 0xffffff01
ESI: 0x83
EDI: 0xff8f4790 --> 0xffffffff
EBP: 0xff8f45c8 --> 0xffffffff
ESP: 0xff8f45c0 --> 0xffffffff
EIP: 0xf5603824 (:Office::IOfficeShape::updateNumbering(long*, long*, int*)+68>: 0x5f5e0889)
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xf560381c <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+60>: jl 0xf5603810 <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+48>
0xf560381e <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+62>: inc DWORD PTR [edi+ecx*4]
0xf5603821 <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+65>: mov eax,DWORD PTR [ebp+0xc]
=> 0xf5603824 <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+68>: mov DWORD PTR [eax],ecx
0xf5603826 <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+70>: pop esi
0xf5603827 <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+71>: pop edi
0xf5603828 <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+72>: pop ebp
0xf5603829 <intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*)+73>: ret
[------------------------------------stack-------------------------------------]
0000| 0xff8f45c0 --> 0xffffffff
0004| 0xff8f45c4 --> 0xffffffff
0008| 0xff8f45c8 --> 0xffffffff
0012| 0xff8f45cc --> 0xffffffff
0016| 0xff8f45d0 --> 0xffffffff
0020| 0xff8f45d4 --> 0xffffffff
0024| 0xff8f45d8 --> 0xffffffff
0028| 0xff8f45dc --> 0xffffffff
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
As we can see, stack data has been completely overwritten by value 0xFFFFFFFF
inside the intermediate::Office::IOfficeShape::updateNumbering
function.
Showing this function in pseudo-code, it looks like this:
Line 1 _DWORD __cdecl intermediate::Office::IOfficeShape::updateNumbering(intermediate::Office::IOfficeShape *this, int *maxIndex, int *minIndex, int *array)
Line 2 {
Line 3 int _minIndexValue; // ecx
Line 4 int _maxnIndexValue; // edx
Line 5 int *v6; // eax
Line 6 int result; // eax
Line 7 int *v8; // eax
Line 8 int v9; // edx
Line 9
Line 10 _minIndexValue = *minIndex;
Line 11 if ( *minIndex <= 10 )
Line 12 {
Line 13 if ( _minIndexValue == -1 )
Line 14 {
Line 15 _minIndexValue = 0;
Line 16 *minIndex = 0;
Line 17 }
Line 18 }
Line 19 else
Line 20 {
Line 21 _minIndexValue = 10;
Line 22 *minIndex = 10;
Line 23 }
Line 24 _maxnIndexValue = *maxIndex;
Line 25 if ( _minIndexValue < *maxIndex )
Line 26 {
Line 27 if ( _maxnIndexValue != -1 )
Line 28 {
Line 29 v6 = &array[_maxnIndexValue];
Line 30 do
Line 31 {
Line 32 *v6 = -1;
Line 33 --_maxnIndexValue;
Line 34 --v6;
Line 35 }
Line 36 while ( _minIndexValue < _maxnIndexValue );
Line 37 }
Line 38 goto LABEL_7;
Line 39 }
Line 40
Line 41 (...)
The values of significant arguments are equal:
int minIndex = 0xffffff01 (-255)
int maxIndex = 0x0
With the arguments having the above mentioned values, we pass both checks at lines 25
and 27
and later assign a pointer to the first element of the array
table to the v6
variable.
Inside the loop at lines 30-36
just after the first iteration, the v6
pointer will be set to an address before the beginning of array
, causing an out-of-bounds write at line 32
, and will result in corruption of stack values.
The loop for the following parameters will be executed 255 times. The vulnerability exists because the check at line 13
is not sufficient, and does not consider values under zero other than -1.
The vulnerability would not occur if the check at line 24
would be done for unsigned integers
.
Tracking where the value of minIndex
was set we land in the following place :
const intermediate::common::ITextStyle *__cdecl intermediate::odf::TextParagraphProperties::TextParagraphProperties
(...)
mov DWORD PTR [eax+0x64],0xffffff01
So -255
value of minIndex
(according of some getter function name it is exactly NumberingLevel
) is a default value set inside the “Text Paragraph Properties” constructor.
Further investigation showed that this default value is only overwritten if a particular paragraph has corresponding PAPX
(Paragraph Property Exceptions) record, which contains sprm
(Property Modifier)
having a value of 0x260a
. That sprm
according to the MS-DOC
format specification is sprmPIlvl
.
Also, looking at the call stack before the corruption:
gdb-peda$ bt
#0 0xf56037e5 in intermediate::Office::IOfficeShape::updateNumbering(long*, long*, int*) () from ./libISYSreadershd.so
#1 0xf5633bbe in intermediate::Office::Shape2003::addTextContentUnit(intermediate::Office::IOfficeTextItem*, intermediate::common::ITextContentUnit*, intermediate::common::ITextNumberingTable*, long*, int*) () from ./libISYSreadershd.so
#2 0xf566ab76 in intermediate::odf::TextDocumentContent::createParagraph(std::vector<intermediate::common::ITextContentUnit*, std::allocator<intermediate::common::ITextContentUnit*> >&, int, int, bool, int, bool) () from ./libISYSreadershd.so
#3 0xf566ba72 in intermediate::odf::TextDocumentContent::createParagraph(std::vector<intermediate::common::ITextContentUnit*, std::allocator<intermediate::common::ITextContentUnit*> >&, int, int, bool) () from ./libISYSreadershd.so
#4 0xf566e1ed in intermediate::odf::TextDocumentContent::TextDocumentContent(reader::word97_2003::Document const&, intermediate::odf::TextDocumentPackages*, intermediate::odf::TextNumberingTable*) () from ./libISYSreadershd.so
#5 0xf568b49d in intermediate::odf::TextDocumentPackagesImpl::Init(reader::word97_2003::Document const&, intermediate::odf::TextDocumentPackages*) () from ./libISYSreadershd.so
#6 0xf568c047 in intermediate::odf::TextDocumentPackages::TextDocumentPackages(std::auto_ptr<reader::word97_2003::Document>) () from ./libISYSreadershd.so
#7 0xf575cdf4 in ISYS_NS::LibraryHD::CDocument::openWord(IStorage*) () from ./libISYSreadershd.so
#8 0xf575dfea in ISYS_NS::LibraryHD::CDocument::open(IGR_Stream*, int, wchar_t const*) () from ./libISYSreadershd.so
#9 0xf57594d9 in ISYS_NS::LibraryHD::IGR_HDAPI_Open(IGR_Stream*, int, wchar_t const*, void**, wchar_t*) () from ./libISYSreadershd.so
#10 0xf623f6d1 in ISYS_NS::exports::IGR_Open_File_FromStream(wchar_t const*, wchar_t const*, ISYS_NS::CStream*, bool, ISYS_NS::exports::Ext_Open_Options*, int, wchar_t const*, int*, int*, void**, int*, int, Error_Control_Block*) () from ./libISYSreaders.so
#11 0xf624513c in ISYS_NS::exports::IGR_Open_Stream_Ex(IGR_Stream*, int, unsigned short const*, int*, int*, void**, Error_Control_Block*) () from ./libISYSreaders.so
#12 0xf5f4b673 in IGR_Open_Stream_Ex () from ./libISYS11df.so
#13 0x08050654 in processStream(std::string const&, tagTIGR_Stream*, bool, int, int, bool, std::ostream&, int, double) ()
#14 0x08054574 in processFile(std::string const&, int, int, bool, std::ostream&) ()
#15 0x080581f9 in main ()
#16 0xf5bbe637 in __libc_start_main (main=0x8057b90 <main>, argc=0x5, argv=0xff8fbc34, init=0x8069890 <__libc_csu_init>, fini=0x8069880 <__libc_csu_fini>, rtld_fini=0xf7fd2880 <_dl_fini>, stack_end=0xff8fbc2c) at ../csu/libc-start.c:291
#17 0x0804d6e1 in _start ()
We can deduce that the paragraph is inside a shape object, and that is one of the crucial requirements to trigger the vulnerability. An attacker who provides a malicious .doc document for conversion to HTML could trigger this vulnerability, and potentially gain code execution on the system.
Starting program: /home/icewall/bugs/Perceptive_11.4.2647/bin/linux/intel-32/isys_doc2text --html -o /tmp/a ./storage/2fa87ae8d1beba2b25940dca4088afde
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[1] File type: Microsoft Word (25); Capabilities: 15 - ./storage/2fa87ae8d1beba2b25940dca4088afde
Program received signal SIGSEGV, Segmentation fault.
0xf516d174 in ?? () from ./libISYSreadershd.so
(gdb) bt 10
#0 0xf516d174 in ?? () from ./libISYSreadershd.so
#1 0xffffffff in ?? ()
#2 0xffffffff in ?? ()
#3 0xffffffff in ?? ()
#4 0xffffffff in ?? ()
#5 0xffffffff in ?? ()
#6 0xffffffff in ?? ()
#7 0xffffffff in ?? ()
#8 0xffffffff in ?? ()
#9 0xffffffff in ?? ()
(More stack frames follow...)
gdb-peda$ context
[----------------------------------registers-----------------------------------]
EAX: 0xffffffff
EBX: 0xf56b4f0c --> 0xa42fcc
ECX: 0xffffff01
EDX: 0xffffff01
ESI: 0xa3
EDI: 0xffffade0 --> 0xffffffff
EBP: 0xffffaa98 --> 0xffffffff
ESP: 0xffffaa90 --> 0xffffffff
EIP: 0xf516d174 (mov DWORD PTR [eax],ecx)
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xf516d16c: jl 0xf516d160
0xf516d16e: inc DWORD PTR [edi+ecx*4]
0xf516d171: mov eax,DWORD PTR [ebp+0xc]
=> 0xf516d174: mov DWORD PTR [eax],ecx
0xf516d176: pop esi
0xf516d177: pop edi
0xf516d178: pop ebp
0xf516d179: ret
[------------------------------------stack-------------------------------------]
0000| 0xffffaa90 --> 0xffffffff
0004| 0xffffaa94 --> 0xffffffff
0008| 0xffffaa98 --> 0xffffffff
0012| 0xffffaa9c --> 0xffffffff
0016| 0xffffaaa0 --> 0xffffffff
0020| 0xffffaaa4 --> 0xffffffff
0024| 0xffffaaa8 --> 0xffffffff
0028| 0xffffaaac --> 0xffffffff
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
2018-03-16 - Vendor Disclosure
2018-03-22 - Vendor patched
2018-04-26 - Public Release
Discovered by Marcin "Icewall" Noga of Cisco Talos.