CodeQL 文档

解压缩期间的任意文件访问(“Zip Slip”)

ID: java/zipslip
Kind: path-problem
Security severity: 7.5
Severity: error
Precision: high
Tags:
   - security
   - external/cwe/cwe-022
Query suites:
   - java-code-scanning.qls
   - java-security-extended.qls
   - java-security-and-quality.qls

点击查看 CodeQL 代码库中的查询

如果未正确验证来自存档的文件名,则从恶意 zip 文件或类似类型的存档中提取文件时,可能会遭受目录遍历攻击。

Zip 存档包含表示存档中每个文件的存档条目。这些条目包含条目的文件路径,但这些文件路径不受限制,并且可能包含意外的特殊元素,例如目录遍历元素 (..)。如果使用这些文件路径来创建文件系统路径,则文件操作可能会在意外的位置发生。这可能会导致敏感信息泄露或删除,或者攻击者能够通过修改意外文件来影响行为。

例如,如果一个 zip 文件包含文件条目 ..\sneaky-file,并且该 zip 文件被解压缩到目录 c:\output 中,那么简单地组合路径将导致输出文件路径为 c:\output\..\sneaky-file,这将导致文件被写入到 c:\sneaky-file

建议

确保验证从 zip 存档条目构造的输出路径,以防止将文件写入意外的位置。

从 zip 存档条目写入输出文件的推荐方法是验证输出文件的规范化完整路径是否以与目标目录匹配的前缀开头。路径规范化可以使用 java.io.File.getCanonicalFile()java.nio.file.Path.normalize() 完成。可以使用 String.startsWith(..) 进行前缀检查,但最好使用 java.nio.file.Path.startsWith(..),因为后者适用于完整的路径段。

另一种方法是根据预期文件的白名单验证存档条目。

示例

在此示例中,从 zip 存档项条目获取的文件路径与目标目录组合在一起。结果用作目标文件路径,而不验证结果是否在目标目录内。如果提供包含 ..\sneaky-file 之类的存档路径的 zip 文件,则此文件将被写入目标目录之外。

void writeZipEntry(ZipEntry entry, File destinationDir) {
    File file = new File(destinationDir, entry.getName());
    FileOutputStream fos = new FileOutputStream(file); // BAD
    // ... write entry to fos ...
}

要修复此漏洞,我们需要验证规范化的 file 是否仍然以 destinationDir 作为其前缀,如果不是,则抛出异常。

void writeZipEntry(ZipEntry entry, File destinationDir) {
    File file = new File(destinationDir, entry.getName());
    if (!file.toPath().normalize().startsWith(destinationDir.toPath()))
        throw new Exception("Bad zip entry");
    FileOutputStream fos = new FileOutputStream(file); // OK
    // ... write entry to fos ...
}

参考资料

  • ©GitHub 公司
  • 条款
  • 隐私