CVE-2017-2795
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.
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
https://www.antennahouse.com/antenna1/
8.3 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
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
.
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 ()
2017-02-21 - Vendor Disclosure
2017-05-04 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.