Talos Vulnerability Report

TALOS-2017-0288

AntennaHouse DMC HTMLFilter Txo Code Execution Vulnerability

May 4, 2017
CVE Number

CVE-2017-2795

Summary

An exploitable heap corruption vulnerability exists in the Txo functionality of AntennaHouse DMC HTMLFilter as used by MarkLogic 8.0-6. A specially crafted xls file can cause a heap corruption resulting in arbitrary code execution. An attacker can send/provide malicious XLS file to trigger this vulnerability.

Tested Versions

AntennaHouse DMC HTMLFilter shipped with MarkLogic 8.0-6

fb1a22fa08c986ec3614284f4e912b0a  /opt/MarkLogic/Converters/cvtofc/libdhf_rdoc.so
15b0acc464fba28335239f722a62037f  /opt/MarkLogic/Converters/cvtofc/libdmc_comm.so
1eabb31236c675f9856a7d001b339334  /opt/MarkLogic/Converters/cvtofc/libdhf_rxls.so
1415cbc784f05db0e9db424636df581a  /opt/MarkLogic/Converters/cvtofc/libdhf_comm.so
4ae366fbd4540dd4c750e6679eb63dd4  /opt/MarkLogic/Converters/cvtofc/libdmc_conf.so
81db1b55e18a0cb70a78410147f50b9c  /opt/MarkLogic/Converters/cvtofc/libdhf_htmlif.so
d716dd77c8e9ee88df435e74fad687e6  /opt/MarkLogic/Converters/cvtofc/libdhf_whtml.so
e01d37392e2b2cea757a52ddb7873515  /opt/MarkLogic/Converters/cvtofc/convert

Product URLs

https://www.antennahouse.com/antenna1/

CVSSv3 Score

8.3 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H

Details

This vulnerability is present in the AntennaHouse DMC HTMLFilter which is used, among others, to convert XLS files to (X)HTML form.  

This product is mainly used by MarkLogic for office document conversions as part of their web based document search and rendering engine. A specially crafted XLS file can lead to an heap corruption and ultimately to remote code execution.

Running the XLS to HTML converter under Valgrind we can see the following result:

icewall@ubuntu:~/bugs/cvtofc_86$ valgrind ./convert config_xls/
==21170== Memcheck, a memory error detector
==21170== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==21170== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==21170== Command: ./convert config_xls/
==21170== 
input=/home/icewall/bugs/cvtofc_86/config_xls/toconv.xls
output=/home/icewall/bugs/cvtofc_86/config_xls/conv.html
type=2
info.options='0'
Return from GetFileInfo=0
HtmlInfo.GroupName=UTF-8
HtmlInfo.DefLangName=English
HtmlInfo.bBigEndian=0
HtmlInfo.options=0
HtmlInfo.SheetId=0
HtmlInfo.SlideId=0
HtmlInfo.lpFunc=(nil)
HtmlInfo.szImageFolder=

==21170== Invalid write of size 4
==21170==    at 0x402EE82: memcpy (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==21170==    by 0x4042EA7: Txo (in /home/icewall/bugs/cvtofc_86/libdhf_rxls.so)
==21170==    by 0x4042813: MsoDrawingRec (in /home/icewall/bugs/cvtofc_86/libdhf_rxls.so)
==21170==    by 0x405324E: DHF_RGetObject (in /home/icewall/bugs/cvtofc_86/libdhf_rxls.so)
==21170==    by 0x403979E: FilterToHtml (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so)
==21170==    by 0x4038AFB: DHF_GetHtml_V11 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so)
==21170==    by 0x8049AF7: main (in /home/icewall/bugs/cvtofc_86/convert)
==21170==  Address 0x4c15344 is 52 bytes inside a block of size 40 alloc'd
==21170==    at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==21170==    by 0x42DCCB1: DMC_malloc (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==21170==    by 0x4042D73: Txo (in /home/icewall/bugs/cvtofc_86/libdhf_rxls.so)
==21170==    by 0x4042813: MsoDrawingRec (in /home/icewall/bugs/cvtofc_86/libdhf_rxls.so)
==21170==    by 0x405324E: DHF_RGetObject (in /home/icewall/bugs/cvtofc_86/libdhf_rxls.so)
==21170==    by 0x403979E: FilterToHtml (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so)
==21170==    by 0x4038AFB: DHF_GetHtml_V11 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so)
==21170==    by 0x8049AF7: main (in /home/icewall/bugs/cvtofc_86/convert)
==21170== 

The dynamically allocated buffer in the Txo function is overflowed during a memcpy operation. Let's check the pseudocode of the Txo function and try to spot the vulnerable code:

Line 1  signed int __cdecl Txo(struct_a1 *a1)
Line 2  {
Line 3    (...)
Line 4     bufferSize = 2 * (unsigned __int16)Exc_GetWord((int)a1, a1->dword128 + 10);
Line 5    *(_DWORD *)(a1->dword9B0 + 80 * a1->dword9B4 - 68) = ((unsigned __int16)Exc_GetWord((int)a1, a1->dword128 + 12) >> 3)
Line 6                                                       - 1;
Line 7    if ( *(_DWORD *)(a1->dword9B0 + 80 * a1->dword9B4 - 72) )
Line 8    {
Line 9      *(_BYTE *)(a1->dword9B0 + 80 * a1->dword9B4 - 80) = Exc_GetWord((int)a1, a1->dword128 + 2);
Line 10     buffer = DMC_malloc(bufferSize + 6);
Line 11     if ( !*(_DWORD *)(a1->dword9B0 + 80 * a1->dword9B4 - 76) )
Line 12       return 12;
Line 13     v4 = a1->dword9B0 + 80 * a1->dword9B4 - 80;
Line 14     memset(*(void **)(v4 + 4), 0, *(_DWORD *)(v4 + 8) + 6);
Line 15     while ( (signed int)offset < *(_DWORD *)(a1->dword9B0 + 80 * a1->dword9B4 - 72) )
Line 16     {
Line 17       if ( ReadRec((int)a1) )
Line 18         return 0;
Line 19       if ( a1->word122 != 60 )
Line 20         goto LABEL_17;
Line 21       v5 = (_BYTE *)a1->dword128;
Line 22       if ( *v5 )
Line 23       {
Line 24         memcpy((void *)(*(_DWORD *)(a1->dword9B0 + 80 * a1->dword9B4 - 76) + offset), v5 + 1, a1->someLenSrc - 1);
Line 25         offset = LOWORD(a1->someLenSrc) + offset - 1;
Line 26       }
Line 27       else
Line 28       {
Line 29         memcpy(a1->pvoid12C, (const void *)(a1->dword128 + 1), a1->someLenSrc - 1);
Line 30         a1->someLen = a1->someLenSrc - 1;
Line 31         UnCompressUnicode((int)a1);
Line 32         memcpy(Buffer + offset), a1->srcBuffer, 2 * a1->someLen);
Line 33         offset += 2 * LOWORD(a1->someLen);
Line 34       }
Line 35     }

We see that allocation for buffer is made at line 10 based on bufferSize. The memcpy where the overflow appears as is at line 32. From the Valgrind output we know that the allocation is made for 40 bytes, let's figure out what value has the memcpy size argument has:

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x6d ('m')
EBX: 0xf7fcc000 --> 0x8e98 
ECX: 0xe0232ffe --> 0x5a0032 ('2')
EDX: 0xdffd0000 --> 0x0 
ESI: 0xe020c604 --> 0x0 
EDI: 0xe020effe --> 0x545a3200 ('')
EBP: 0xfffec7e8 --> 0xfffec808 --> 0xfffec838 --> 0xfffec888 --> 0xfffec8b8 --> 0xfffec8e8 --> 0xfffeccc8 --> 0xffffd038 --> 0x0 
ESP: 0xfffec7c0 --> 0xfffec7e0 --> 0x77 ('w')
EIP: 0xf7fc7df7 (mov    BYTE PTR [edx],al)
EFLAGS: 0x10292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7fc7def:  mov    ecx,DWORD PTR [ebp-0xc]
   0xf7fc7df2:  add    eax,ecx
   0xf7fc7df4:  movzx  eax,BYTE PTR [eax]
=> 0xf7fc7df7:  mov    BYTE PTR [edx],al
   0xf7fc7df9:  add    DWORD PTR [ebp-0x14],0x1
   0xf7fc7dfd:  mov    eax,DWORD PTR [ebp-0x14]
   0xf7fc7e00:  cmp    eax,DWORD PTR [ebp+0x10]
   0xf7fc7e03:  jb     0xf7fc7de4
[------------------------------------stack-------------------------------------]
0000| 0xfffec7c0 --> 0xfffec7e0 --> 0x77 ('w')
0004| 0xfffec7c4 --> 0xe020c604 --> 0x0 
0008| 0xfffec7c8 --> 0xfffec808 --> 0xfffec838 --> 0xfffec888 --> 0xfffec8b8 --> 0xfffec8e8 --> 0xfffeccc8 --> 0xffffd038 --> 0x0 
0012| 0xfffec7cc --> 0xf7fa935d (mov    edx,eax)
0016| 0xfffec7d0 --> 0xe029abd8 --> 0xe029ef48 --> 0x0 
0020| 0xfffec7d4 --> 0x28 ('(')
0024| 0xfffec7d8 --> 0xdffcffd8 --> 0x5a0032 ('2')
0028| 0xfffec7dc --> 0xe0232ffe --> 0x5a0032 ('2')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xf7fc7df7 in _duma_memcpy (dest=0xdffcffd8, src=0xe0232ffe, size=0xec) at duma.c:2264
2264        d[i] = s[i];

Here we can see the value is equal to 0xEC.

Is easy to observe that values used for allocation and for memcpy operation are different, plus neither of them are checked. Both values are read directly from the file via Exc_GetWord. Let's see what fields the values are coming from:

The raw value of bufferSize 0x11 (value before multiplication) is coming from offset 0xFB5.

- It's a field that belongs to the `Txo` record located at offset 0xFB5. 
- This record is fully described in the MS-XLS specification in section `2.4.329 TxO`. 

The value of the size argument in the memcpy operation is located at offset: 0xFBF .

- It's a field that belongs to the `Continue` record located at offset : 0xFBD
- The desciption of this record can be found in the MS-XLS specification in section  `2.4.58 Continue`.

Crash Information

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x6d ('m')
EBX: 0xf7fcc000 --> 0x8e98 
ECX: 0xe0232ffe --> 0x5a0032 ('2')
EDX: 0xdffd0000 --> 0x0 
ESI: 0xe020c604 --> 0x0 
EDI: 0xe020effe --> 0x545a3200 ('')
EBP: 0xfffec7e8 --> 0xfffec808 --> 0xfffec838 --> 0xfffec888 --> 0xfffec8b8 --> 0xfffec8e8 --> 0xfffeccc8 --> 0xffffd038 --> 0x0 
ESP: 0xfffec7c0 --> 0xfffec7e0 --> 0x77 ('w')
EIP: 0xf7fc7df7 (mov    BYTE PTR [edx],al)
EFLAGS: 0x10292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7fc7def:  mov    ecx,DWORD PTR [ebp-0xc]
   0xf7fc7df2:  add    eax,ecx
   0xf7fc7df4:  movzx  eax,BYTE PTR [eax]
=> 0xf7fc7df7:  mov    BYTE PTR [edx],al
   0xf7fc7df9:  add    DWORD PTR [ebp-0x14],0x1
   0xf7fc7dfd:  mov    eax,DWORD PTR [ebp-0x14]
   0xf7fc7e00:  cmp    eax,DWORD PTR [ebp+0x10]
   0xf7fc7e03:  jb     0xf7fc7de4
[------------------------------------stack-------------------------------------]
0000| 0xfffec7c0 --> 0xfffec7e0 --> 0x77 ('w')
0004| 0xfffec7c4 --> 0xe020c604 --> 0x0 
0008| 0xfffec7c8 --> 0xfffec808 --> 0xfffec838 --> 0xfffec888 --> 0xfffec8b8 --> 0xfffec8e8 --> 0xfffeccc8 --> 0xffffd038 --> 0x0 
0012| 0xfffec7cc --> 0xf7fa935d (mov    edx,eax)
0016| 0xfffec7d0 --> 0xe029abd8 --> 0xe029ef48 --> 0x0 
0020| 0xfffec7d4 --> 0x28 ('(')
0024| 0xfffec7d8 --> 0xdffcffd8 --> 0x5a0032 ('2')
0028| 0xfffec7dc --> 0xe0232ffe --> 0x5a0032 ('2')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xf7fc7df7 in _duma_memcpy (dest=0xdffcffd8, src=0xe0232ffe, size=0xec) at duma.c:2264
2264        d[i] = s[i];
gdb-peda$ exploitable
Description: Access violation on destination operand
Short description: DestAv (9/29)
Hash: e564cdb159d6f32354872c8056b44ae9.9a9eb3a0067966210fd703064a290e74
Exploitability Classification: EXPLOITABLE
Explanation: The target crashed on an access violation at an address matching the destination operand of the instruction. This likely indicates a write access violation, which means the attacker may control the write address and/or value.
Other tags: AccessViolation (28/29)
gdb-peda$ bt
#0  0xf7fc7df7 in _duma_memcpy (dest=0xdffcffd8, src=0xe0232ffe, size=0xec) at duma.c:2264
#1  0xf7fc8278 in memcpy (dest=0xdffcffd8, src=0xe0232ffe, size=0xec) at duma.c:2501
#2  0xf7fa3ea8 in Txo () from ./libdhf_rxls.so
#3  0xf7fa3814 in MsoDrawingRec () from ./libdhf_rxls.so
#4  0xf7fb424f in DHF_RGetObject () from ./libdhf_rxls.so
#5  0xf7fc179f in FilterToHtml () from ./libdhf_htmlif.so
#6  0xf7fc0afc in DHF_GetHtml_V11 () from ./libdhf_htmlif.so
#7  0x08049af8 in main ()
#8  0xf7d60af3 in __libc_start_main (main=0x8049730 <main>, argc=0x2, argv=0xffffd0d4, init=0x8049f70 <__libc_csu_init>, fini=0x8049f60 <__libc_csu_fini>, rtld_fini=0xf7feb160 <_dl_fini>, 
    stack_end=0xffffd0cc) at libc-start.c:287
#9  0x08048ad1 in _start ()

Timeline

2017-02-21 - Vendor Disclosure
2017-05-04 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.