Zip Path Traversal

Kategori OWASP: MASVS-STORAGE: Penyimpanan

Ringkasan

Kerentanan Zip Path Traversal, yang juga dikenal sebagai ZipSlip, berkaitan dengan penanganan arsip terkompresi. Di halaman ini, kami mendemonstrasikan kerentanan ini menggunakan format ZIP sebagai contoh, tetapi masalah serupa dapat muncul di library yang menangani format lain, seperti TAR, RAR, atau 7z.

Alasan yang mendasari masalah ini adalah bahwa di dalam arsip ZIP, setiap file yang dipaketkan disimpan dengan nama yang sepenuhnya memenuhi syarat, sehingga memungkinkan karakter khusus seperti garis miring dan titik. Library default dari paket java.util.zip tidak memeriksa nama entri arsip untuk karakter traversal direktori (../) sehingga Anda harus sangat berhati-hati saat menggabungkan nama yang diekstrak dari arsip dengan jalur direktori yang ditargetkan.

Sangat penting untuk memvalidasi library atau cuplikan kode pengekstrak ZIP apa pun dari sumber eksternal. Ada banyak library seperti ini yang rentan terhadap Zip Path Traversal.

Dampak

Kerentanan Zip Path Traversal dapat digunakan untuk mencapai penimpaan file arbitrer. Bergantung pada kondisinya, dampaknya dapat bervariasi, tetapi dalam banyak kasus, kerentanan ini dapat menyebabkan masalah keamanan besar seperti eksekusi kode.

Mitigasi

Untuk mengurangi masalah ini, sebelum mengekstrak setiap entri, Anda harus selalu memastikan bahwa jalur target adalah turunan dari direktori tujuan. Kode di bawah ini menganggap direktori tujuan aman, hanya dapat ditulis oleh aplikasi Anda dan tidak berada di bawah kontrol penyerang. Jika tidak, aplikasi Anda bisa menjadi rentan terhadap kerentanan lainnya, seperti serangan 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;
 }

Agar tidak menimpa file yang ada secara tidak sengaja, Anda juga harus memastikan direktori tujuan kosong sebelum memulai proses ekstraksi. Jika tidak, aplikasi Anda berisiko mengalami error, atau pada kasus ekstrem, aplikasi akan disusupi.

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

Referensi

  • Catatan: teks link ditampilkan saat JavaScript nonaktif
  • Path traversal