AntennaHouse DMC HTMLFilter iBldDirInfo Code Execution Vulnerability(CVE-2017-2792)

2017-09-19T00:00:00
ID SSV:96522
Type seebug
Reporter Root
Modified 2017-09-19T00:00:00

Description

Summary

An exploitable heap corruption vulnerability exists in the iBldDirInfo functionality of AntennaHouse DMC HTMLFilter used by MarkLogic 8.0-6. A specially crafted xls file can cause a heap corruption resulting in arbitrary code execution. An attacker can provide a 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 xls document conversions as part of their web based document search and rendering engine. A specially crafted XLS file can lead to heap corruption and ultimately to remote code execution.

Let's investigate this vulnerability. After execution of the XLS to html converter with a malformed xls file as an input we can easily observe the following when using Valgrind: icewall@ubuntu:~/bugs/cvtofc_86$ valgrind ./convert config_xls/ ==43073== Memcheck, a memory error detector ==43073== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==43073== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==43073== Command: ./convert config_xls/ ==43073== 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' ==43073== Invalid write of size 1 ==43073== at 0x42E012A: iBldDirInfo (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42DEDDA: DMC_2OLEopen (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42C0941: CommOLEOpen (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42BB8C9: autoOLE (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42BFD2C: detectFormat (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42C0269: DMC_FileDtct (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x4038144: DHF_GetFileInfo_V1 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so) ==43073== by 0x80498B4: main (in /home/icewall/bugs/cvtofc_86/convert) ==43073== Address 0x4bedb90 is 0 bytes after a block of size 1,536 alloc'd ==43073== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so) ==43073== by 0x42DCCB1: DMC_malloc (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42DFFAD: iBldDirInfo (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42DEDDA: DMC_2OLEopen (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42C0941: CommOLEOpen (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42BB8C9: autoOLE (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42BFD2C: detectFormat (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42C0269: DMC_FileDtct (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x4038144: DHF_GetFileInfo_V1 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so) ==43073== by 0x80498B4: main (in /home/icewall/bugs/cvtofc_86/convert) ==43073== ==43073== Invalid write of size 1 ==43073== at 0x42E0151: iBldDirInfo (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42DEDDA: DMC_2OLEopen (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42C0941: CommOLEOpen (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42BB8C9: autoOLE (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42BFD2C: detectFormat (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42C0269: DMC_FileDtct (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x4038144: DHF_GetFileInfo_V1 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so) ==43073== by 0x80498B4: main (in /home/icewall/bugs/cvtofc_86/convert) ==43073== Address 0x4bedb91 is 1 bytes after a block of size 1,536 alloc'd ==43073== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so) ==43073== by 0x42DCCB1: DMC_malloc (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42DFFAD: iBldDirInfo (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42DEDDA: DMC_2OLEopen (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so) ==43073== by 0x42C0941: CommOLEOpen (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42BB8C9: autoOLE (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42BFD2C: detectFormat (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x42C0269: DMC_FileDtct (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so) ==43073== by 0x4038144: DHF_GetFileInfo_V1 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so) ==43073== by 0x80498B4: main (in /home/icewall/bugs/cvtofc_86/convert)

We see that a heap-based buffer overflow occurs in the function iBldDirInfo. The overflowed buffer is also allocated in this function. Running our target with an instrumented heap we stop in the following place : `` Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x93 EBX: 0xf7c1acac --> 0x5ebf4 ECX: 0xe04a0fa0 --> 0x84938493 EDX: 0x60 ('') ESI: 0x60 ('') EDI: 0x60 ('') EBP: 0xfffec748 --> 0xfffec788 --> 0xfffec7b8 --> 0xfffec9b8 --> 0xfffec9d8 --> 0xfffeca08 --> 0xfffeccc8 --> 0xffffd038 --> 0x0 ESP: 0xfffec640 --> 0xffffffff EIP: 0xf7bc412a (mov BYTE PTR [edx+ecx1],al) EFLAGS: 0x10a06 (carry PARITY adjust zero sign trap INTERRUPT direction OVERFLOW) [-------------------------------------code-------------------------------------] 0xf7bc411d: movsx edx,si 0xf7bc4120: shr ax,0x8 0xf7bc4124: mov ecx,DWORD PTR [ebp-0xcc] => 0xf7bc412a: mov BYTE PTR [edx+ecx1],al 0xf7bc412d: inc esi 0xf7bc412e: movsx eax,si 0xf7bc4131: mov dl,BYTE PTR [ebp-0xfa] 0xf7bc4137: jmp 0xf7bc414b [------------------------------------stack-------------------------------------] 0000| 0xfffec640 --> 0xffffffff 0004| 0xfffec644 --> 0xffffffff 0008| 0xfffec648 --> 0xffffffff 0012| 0xfffec64c --> 0x93840000 0016| 0xfffec650 --> 0xe057e340 --> 0x0 0020| 0xfffec654 --> 0xe057cde0 --> 0xe049e000 --> 0xaaaaaaaa 0024| 0xfffec658 --> 0xe049ee00 --> 0x1 0028| 0xfffec65c --> 0xe049f000 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0xf7bc412a in iBldDirInfo () from ./libdmc_comm.so gdb-peda$ bt

0 0xf7bc412a in iBldDirInfo () from ./libdmc_comm.so

1 0xf7bc2ddb in DMC_2OLEopen () from ./libdmc_comm.so

2 0xf7c25942 in CommOLEOpen () from ./libdmc_dtct.so

3 0xf7c208ca in autoOLE () from ./libdmc_dtct.so

4 0xf7c24d2d in detectFormat () from ./libdmc_dtct.so

5 0xf7c2526a in DMC_FileDtct () from ./libdmc_dtct.so

6 0xf7fc0145 in DHF_GetFileInfo_V1 () from ./libdhf_htmlif.so

7 0x080498b5 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 ()

```

To figure out something more about the place where the out-of-bound write appears, we can review the pseudo code of the iBldDirInfo function: Line 1 signed int __cdecl iBldDirInfo(struct_a1 *a1) Line 2 { Line 3 (...) Line 4 Line 5 v24 = 0; Line 6 v18 = -2; Line 7 v17 = 0; Line 8 v1 = (unsigned int)(a1-&gt;dword2C * a1-&gt;dword38) &gt;&gt; 2; Line 9 v21 = a1-&gt;dword38 / 128; Line 10 v23 = 1; Line 11 v2 = 0; Line 12 for ( i = lGetSect(a1, a1-&gt;dwordC); ; i = lGetSect(a1, v19) ) Line 13 { Line 14 v19 = i; Line 15 if ( i == -1 ) Line 16 break; Line 17 if ( i &lt;= -2 ) Line 18 { Line 19 v5 = v21 * v23; Line 20 a1-&gt;dword48 = v5; Line 21 v6 = DMC_malloc(96 * v5); Line 22 a1-&gt;buffer = v6; Line 23 if ( v6 ) Line 24 { Line 25 v20 = a1-&gt;dwordC; Line 26 if ( !DMC_FileSeek(a1-&gt;pfile0, (v20 &lt;&lt; a1-&gt;word8) + 512, 0) ) Line 27 { Line 28 a1-&gt;word52 = 0; Line 29 elementIndex = 0; Line 30 while ( DMC_FileRead(rawDirectoryEntry, 1u, 0x80u, a1-&gt;pfile0) && elementIndex &lt; a1-&gt;dword48 ) Line 31 { Line 32 directoryEntry = (a1-&gt;buffer + 96 * elementIndex); Line 33 v7 = usGetShort(&v26); Line 34 if ( v7 ) Line 35 { Line 36 directoryEntry-&gt;unsigned20 = v7; Line 37 ++a1-&gt;word52; Line 38 } Line 39 else Line 40 { Line 41 v24 = 1; Line 42 } Line 43 if ( v7 && !v24 ) Line 44 { Line 45 memset(directoryEntry, 0, 0x20u); Line 46 v8 = 0; Line 47 index = 0; Line 48 directoryEntry-&gt;unsigned20; Line 49 while ( index &lt;= 127 ) Line 50 { Line 51 v10 = DMC_unicodetosjis(((rawDirectoryEntry[index + 1] &lt;&lt; 8) | rawDirectoryEntry[index])); Line 52 v15 = v10; Line 53 if ( v10 ) Line 54 { Line 55 v11 = v10 & 0xFF00; Line 56 if ( v11 ) Line 57 directoryEntry-&gt;Name[v8++] = BYTE1(v11); Line 58 v12 = v8; Line 59 v13 = v15; Line 60 } Line 61 else Line 62 { Line 63 v12 = v8; Line 64 v13 = rawDirectoryEntry[index]; Line 65 } Line 66 directoryEntry-&gt;Name[v12] = v13; Line 67 ++v8; Line 68 index += 2; Line 69(...)

The out of bound write occurs at line 57. This code parses a Compound File Directory Entry, doing name conversion from Unicode to SJIS (Shift Japanese Industrial Standards) and fills dynamically allocated structure directoryEntry with the converted name. According to the documentation Directory Name should not be bigger than 64 bytes. We see a check for that in the while loop at line 49. The problem appears because instead of only increasing the counter index by 2 each time in the while loop, the developers also increase v8 by 2 when a correct conversion took place from Unicode to SJIS. We see v8 being incremented at lines 57 and 67. v8 as a Directory Name string index should not exceed 64 and should definitely not exceed 96 as is expected at line 32. As we can imagine, v8 can easly reach a value like 126 which occurs when:

there is still space for more Directory Entry structures (see allocation at line 21, v5 equals the amount of Directory Entries), a write on buffer space dedicated for the next Directory Entry which will cause the current Directory Entry name to be overwritten during the next Directory Entry read.

But the most dangerous scenario appears when the parsed Directory Entry is the last one, writing outside of the Name buffer will cause a heap based buffer overflow, causing heap corruption which can lead to arbitrary code execution.

Crash Information

Program received signal SIGABRT, Aborted. [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xab33 ECX: 0xab33 EDX: 0x6 ESI: 0x68 ('h') EDI: 0xf7f06000 --&gt; 0x1aada8 EBP: 0xfffec698 --&gt; 0x8095590 --&gt; 0x84938493 ESP: 0xfffec3d4 --&gt; 0xfffec698 --&gt; 0x8095590 --&gt; 0x84938493 EIP: 0xf7fdacd9 (pop ebp) EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0xf7fdacd3: mov ebp,esp 0xf7fdacd5: sysenter 0xf7fdacd7: int 0x80 =&gt; 0xf7fdacd9: pop ebp 0xf7fdacda: pop edx 0xf7fdacdb: pop ecx 0xf7fdacdc: ret 0xf7fdacdd: and edi,edx [------------------------------------stack-------------------------------------] 0000| 0xfffec3d4 --&gt; 0xfffec698 --&gt; 0x8095590 --&gt; 0x84938493 0004| 0xfffec3d8 --&gt; 0x6 0008| 0xfffec3dc --&gt; 0xab33 0012| 0xfffec3e0 --&gt; 0xf7d89687 (xchg ebx,edi) 0016| 0xfffec3e4 --&gt; 0xf7f06000 --&gt; 0x1aada8 0020| 0xfffec3e8 --&gt; 0xfffec484 --&gt; 0x270f 0024| 0xfffec3ec --&gt; 0xf7d8cab3 (mov edx,DWORD PTR gs:0x8) 0028| 0xfffec3f0 --&gt; 0x6 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGABRT 0xf7fdacd9 in ?? () gdb-peda$ exploitable Description: Heap error Short description: HeapError (15/29) Hash: eeae24290f4714292e59633fa30e2480.ff49ca1e4902951bc44eee297b21de88 Exploitability Classification: EXPLOITABLE Explanation: The target's backtrace indicates that libc has detected a heap error or that the target was executing a heap function when it stopped. This could be due to heap corruption, passing a bad pointer to a heap function such as free(), etc. Since heap errors might include buffer overflows, use-after-free situations, etc. they are generally considered exploitable. Other tags: AbortSignal (27/29)

Timeline

  • 2017-02-09 - Vendor Disclosure
  • 2017-05-04 - Public Release

CREDIT

  • Discovered by Marcin 'Icewall' Noga of Cisco Talos.