Travessia de caminho de arquivo ZIP

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Visão geral

A vulnerabilidade de travessia de caminho de arquivo ZIP, também conhecida como ZipSlip, está relacionada ao gerenciamento de arquivos compactados. Nesta página, demonstramos essa vulnerabilidade usando o formato ZIP como exemplo, mas problemas semelhantes podem surgir em bibliotecas que processam outros formatos, como TAR, RAR ou 7z.

O motivo desse problema é que, nos arquivos ZIP, cada arquivo empacotado é armazenado com um nome totalmente qualificado, o que permite caracteres especiais, como barras e pontos. A biblioteca padrão do pacote java.util.zip não verifica os nomes das entradas de arquivo para caracteres de travessia de diretório (../). Portanto, é necessário ter cuidado especial ao concatenar o nome extraído do arquivo com o caminho do diretório de destino.

É muito importante validar todos os snippets de código ou bibliotecas de extração de ZIP de origens externas. Muitas dessas bibliotecas são vulneráveis a travessias de caminho de arquivo ZIP.

Impacto

A vulnerabilidade de travessia de caminho de arquivo ZIP pode ser usada para conseguir substituição arbitrária de arquivos. Dependendo das condições, o impacto pode variar, mas em muitos casos essa vulnerabilidade pode levar a grandes problemas de segurança, como a execução de código.

Mitigações

Para atenuar esse problema, antes de extrair cada entrada, verifique sempre se o caminho é um filho do diretório de destino. O código abaixo presume que o diretório de destino é seguro, gravável apenas pelo app, e que não está sob controle do invasor. Caso contrário, ele poderá estar propenso a outras vulnerabilidades, como ataques de link simbólico.

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;
}

Para evitar a substituição acidental de arquivos existentes, verifique se o diretório de destino está vazio antes de iniciar o processo de extração. Caso contrário, você corre o risco de possíveis falhas ou, em casos extremos, de um comprometimento do aplicativo.

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.getName());
            // ...
        }
    }
}

Recursos