Talos Vulnerability Report

TALOS-2017-0417

libxls xls_addCell MulBlank Code Execution Vulnerability

November 9, 2017
CVE Number

CVE-2017-2910

Summary

An exploitable Out-of-bounds Write vulnerability exists in the xls_addCell function of libxls 1.4. A specially crafted xls file can cause a memory corruption resulting in remote code execution. An attacker can send malicious xls file to trigger this vulnerability.

Tested Versions

libxls 1.4 readxl package 1.0.0 for R (tested using Microsoft R 4.3.1)

Product URLs

http://libxls.sourceforge.net/

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-787: Out-of-bounds Write

Details

libxls is a C library supported on windows, mac, cygwin which can read Microsoft Excel File Format (XLS) files. The library is used by the `readxl` package of the `Microsoft R` programming language. An out-of-bounds write appears in the `xls_addCell` function during parsing of the MULBLANK record. Let's take a look at the vulnerable code:

Line 399	void xls_addCell(xlsWorkSheet* pWS,BOF* bof,BYTE* buf)
Line 400	{
Line 401	(...)
Line 402		row=&pWS->rows.row[((COL*)buf)->row];
Line 403		cell=&row->cells.cell[((COL*)buf)->col-row->fcell];
Line 404
Line 405		cell->id=bof->id;
Line 406		cell->xf=((COL*)buf)->xf;
Line 407
Line 408		switch (bof->id)
Line 409		{
Line 410			case 0x0BE:	//MULBLANK
Line 411			for (i=0;i<=*(WORD *)(buf+(bof->size-2))-((COL*)buf)->col;i++)
Line 412			{
Line 413				cell=&row->cells.cell[((COL*)buf)->col-row->fcell+i];
Line 414				//				col=row->cols[i];
Line 415				cell->id=bof->id;
Line 416				cell->xf=*((WORD *)(buf+(4+i*2)));
Line 417				cell->str=xls_getfcell(pWS->workbook,cell);
Line 418			}	

According to the Microsoft MS-XLS document: 2.4.174 MulBlank is

The MulBlank record specifies a series of blank cells in a sheet row. This record can store up to 256
IXFCell structures.

At line 411 the amount of IXFCells is calculated as follows:

*(WORD *)(buf+(bof->size-2))-((COL*)buf)->col where
*(WORD *)(buf+(bof->size-2)) == colLast

and ((COL*)buf)->col == colFirst

In our PoC the MulBlank record is located at offset 0x1bcc. Next at line 413 using the loop index i, further cells are pulled out from a particular row.

There is no check to ensure that the calculated index : ((COL*)buf)->col-row->fcell+i does not exceed the available amount of cells in that particular row.

Also lines

Line 402		row=&pWS->rows.row[((COL*)buf)->row];
Line 403		cell=&row->cells.cell[((COL*)buf)->col-row->fcell];

contain the same vulnerability because :

((COL*)buf)->row

is not checked and its value comes directly from the file. That situation leads to out of bounds writes and finally heap corruption.

(gdb) p/x *bof
$5 = {id = 0xbe, size = 0x52}
(gdb) p *row
$6 = {index = 0, fcell = 0, lcell = 232, height = 1395, flags = 448, xf = 36, xfflags = 0 '\000', cells = {count = 0, cell = 0x607500}}
(gdb) p/x *pWS
$8 = {filepos = 0xb6f, defcolwidth = 0x800, rows = {lastcol = 0xe8, lastrow = 0x6, row = 0x607440}, workbook = 0x603010, colinfo = {count = 0x4c, col = 0x607140}, maxcol = 0x0}

Crash Information

Starting program: /home/icewall/bugs/libxls-0.2.0/build/bin/xls2csv ./crashes/9204a990ea8f1f0d57cf6d7102e166fc

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7bd165c in xls_addCell (pWS=0x606980, bof=0x7fffffffdc10, buf=0x6066b0 "") at xls.c:438
438                 cell->str=xls_getfcell(pWS->workbook,cell);
(gdb) bt
#0  0x00007ffff7bd165c in xls_addCell (pWS=0x606980, bof=0x7fffffffdc10, buf=0x6066b0 "") at xls.c:438
#1  0x00007ffff7bd2ceb in xls_parseWorkSheet (pWS=0x606980) at xls.c:875
#2  0x0000000000400aed in main (pintArgc=2, ptstrArgv=0x7fffffffdd78) at xls2csv.c:90

[----------------------------------registers-----------------------------------]
RAX: 0x665fe8 --> 0xbe0000000000be 
RBX: 0x0 
RCX: 0x0 
RDX: 0x6647f0 --> 0x0 
RSI: 0x7fffffffb390 --> 0x6469206c6c6100 ('')
RDI: 0x6647f0 --> 0x0 
RBP: 0x7fffffffdbf0 --> 0x7fffffffdc30 --> 0x7fffffffdc90 --> 0x400e30 (<__libc_csu_init>:      push   r15)
RSP: 0x7fffffffdbb0 --> 0x1 
RIP: 0x7ffff7bd165c (<xls_addCell+743>: mov    QWORD PTR [rax+0x18],rdx)
R8 : 0x645000 --> 0x0 
R9 : 0x0 
R10: 0x645000 --> 0x0 
R11: 0x1 
R12: 0x400820 (<_start>:        xor    ebp,ebp)
R13: 0x7fffffffdd70 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7bd1650 <xls_addCell+731>:    call   0x7ffff7bcda00 <xls_getfcell@plt>
   0x7ffff7bd1655 <xls_addCell+736>:    mov    rdx,rax
   0x7ffff7bd1658 <xls_addCell+739>:    mov    rax,QWORD PTR [rbp-0x10]
=> 0x7ffff7bd165c <xls_addCell+743>:    mov    QWORD PTR [rax+0x18],rdx
   0x7ffff7bd1660 <xls_addCell+747>:    add    DWORD PTR [rbp-0x14],0x1
   0x7ffff7bd1664 <xls_addCell+751>:    mov    rax,QWORD PTR [rbp-0x30]
   0x7ffff7bd1668 <xls_addCell+755>:    movzx  eax,WORD PTR [rax+0x2]
   0x7ffff7bd166c <xls_addCell+759>:    movzx  eax,ax
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdbb0 --> 0x1 
0008| 0x7fffffffdbb8 --> 0x6066b0 --> 0xf000f00020000 
0016| 0x7fffffffdbc0 --> 0x7fffffffdc10 --> 0x5200be 
0024| 0x7fffffffdbc8 --> 0x606980 --> 0xe8080000000b6f 
0032| 0x7fffffffdbd0 --> 0x52 ('R')
0040| 0x7fffffffdbd8 --> 0x26d600000052 
0048| 0x7fffffffdbe0 --> 0x665fe8 --> 0xbe0000000000be 
0056| 0x7fffffffdbe8 --> 0x607440 --> 0x57300e800000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV

Timeline

2017-08-15 - Vendor Disclosure
2017-11-09 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.