OWASP 类别: MASVS-STORAGE:存储
概述
ZIP 路径遍历漏洞,也称为 ZipSlip,与处理压缩存档有关。在本页中,我们以 ZIP 格式为例演示此漏洞,但类似的问题也可能出现在处理其他格式(如 TAR、RAR 或 7z)的库中。
此问题的根本原因是,在 ZIP 存档内部,每个打包的文件都存储有完整限定名称,这允许使用斜杠和点等特殊字符。来自 java.util.zip
包的默认库不会检查存档条目的名称中是否存在目录遍历字符(../
),因此在将从存档中提取的名称与目标目录路径连接时,必须格外小心。
验证来自外部来源的任何 ZIP 提取代码片段或库非常重要。许多此类库容易受到 ZIP 路径遍历的攻击。
影响
ZIP 路径遍历漏洞可用于实现任意文件覆盖。根据具体情况,影响可能会有所不同,但在许多情况下,此漏洞会导致严重的安全问题,例如代码执行。
缓解措施
为了缓解此问题,在提取每个条目之前,应始终验证目标路径是否为目标目录的子目录。以下代码假设目标目录是安全的——仅可由您的应用写入,并且不受攻击者控制——否则,您的应用可能会容易受到其他漏洞(例如符号链接攻击)的影响。
Kotlin
companion object {
@Throws(IOException::class)
fun newFile(targetPath: File, zipEntry: ZipEntry): File {
val name: String = zipEntry.name
val f = File(targetPath, name)
val canonicalPath = f.canonicalPath
if (!canonicalPath.startsWith(
targetPath.canonicalPath + File.separator)) {
throw ZipException("Illegal name: $name")
}
return f
}
}
Java
public static File newFile(File targetPath, ZipEntry zipEntry) throws IOException {
String name = zipEntry.getName();
File f = new File(targetPath, name);
String canonicalPath = f.getCanonicalPath();
if (!canonicalPath.startsWith(targetPath.getCanonicalPath() + File.separator)) {
throw new ZipException("Illegal name: " + name);
}
return f;
}
为了避免意外覆盖现有文件,在开始提取过程之前,还应确保目标目录为空。否则,您可能会面临潜在的应用崩溃风险,或者在极端情况下,应用遭到破坏。
Kotlin
@Throws(IOException::class)
fun unzip(inputStream: InputStream?, destinationDir: File) {
if (!destinationDir.isDirectory) {
throw IOException("Destination is not a directory.")
}
val files = destinationDir.list()
if (files != null && files.isNotEmpty()) {
throw IOException("Destination directory is not empty.")
}
ZipInputStream(inputStream).use { zipInputStream ->
var zipEntry: ZipEntry
while (zipInputStream.nextEntry.also { zipEntry = it } != null) {
val targetFile = File(destinationDir, zipEntry.name)
// ...
}
}
}
Java
void unzip(final InputStream inputStream, File destinationDir)
throws IOException {
if(!destinationDir.isDirectory()) {
throw IOException("Destination is not a directory.");
}
String[] files = destinationDir.list();
if(files != null && files.length != 0) {
throw IOException("Destination directory is not empty.");
}
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
final File targetFile = new File(destinationDir, zipEntry);
…
}
}
}
资源
为您推荐
- 注意:当 JavaScript 关闭时,将显示链接文本。
- 路径遍历