Multiple Buffer Overflow Vulnerabilities In InfoZip UnZip

Title

Multiple buffer overflow vulnerabilities

Product

InfoZip UnZip

Vulnerable Version

UnZip <= 6.00 / UnZip <= 6.1c22

Fixed Version

6.10c23

CVE Number

CVE-2018-1000031,CVE-2018-1000032,CVE-2018-1000033, CVE-2018-1000034,CVE-2018-1000035

Impact

high

Found

03.11.2017

By

R. Freingruber (Office Vienna) | SEC Consult Vulnerability Lab

InfoZip UnZip is affected by multiple buffer overflow security vulnerabilities. An attacker can exploit this vulnerability to get arbitrary code execution or at least denial-of-service (crash) on the target system.

 

Vendor Description

“UnZip is an extraction utility for archives compressed in .zip format (also called “zipfiles”). Although highly compatible both with PKWARE’s PKZIP and PKUNZIP utilities for MS-DOS and with Info-ZIP’s own Zip program, our primary objectives have been portability and non-MSDOS functionality. UnZip will list, test, or extract files from a .zip archive, commonly found on MS-DOS systems. The default behavior (with no options) is to extract into the current directory (and subdirectories below it) all files from the specified zipfile.”

Source: http://www.info-zip.org/UnZip.html

InfoZip’s UnZip is used as default utility for uncompressing ZIP archives on nearly all *nix systems. It gets shipped with many commerical products on Windows to provide (un)compressing functionality as well.

Business Recommendation

InfoZip Unzip should be updated to the latest available version.

Vulnerability Overview/ Description

1) Heap-based buffer overflow in password protected ZIP archives (CVE-2018-1000035)

InfoZip’s UnZip suffers from a heap-based buffer overflow when uncompressing password protected ZIP archives. An attacker can exploit this vulnerability to overwrite heap chunks to get arbitrary code execution on the target system.

For newer builds the risk for this vulnerability is partially mitigated because modern compilers automatically replace unsafe functions with length checking variants of the same function (for example sprintf gets replaced by sprintf_chk). This is done by the compiler at locations were the length of the destination buffer can be calculated.

Nevertheless, it must be mentioned that UnZip is used on many systems including older systems or on exotic architectures on which this protection is not in place. Moreover, pre-compiled binaries which can be found on the internet lack the protection because the last major release of InfoZip’s UnZip was in 2009 and compilers didn’t enable this protection per default at that time. The required compiler flags are also not set in the Makefile of UnZip. Compiled applications are therefore only protected if the used compiler has this protection enabled per default which is only the case with modern compilers.

To trigger this vulnerability (and the following) it’s enough to uncompress a manipulated ZIP archive. Any of the following invocations can be used to trigger and abuse the vulnerabilities:

>unzip malicious.zip >unzip -p malicious.zip >unzip -t malicious.zip

2) Heap-based out-of-bounds write (CVE-2018-1000031)

This vulnerability only affects UnZip 6.1c22 (next beta version of UnZip). InfoZip’s UnZip suffers from a heap-based out-of-bounds write if the archive filename does not contain a .zip suffix.

3) Heap/BSS-based buffer overflow (Bypass of CVE-2015-1315) (CVE-2018-1000032)

This vulnerability only affects UnZip 6.1c22 (next beta version of UnZip). InfoZip’s UnZip suffers from a heap/BSS-based buffer-overflow which can be used to write null-bytes out-of-bound when converting attacker-controlled strings to the local charset.

4) Heap out-of-bounds access in ef_scan_for_stream (CVE-2018-1000033)

This vulnerability only affects UnZip 6.1c22 (next beta version of UnZip). InfoZip’s UnZip suffers from a heap out-of-bounds access vulnerability.

5) Multiple vulnerabilities in the LZMA compression algorithm (CVE-2018-1000034)

This vulnerability only affects UnZip 6.1c22 (next beta version of UnZip). InfoZip’s UnZip suffers from multiple vulnerabilities in the LZMA implementation. Various crash dumps have been supplied to the vendor but no further analysis has been performed.

Proof Of Concept

1) Heap-based buffer overflow in password protected ZIP archives (CVE-2018-1000035)

Unzipping a malicious archive results in the following output (on Ubuntu 16.04 with UnZip 6.0 which was installed via aptitude install unzip):

*** buffer overflow detected ***: unzip terminated ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x*****)[0x************] /lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x**)[0x************] /lib/x86_64-linux-gnu/libc.so.6(+0x*****)[0x************] /lib/x86_64-linux-gnu/libc.so.6(+0x*****)[0x************] /lib/x86_64-linux-gnu/libc.so.6(_IO_default_xsputn+0x**)[0x************] /lib/x86_64-linux-gnu/libc.so.6(_IO_vfprintf+0x**)[0x************] /lib/x86_64-linux-gnu/libc.so.6(__vsprintf_chk+0x**)[0x************] /lib/x86_64-linux-gnu/libc.so.6(__sprintf_chk+0x**)[0x************] unzip[0x40c02b] unzip[0x4049ac] unzip[0x40762c] unzip[0x409b60] unzip[0x411175] unzip[0x411bdf] unzip[0x404191]

Function names can be mapped to the backtrace by compiling the application with debug symbols:

(gdb) backtrace #0 0x000000000040c706 in UzpPassword () #1 0x00000000004043ce in decrypt () #2 0x000000000040731c in extract_or_test_entrylist () #3 0x00000000004094af in extract_or_test_files () #4 0x00000000004149a5 in do_seekable () #5 0x000000000041540f in process_zipfiles () #6 0x0000000000403921 in unzip ()

The vulnerability resides inside the UzpPassword function in the following code snippet (file ./fileio.c):

[1591] if ((prompt = (char *)malloc(2*FILNAMSIZ + 15)) != (char *)NULL) { [1592] sprintf(prompt, LoadFarString(PasswPrompt), [1593] FnFilter1(zfn), FnFilter2(efn)); ... [1595] }

The allocation at line 1591 allocates a fixed size buffer and then writes into it at line 1592. It writes the following format string (PasswPrompt) into the buffer: “[%s] %s password: ”

This string has a length of 15 including the null-termination which explains the +15 in the allocation. The developer allocated 2*FILENAMESIZ which corresponds to 2 * PATH_MAX for the two format strings (zfn and efn). zfn is the archive filename and can therefore not exceed PATH_MAX. efn is the current processed filename inside the ZIP archive which should typically be smaller than PATH_MAX for normal files. However, since an attacker can manipulate the archive file the name can arbitrarily be chosen which leads to a heap-based buffer overflow.

As already mentioned, modern compilers replace unsafe functions with safe alternatives as a defense in depth mechanism. This feature is called BOSC (Built-in object size checking) and is part of the FORTIFY_SOURCE=2 protection. The following link shows the source code (and vulnerability) inside the Ubuntu package:
http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/unzip/trusty-updates/view/head:/fileio.c#L1593

By checking the installed compiled binary the following code can be seen (UnZip 6.0 from Ubuntu 16.04):

0x40bfc6: mov edi,0x200f 0x40bfcb: mov r13,r8 0x40bfce: mov QWORD PTR [rsp+0x8],r9 0x40bfd3: call 0x401d30 <malloc@plt> ... 0x40c01a: mov edx,0x200f 0x40c01f: mov esi,0x1 0x40c024: xor eax,eax 0x40c026: call 0x401f40 <__sprintf_chk@plt>

The code allocates 0x200f (=4096*2 + 15) bytes but the unsafe sprintf function was replaced with the length-checking sprintf_chk() function which receives as argument the size of the buffer (0x200f at address 0x40c01a). The risk is therefore mitigated on Ubuntu (and other modern operating systems), at least with the currently used compiler default flags.

However, many pre-compiled UnZip binaries can be found on the internet which are not compiled with this protection. For example, the following three links are the first links which can be found when searching for InfoZip UnZIP on the internet and which contain unprotected binaries:
http://gnuwin32.sourceforge.net/packages/unzip.htm
https://oss.oracle.com/el4/unzip/unzip.tar
http://www.willus.com/archive/zip64/

2) Heap-based out-of-bounds write (CVE-2018-1000031)

When uncompressing ZIP archives the following code gets executed (file fileio.c:345 function set_zipfn_sgmnt_name() in UnZip 6.1c22):

#define SGMNT_NAME_BOOST 8 ... if (G.zipfn_sgmnt == NULL) { [1] G.zipfn_sgmnt_size = (int)strlen(G.zipfn)+ SGMNT_NAME_BOOST; if ((G.zipfn_sgmnt = izu_malloc(G.zipfn_sgmnt_size)) == NULL) ... [2] zfstrcpy(G.zipfn_sgmnt, G.zipfn); /* Expect to find ".zXX" at the end of the segment file name. */ [3] sufx_len = IZ_MAX( 0, ((int)strlen(G.zipfn_sgmnt)- 4)); [4] suffix = G.zipfn_sgmnt+ sufx_len; ... else // No .zip extension { [5] zfstrcpy( (suffix+ sufx_len), ZSUFX); [6] suffix += sufx_len+ 2; } /* Insert the next segment number into the file name (G.zipfn_sgmnt). */ [7] sprintf(suffix, "%02d", (sgmnt_nr+ 1));

G.zipfn is the filename / path of the archive file. Line [1] allocates space for this name plus 8 (SGMNT_NAME_BOOST). Line [2] copies the name. [3] and [4] calculate the end address minus 4 which should point to a suffix if one is present. After [4] the variable suffix already points to this address. However, line [5] adds sufx_len again to suffix, the write target is therefore the base address + 2*(allocation_length – 4) but the buffer can only hold allocation_len bytes. Line [7] is another out-of-bounds write because of line [6].

Memory trace of the crash:

#1 0xf7af3c2f in memcpy (/usr/lib/i386-linux-gnu/libasan.so.2+0x8ac2f) #2 0x80969be in set_zipfn_sgmnt_name unzip610c22/fileio.c:424 #3 0x808eb82 in find_local_header unzip610c22/extract.c:4469 #4 0x808eb82 in extract_or_test_entrylist unzip610c22/extract.c:4745 #5 0x808eb82 in extract_or_test_files unzip610c22/extract.c:5698 #6 0x80cf6ca in extract_archive_seekable unzip610c22/process.c:1314 #7 0x80cf6ca in extract_archive unzip610c22/process.c:1465 #8 0x80d5676 in process_zipfiles unzip610c22/process.c:2033 #9 0x80636a2 in unzip unzip610c22/unzip.c:1840 #10 0x804a5b6 in main unzip610c22/unzip.c:1280 #11 0xf78cb636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)

Please note that this vulnerability must not lead to a crash. If the overwritten memory is not used, the program works as expected.

3) Heap/BSS-based buffer overflow (Bypass of CVE-2015-1315) (CVE-2018-1000032)

During parsing ZIP archives the function charset_to_intern() can be called (unix/Unix.c:2427) with the “string” argument pointing to attacker controlled data. This function converts the string in-place to another charset (string is an INOUT argument).

The following code performs this task in the function:

[1] slen = strlen(string); [2] s = string; [3] dlen = buflen = 2 * slen; [4] d = buf = izu_malloc(buflen + 1); if (d) { [5] memset( buf, 0, buflen); /* 2015-02-12 William Robinet, SMS. CVE-2015-1315. * Added FILNAMSIZ check to avoid buffer overflow. Better would * be to pass in an actual destination buffer size. */ [6] if ((iconv(cd, &s, &slen, &d, &dlen) != (size_t)-1) && [7] (strlen(buf) < FILNAMSIZ)) { [8] strncpy(string, buf, buflen); } izu_free(buf); }

The input string pointer is stored in the variable “string” and “s” (see [2]). Line [6] tries to convert the input (“s”) via iconv() to another charset. The destination is “d” / “buf” (see line [4]). This destination buffer has a size of two times the input length plus one ([3]).

The first problem can be found in line [5] which just initializes “buflen” bytes and not “buflen+1” bytes. Consider the input string is empty, therefore slen=0. This will allocate 1 byte at line [4] because of the +1. However, [5] calls memset with buflen=0 which leaves the 1-byte buffer uninitialized. In line [7] strlen() can therefore access data out-of-bounds if the uninitialized byte does not contain a null-byte. This flaw is not critical because it can just crash the application. Nevertheless, it should be fixed.

The second problem is harder to identify. The function do_string() is used to parse strings from ZIP archives. If the option DS_FN gets passed, the string is written into the filename[] array from the global variable G. The code at extract.c:5584 (in the function extract_or_test_files()) calls for example this function with this option:

do_string(__G__ G.crec.filename_length, DS_FN)) != PK_COOL) Inside do_string() the following code can be found (fileio.c:3225): Ext_ASCII_TO_Native(G.filename, G.pInfo->hostnum, G.pInfo->hostver, G.pInfo->HasUxAtt, (option == DS_FN_L));

The “Ext_ASCII_TO_Native” is a define which redirects to charset_to_intern(). The first argument (G.filename in this case) is passed to this function and can be accessed with the “string” argument in the above code. At line [6] iconv() is used to convert the input string from one charset (e.g.: CP850) to another (e.g.: UTF-8). Therefore “buf” contains the converted string after this call. With line [8] this converted string should be copied over the original location from the argument (G.filename in our case).

The strncpy at [8] limits the number of written characters to buflen. Because of [3] buflen is two times the input length and therefore a buffer overflow can happen (the real size of the input buffer is not passed to the function). This vulnerability was CVE-2015-1315 and an additional check was added to prevent this buffer overflow. The additional check at [7] checks the length of the converted string with this line:

[7] (strlen(buf) < FILNAMSIZ))

Only if this check is passed the code at [8] gets executed:

[8] strncpy(string, buf, buflen);

This should logically limit the number of bytes which can be written to be smaller than FILNAMSIZ (even if the wrong, higher number, is passed to strncpy).

For example:

G.filename is defined in globals.h:372 (inside the Uz_Globs struct): char filename[FILNAMSIZ];

FILNAMSIZ is defined in unzpriv.h and is equal to PATH_MAX (4096). Therefore, filename can hold a buffer of size 4096. When the above code gets  executed and G.filename gets converted to another charset, this code gets executed:

[6] if ((iconv(cd, &s, &slen, &d, &dlen) != (size_t)-1) && [7] (strlen(buf) < FILNAMSIZ)) { [8] strncpy(string, buf, buflen); }

Let’s assume that our input string had a length of 2940. Because of [3] buflen will be 5880 (2*2940). That means if [8] is reached a strncpy with a limit of 5880 gets executed, however, the destination buffer only has a size of 4096 bytes (G.filename). The check at [7] should protect against this because if strlen(buf) (the source from strncpy) is bigger or equal than FILNAMSIZ (4096), the strncpy does not get executed. And since strncpy just copies until the first null-byte, it should just be possible to copy at maximum strlen(buf) bytes in this strncpy.

This assumption is wrong though.
Strncpy() always writes n bytes – in the above case it will always write 5880 bytes and therefore a buffer overflow will always occur. This behavior can be found in the manpage of strncpy:
“If the length of src is less than n, strncpy() writes additional null bytes to dest to ensure that a total of n bytes are written.”

The strncpy can therefore be used to write null-bytes out-of-bound in the BSS or heap segment. Since the input string length is under attacker control, the write length can be manipulated. That means that an attacker can perform a partial overwrite to exploit this vulnerability. For example, the attacker can overwrite data in the Uz_Globs struct after G.filename with null-bytes. One attack target can be heap addresses. They can be partially overwritten (lower bytes) to change the heap address to point to an attacker controlled heap chunk to get control over the data and therefore also over the execution.

4) Heap out-of-bounds access in ef_scan_for_stream (CVE-2018-1000033)

The first two arguments to the function ef_scan_for_stream() (extract.c:1167) are: ef_ptr and ef_len. This function is for example called at: extract.c:4795

sts = ef_scan_for_stream( G.extra_field, (long)G.lrec.extra_field_length, &btmp_siz, &bitmap[ 0], &xlhdr, &cmnt);

The second argument (ef_len) stores the length / size of the first argument (ef_ptr) and access checks must be erformed to ensure that no out-of-bounds access occurs.

Code line extract.c:1233 can access data out-of-bounds because length checks are missing:

bitmap = *(ef_ptr+ (data_byte++));

Debugger output:

Program received signal SIGSEGV, Segmentation fault. ef_scan_for_stream (...) at extract.c:1233 1233 bitmap = *(ef_ptr+ (data_byte++)); (gdb) print /x ef_ptr $10 = 0x7ffff7ed5f3b (gdb) print /x data_byte $11 = 0xc6 (gdb) print /x ef_len $12 = 0xc5

5) Multiple vulnerabilities in the LZMA compression algorithm (CVE-2018-1000034)

Invalid access attempts can occur at:

szip/LzmaDec.c:275 - IF_BIT_0(probLen) szip/LzmaDec.c:242 - IF_BIT_0(prob) szip/LzmaDec.c:217 - IF_BIT_0(prob) szip/LzmaDec.c:299 - TREE_6_DECODE(prob, distance); szip/LzmaDec.c:189 - GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit) szip/LzmaDec.c:264 - IF_BIT_0(probLen) szip/LzmaDec.c:201 - IF_BIT_0(prob) szip/LzmaDec.c:213 - IF_BIT_0(prob) szip/LzmaDec.c:290 - TREE_DECODE(probLen, limit, len); szip/LzmaDec.c:233 - IF_BIT_0(prob)

No further analysis has been performed on the LZMA compression code. The vendor will remove this code entirely in future releases.

Vulnerable / Tested Versions

Versions before and including 6.10 / 6.1c22 of InfoZip’s Unzip have been found to be vulnerable. Version 6.0 was the latest major release at the time the security vulnerabilities were discovered. The next beta version is 6.1c22 which has been tested as well.

Vendor Contact Timeline

2017-11-03: Vulnerability 1 identified, further internal analysis
2017-11-06: Attempt to contact the developers via bug report page
2017-11-10: Initial contact to the developer via sms@antinode.info
2017-11-10: Information from the main developer: A new beta version (6.1c22), which will be released soon, incorporates some security features. A link to the new beta version was provided.
2017-11-12: Sending encrypted advisory to sms@antinode.info, Informed developer of the latest possible release date (2017-12-30).
2017-11-13: Developer confirms the vulnerability and notes that it should be easy to fix. The developer asks for a notification if vulnerabilities are found in version 6.1c22.
2017-11-21: Vulnerability 2-5 in UnZip 6.1c22 identified, the updated encrypted advisory with crash files was sent to the developer.
2017-11-23: Developer confirmed the e-mail containing the updated advisory.
2017-12-06: Asking the developer when an update will be available and to coordinate the release of the advisory together.
2017-12-11: E-mail from the developer: All vulnerabilities (except LZMA vulnerabilities) are fixed in version 6.1c23. A link to the new version was provided. The LZMA code / feature will likely be disabled until a better solution is available.
2017-12-13: Asking the developer for a coordinated release of the advisory.
2018-01-04: Informing the developer about the changed release date because of the holidays. Distribution mailing lists will be informed on 2018-01-17, the advisory will be released about one week after that. Asking the developer for an InfoZip version with LZMA disabled.
2018-01-10: Informing the developer again that the current solution is to upgrade to version 6.10c23 which still contains the LZMA vulnerabilities and if a version without LZMA is available.
2018-01-17: Informing distros@vs.openwall.org about the upcoming advisory.
2018-02-01: Received CVE numbers.
2018-02-07: Publication of the advisory

Solution

Update to version 6.10c23: http://antinode.info/ftp/info-zip/unzip610c23.zip

Please note that the LZMA vulnerabilities are not yet fixed in this version.

Workaround

None

Advisory URL

https://www.sec-consult.com/en/vulnerability-lab/advisories/index.html

 

 

EOF R. Freingruber @2018

Contact

Interested to work with the experts of SEC Consult? Send us your application.
Want to improve your own cyber security with the experts of SEC Consult? Contact our local offices.