Pangu LABS published a bug called ZipperDown that causes RCE through directory traversal.

Because the existing iOS App basically uses SSZipArchive or Ziparchive to achieve decompression, so the vulnerability is from the use of third-party Zip library decompression Zip file process does not check the file name in the Zip caused; For example, SSZipArchive will unzip the file name directly after the destination path, if the file name contains “.. / “can be implemented to jump to any directory in the application, and further to achieve file overwrite.

(if you want to test the hole, can go to dead simple download this Demo:https://github.com/muzipiao/ZipperDown)


The Ziparchive has since fixed the bug. A – (NSString *)_sanitizedPath method has been added to the code to specifically handle the file names in the compressed package and clean up the harmful parts.

Ziparchive library source address: https://github.com/ZipArchive/ZipArchive


Ssziparchii. m _sanitizedPath method source code:

- (NSString *)_sanitizedPath
{
    // Change Windows paths to Unix paths: https://en.wikipedia.org/wiki/Path_(computing)
    // Possible improvement: only do this if the archive was created on a non-Unix system
    NSString *strPath = [self stringByReplacingOccurrencesOfString:@"\ \" withString:@"/"];

    // Percent-encode file path (where path is defined by https://tools.ietf.org/html/rfc8089)
    // The key part is to allow characters "." and "/" and disallow "%".
    // CharacterSet.urlPathAllowed seems to do the job
#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 || __WATCH_OS_VERSION_MIN_REQUIRED >= 20000 || __TV_OS_VERSION_MIN_REQUIRED >= 90000)
    strPath = [strPath stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet];
#else
    // Testing availability of @available (https://stackoverflow.com/a/46927445/1033581)
#if __clang_major__ < 9
    // Xcode 8-
    if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4) {
#else
    // Xcode 9+
    if@available(macOS 10.9, iOS 7.0, watchOS 2.0, tvOS 9.0, *) {#endif
        strPath = [strPath stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet];
    } else {
        strPath = [strPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    }
#endif

    // `NSString.stringByAddingPercentEncodingWithAllowedCharacters:` may theorically fail: https://stackoverflow.com/questions/33558933/
    // But because we auto-detect encoding using `NSString.stringEncodingForData:encodingOptions:convertedString:usedLossyConversion:`,
    // we likely already prevent UTF-16, UTF-32 and invalid Unicode in the form of unpaired surrogate chars: https://stackoverflow.com/questions/53043876/
    // To be on the safe side, we will still perform a guard check.
    if (strPath == nil) {
        return nil;
    }

    // Add scheme "file:///" to support sanitation on names with a colon like "file:a/.. /.. /.. /usr/bin"
    strPath = [@"file:///" stringByAppendingString:strPath];

    // Sanitize path traversal characters to prevent directory backtracking. Ignoring these characters mimicks the default behavior of the Unarchiving tool on macOS.
    // ".. /.. /.. /.. /.. /.. /.. /.. /.. /.. /.. /tmp/test.txt" -> "tmp/test.txt"
    // "a/b/.. /c.txt" -> "a/c.txt"
    strPath = [NSURL URLWithString:strPath].standardizedURL.absoluteString;

    // Remove the "file:///" scheme
    strPath = [strPath substringFromIndex:8];

    // Remove the percent-encoding
#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 || __WATCH_OS_VERSION_MIN_REQUIRED >= 20000 || __TV_OS_VERSION_MIN_REQUIRED >= 90000)
    strPath = strPath.stringByRemovingPercentEncoding;
#else
    // Testing availability of @available (https://stackoverflow.com/a/46927445/1033581)
#if __clang_major__ < 9
    // Xcode 8-
    if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4) {
#else
    // Xcode 9+
    if@available(macOS 10.9, iOS 7.0, watchOS 2.0, tvOS 9.0, *) {#endif
        strPath = strPath.stringByRemovingPercentEncoding;
    } else {
        strPath = [strPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    }
#endif

    return strPath;
}
Copy the code


Parse the _sanitizedPath method

This method clears the harmful parts of the path:

(1) Convert Windows path to Unix path, replace “\\” with “/” :

 NSString *strPath = [self stringByReplacingOccurrencesOfString:@"\ \" withString:@"/"]; 

Copy the code

(2) allow “.” and “/”, but not “%”;

strPath = [strPath stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet]; Copy the code

Examples of effects:

Before processing: HTTP %3a%2f%2fwww.rfc-editor.org%2finfo%2frfc1738%3e

After processing: HTTP %253a%252f%252fwww.rfc-editor.org%252finfo%252frfc1738%253e

You can see that “%” is replaced by “%25”

(3) add the “file:///” prefix of scheme so that when converted to NSURL, it can handle the “.. /” to obtain the absolute path:

// Add the path"file:///"Prefix strPath = [@"file:///"stringByAppendingString:strPath]; / / path into NSURL, and using the standardizedURL. AbsoluteString to finally clear the path".. /"Be an absolute path strPath = [NSURL URLWithString: strPath]. StandardizedURL. AbsoluteString; / / delete"file:///"StrPath = [strPath substringFromIndex:8];Copy the code

Examples of effects:

Original path: test/.. /code.png

After adding the prefix: file:///test/,… /code.png

NSURL obtains the absolute path: file:///code.png

Delete the code.png prefix

(4) Clear percentage sign code

strPath = strPath.stringByRemovingPercentEncoding; 

Copy the code

Examples of effects:

Before processing: HTTP %253a%252f%252fwww.rfc-editor.org%252finfo%252frfc1738%253e

After processing: HTTP %3a%2f%2fwww.rfc-editor.org%2finfo%2frfc1738%3e

You can see that “%25” is replaced by “%”.

After the above steps, you finally get a strPath that works


Development:

From the source debugging, also found a small problem: when the file name contains “.. \”, the file name is read with a “\” escape character by default, resulting in path traversal.

The practical steps are as follows:

1, the original file name is “.. \code.png”, test is the folder:



2, the original file path is “test/.. \code.png”, escape as “test/.. \\code.png”:



3, replace “\\” with “/” in _sanitizedPath, so “test/.. \\code.png” becomes “test/.. / code. PNG “:



4, for “test /.. /code. PNG “added “file:///” prefix:



5, then do the absolute path processing, “file:///test/… /code.png” becomes “file:///code.png”, the test folder is gone!



PNG :// file:// “; // file:// “; // file:// “;



7, after executing the decompression, app Documents path, “.. \code. PNG “file is outside the test folder: