Zip Path Traversal

Categoria OWASP: MASVS-STORAGE: Storage

Panoramica

La vulnerabilità Zip Path Traversal, nota anche come ZipSlip, è correlata alla gestione degli archivi compressi. In questa pagina, dimostriamo questa vulnerabilità utilizzando il formato ZIP come esempio, ma problemi simili possono verificarsi nelle librerie che gestiscono altri formati, come TAR, RAR o 7z.

Il motivo alla base di questo problema è che all'interno degli archivi ZIP, ogni file compresso viene archiviato con un nome completo, che consente caratteri speciali come barre e punti. La libreria predefinita del pacchetto java.util.zip non controlla i nomi delle voci dell'archivio per i caratteri di attraversamento della directory (../), pertanto è necessario prestare particolare attenzione quando si concatena il nome estratto dall'archivio con il percorso della directory di destinazione.

È molto importante convalidare tutti gli snippet di codice o le librerie di estrazione ZIP da fonti esterne. Molte di queste librerie sono vulnerabili a Zip Path Traversal.

Impatto

La vulnerabilità Zip Path Traversal può essere utilizzata per sovrascrivere file arbitrari. A seconda delle condizioni, l'impatto può variare, ma in molti casi questa vulnerabilità può portare a gravi problemi di sicurezza come l'esecuzione di codice.

Mitigazioni

Per mitigare questo problema, prima di estrarre ogni voce, devi sempre verificare che il percorso di destinazione sia un figlio della directory di destinazione. Il codice riportato di seguito presuppone che la directory di destinazione sia sicura, scrivibile solo dalla tua app e non sotto il controllo dell'aggressore, altrimenti la tua app potrebbe essere soggetta ad altre vulnerabilità come gli attacchi di symlink.

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

Per evitare di sovrascrivere accidentalmente i file esistenti, devi anche assicurarti che la directory di destinazione sia vuota prima di avviare il processo di estrazione. In caso contrario, rischi potenziali arresti anomali dell'app o, in casi estremi, una compromissione dell'applicazione.

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

Risorse

  • Nota: il testo del link viene visualizzato quando JavaScript è disattivato
  • Path traversal