CVE-2017-2897
An exploitable out-of-bounds write vulnerability exists in the read_MSAT 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.
libxls 1.4 readxl package 1.0.0 for R (tested using Microsoft R 4.3.1)
http://libxls.sourceforge.net/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H CVSSv3 Calculator: https://www.first.org/cvss/calculator/3.0
CWE-787: Out-of-bounds Write
libxls is a C library supported on Windows, Mac and Linux which can read Microsoft Excel File Format (XLS) files. The library is used by the readxl package that can be installed in the R programming language. An out-of-bounds write appears in the read_MSAT function. Let’s take a look at the vulnerable code:
Line 425 // Read MSAT
Line 426 static int read_MSAT(OLE2* ole2, OLE2Header* oleh)
Line 427 {
Line 428 int sectorNum;
Line 429
Line 430 // reconstitution of the MSAT
Line 431 ole2->SecID = malloc(ole2->cfat*ole2->lsector);
Line 432 (...)
Line 433 int posInSector;
Line 434
Line 435 // read MSAT sector
Line 436 sector_read(ole2, sector, sid);
Line 437 // read content
Line 438 for (posInSector = 0; posInSector < (ole2->lsector-4)/4; posInSector++)
Line 439 {
Line 440 unsigned int s = *(int*)(sector + posInSector*4);
Line 441
Line 442 if (s != FREESECT)
Line 443 {
Line 444 sector_read(ole2, (BYTE*)(ole2->SecID)+sectorNum*ole2->lsector, s);
Line 445 sectorNum++;
Line 446 }
Line 447 }
As we can see in line 431
ole2->SecID
buffer is allocated based on cfat
and lsector
value. The cfat
value is read directly from the file (in our PoC cfat
has value 0x1) where lsector has fixed size 0x200.
Next in lines 438-447
we see that further “sectors” are read from the file (via sector_read) to the ole2->SecID
buffer in the amount of (ole2->lsector-4)/4
. We can observe a lack of any check
whether the new calculated offset for “sector” inside the SecID
buffer does not exceed the buffer size allocated earlier. This thus leads to out of bounds writes and heap memory corruption, which can potentially
lead to arbitrary code execution.
Crash in Microsoft R platform:
library(readxl)
path <- readxl_example("509075387a944995bb90bf109fe8191b.xls")
lapply(excel_sheets(path), read_excel, path = path)
(...)
fread: wanted 1 got 0 loc=68988965376
fread: wanted 1 got 0 loc=68988965376
fread: wanted 1 got 0 loc=68988965376
fread: wanted 1 got 0 loc=68988965376
fread: wanted 1 got 0 loc=68988965376
fread: wanted 1 got 0 loc=541435367936
fread: wanted 1 got 0 loc=1319413954048
fread: wanted 1 got 0 loc=547393582080
fread: wanted 1 got 0 loc=8355054592
fread: wanted 1 got 0 loc=132608
fread: wanted 1 got 0 loc=75611136
fread: wanted 1 got 0 loc=838861312
fread: wanted 1 got 0 loc=1536
fread: wanted 1 got 0 loc=67160064
fread: wanted 1 got 0 loc=637534720
fread: wanted 1 got 0 loc=137438955008
fread: wanted 1 got 0 loc=537149952
*** caught segfault ***
address 0x30895e0, cause 'memory not mapped'
Segmentation fault
directly in libxls lib:
Starting program: /home/icewall/bugs/libxls-1.4.0/build/bin/xls2csv ./crashes/509075387a944995bb90bf109fe8191b
Program received signal SIGSEGV, Segmentation fault.
__mempcpy_sse2 () at ../sysdeps/x86_64/memcpy.S:125
125 ../sysdeps/x86_64/memcpy.S: No such file or directory.
(gdb) bt
#0 __mempcpy_sse2 () at ../sysdeps/x86_64/memcpy.S:125
#1 0x00007ffff787903e in __GI__IO_file_xsgetn (fp=0x603310, data=<optimized out>, n=512) at fileops.c:1392
#2 0x00007ffff786e236 in __GI__IO_fread (buf=<optimized out>, size=512, count=1, fp=0x603310) at iofread.c:38
#3 0x00007ffff7bd03e6 in sector_read (ole2=0x6032b0, buffer=0x623f50 '\b' <repeats 35 times>, "?\232\231\231\231\231\231\271?
\001", sid=0) at ole.c:421
#4 0x00007ffff7bd0525 in read_MSAT (ole2=0x6032b0, oleh=0x6030a0) at ole.c:462
#5 0x00007ffff7bcfe33 in ole2_open (file=0x7fffffffe12c "./crashes/509075387a944995bb90bf109fe8191b", charset=0x400fce
"iso-8859-15//TRANSLIT") at ole.c:327
#6 0x00007ffff7bd2e00 in xls_open (file=0x7fffffffe12c "./crashes/509075387a944995bb90bf109fe8191b", charset=0x400fce
"iso-8859-15//TRANSLIT") at xls.c:910
#7 0x0000000000400957 in main (pintArgc=2, ptstrArgv=0x7fffffffdd78) at xls2csv.c:45
(gdb) frame 4
#4 0x00007ffff7bd0525 in read_MSAT (ole2=0x6032b0, oleh=0x6030a0) at ole.c:462
462 sector_read(ole2, (BYTE*)(ole2->SecID)+sectorNum*ole2->lsector, s);
(gdb) p/x sectorNum
$2 = 0xfd
(gdb) p/x ole2->lsector
$3 = 0x200
(gdb) p/x ole2->SecID
$4 = 0x604550
(gdb) peda_active
gdb-peda$ vmmap 0x604550
Start End Perm Name
0x00603000 0x00624000 rw-p [heap]
gdb-peda$ 0x00624000-0x604550
Undefined command: "0x00624000-0x604550". Try "help".
gdb-peda$ p 0x00624000-0x604550
$5 = 0x1fab0
gdb-peda$ p (BYTE*)(ole2->SecID)+sectorNum*ole2->lsector
$6 = (BYTE *) 0x623f50 '\b' <repeats 35 times>, "?\232\231\231\231\231\231\271?\001"
gdb-peda$ frame 3
#3 0x00007ffff7bd03e6 in sector_read (ole2=0x6032b0, buffer=0x623f50 '\b' <repeats 35 times>, "?\232\231\231\231\231\231\271?
\001", sid=0x0) at ole.c:421
421 fread(buffer, ole2->lsector, 1, ole2->file);
[----------------------------------registers-----------------------------------]
RAX: 0xffffffffffffffff
RBX: 0x603310 --> 0xfbad2488
RCX: 0xffffffffffffffff
RDX: 0x10
RSI: 0x6035e3 --> 0xffffffffffffffff
RDI: 0x623ff3 --> 0xffffffffffffffff
RBP: 0xb3
RSP: 0x7fffffffdac8 --> 0x7ffff787903e (<__GI__IO_file_xsgetn+382>: add QWORD PTR [rbx+0x8],rbp)
RIP: 0x7ffff788f41a (<__mempcpy_sse2+106>: mov QWORD PTR [rdi+0x8],r8)
R8 : 0x6e69ffffffffffff
R9 : 0xffffffffffffffff
R10: 0xffffffffffffffff
R11: 0x246
R12: 0x14d
R13: 0x200
R14: 0x623f50 --> 0x808080808080808
R15: 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7ffff788f410 <__mempcpy_sse2+96>: mov rcx,QWORD PTR [rsi]
0x7ffff788f413 <__mempcpy_sse2+99>: mov r8,QWORD PTR [rsi+0x8]
0x7ffff788f417 <__mempcpy_sse2+103>: mov QWORD PTR [rdi],rcx
=> 0x7ffff788f41a <__mempcpy_sse2+106>: mov QWORD PTR [rdi+0x8],r8
0x7ffff788f41e <__mempcpy_sse2+110>: sub edx,0x10
0x7ffff788f421 <__mempcpy_sse2+113>: lea rsi,[rsi+0x10]
0x7ffff788f425 <__mempcpy_sse2+117>: lea rdi,[rdi+0x10]
0x7ffff788f429 <__mempcpy_sse2+121>: jne 0x7ffff788f410 <__mempcpy_sse2+96>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdac8 --> 0x7ffff787903e (<__GI__IO_file_xsgetn+382>: add QWORD PTR [rbx+0x8],rbp)
0008| 0x7fffffffdad0 --> 0x603310 --> 0xfbad2488
0016| 0x7fffffffdad8 --> 0x200
0024| 0x7fffffffdae0 --> 0x200
0032| 0x7fffffffdae8 --> 0x1
0040| 0x7fffffffdaf0 --> 0x0
0048| 0x7fffffffdaf8 --> 0x7ffff786e236 (<__GI__IO_fread+150>: test DWORD PTR [rbx],0x8000)
0056| 0x7fffffffdb00 --> 0x400820 (<_start>: xor ebp,ebp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
gdb-peda$
2017-08-29 - Vendor Disclosure
2017-11-15 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.