Safely Unzipping ZIP Archives

ZIP archives are a convenient packaging format, and many developers use them. However, ZIP archives are very generous as to what they support, such as relative paths (e.g., a ZIP archive containing an entry whose path is ../../../let/us/overwite/something). This particular bit of flexibility can represent a security flaw, if a Martian-in-the-middle (MITM) attack replaces some downloaded ZIP with another one. NowSecure pointed this out last week, demonstrating how a malformed ZIP, a MITM attack, and the multidex backport allow attackers to execute arbitrary code on devices.

I can’t fix multidex. I have been begging people to use SSL properly (thereby making MITM harder) for a while now. So, there is not a lot that I can do to help on those issues.

I can help on unpacking a ZIP archive, though.

CERT published sample safe-unziping code a while back. I have made a variation on this code, to make it more Android friendly, and added it as the unzip() method on the ZipUtils class on my CWAC-Security library.

As with the CERT code, my unzip() fails on three types of malformed ZIP archive:

  1. Does the ZIP archive attempt to use relative paths to write to locations outside of the directory where we are trying to unzip the contents? This is the attack that NowSecure outlined.

  2. Does the ZIP archive expand to more than X bytes? This is the so-called ZIP bomb attack.

  3. Does the ZIP archive contain more than X entries?

In the case of any of these failures, or a more generic exception (e.g., some IOExecption), unzip() will delete all that was previously unzipped, to not leak storage space.

As this code is doing heavy disk I/O and decompression logic, please call unzip() on a background thread.

If you find flaws in the unzip() logic, please file an issue, and I will work on it.