盘古实验室公布的一个由目录穿越导致RCE的漏洞,称为ZipperDown。
由于现有的iOS App基本上采用SSZipArchive或Ziparchive来实现解压,因此漏洞是来自使用第三方Zip库解压Zip文件的过程中没有对Zip内文件名做校验导致的;例如 SSZipArchive解压时会把文件名直接拼接到目标路径后面,如果文件名中含有“../”则可以实现目录的上一级跳转,从而实现应用内任意目录的跳转,进一步可以实现文件覆盖。
这件事之后Ziparchive修复了该漏洞。代码中增加了一个 - (NSString *)_sanitizedPath 方法用于专门处理压缩包内的文件名,清除其中有害部分。
- (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; }
这个方法清理了路径中的有害部分:
NSString *strPath = [self stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
strPath = [strPath stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet];
效果示例:
处理前:
http%3a%2f%2fwww.rfc-editor.org%2finfo%2frfc1738%3e
处理后:
http%253a%252f%252fwww.rfc-editor.org%252finfo%252frfc1738%253e
可以看到 "%" 被 "%25" 替换了
//为路径增加 "file:///" 前缀 strPath = [@"file:///" stringByAppendingString:strPath]; //路径转成NSURL,并使用 .standardizedURL.absoluteString 从而最终清理路径中的"../"获得最终的绝对路径 strPath = [NSURL URLWithString:strPath].standardizedURL.absoluteString; // 删除 "file:///" 前缀 strPath = [strPath substringFromIndex:8];
效果示例:
原路径:test/../code.png
添加前缀后:file:///test/../code.png
NSURL处理后获得绝对路径:file:///code.png
删除前缀:code.png
strPath = strPath.stringByRemovingPercentEncoding;
效果示例:
处理前:
http%253a%252f%252fwww.rfc-editor.org%252finfo%252frfc1738%253e
处理后:
http%3a%2f%2fwww.rfc-editor.org%2finfo%2frfc1738%3e
可以看到 "%25" 被 "%" 替换了。
从源码的调试中,也发现一个小问题:
当文件名包含"..\"时,文件名被读取时会被默认加入一个"\"转义符,并最终造成了路径穿越。这个路径穿越能力非常有限。
最后于 18小时前 被luoyanbei编辑 ,原因: