### Summary
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.
### 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 CVSSv3 Calculator: https://www.first.org/cvss/calculator/3.0
### CWE
CWE-787: Out-of-bounds Write
### Details
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 Information
```
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$
```
### Timeline
* 2017-08-29 - Vendor Disclosure
* 2017-11-15 - Public Release
暂无评论