Talos Vulnerability Report

TALOS-2018-0538

Hyland Perceptive Document Filters DOC to HTML updateNumbering Code Execution Vulnerability

April 26, 2018
CVE Number

CVE-2018-3855

Summary

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.

Tested Versions

Perceptive Document Filters 11.4.0.2647 - x86/x64 Windows/Linux Perceptive Document Filters 11.2.0.1732 - x86/x64 Windows/Linux

Product URLs

https://www.hyland.com/en/perceptive#docfilters

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-121: Stack-based Buffer Overflow

Details

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.

Crash Information

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

Timeline

2018-03-16 - Vendor Disclosure
2018-03-22 - Vendor patched
2018-04-26 - Public Release

Credit

Discovered by Marcin "Icewall" Noga of Cisco Talos.