Path Traversal Sıkıştırma

OWASP kategorisi: MASVS-STORAGE: Depolama

Genel Bakış

ZipSlip olarak da bilinen Zip Path Traversal (Yol Geçişi) güvenlik açığı, sıkıştırılmış arşivlerin işlenmesiyle ilgilidir. Bu sayfada, ZIP biçimini örnek olarak kullanarak bu güvenlik açığını gösteriyoruz ancak TAR, RAR veya 7z gibi diğer biçimleri işleyen kitaplıklarda da benzer sorunlar ortaya çıkabilir.

Bu sorunun temel nedeni, ZIP arşivlerinde her paketlenmiş dosyanın tam nitelikli bir adla depolanmasıdır. Bu durum, eğik çizgi ve nokta gibi özel karakterlere izin verir. java.util.zip paketindeki varsayılan kitaplık, arşiv girişlerinin adlarında dizin geçişi karakterleri (../) olup olmadığını kontrol etmez. Bu nedenle, arşivden çıkarılan ad ile hedeflenen dizin yolu birleştirilirken özel dikkat gösterilmelidir.

Harici kaynaklardan alınan ZIP çıkarma kod snippet'lerini veya kitaplıklarını doğrulamanız çok önemlidir. Bu tür kitaplıkların çoğu, Zip Path Traversal (Yol Geçişi) güvenlik açığına karşı savunmasızdır.

Etki

Zip Path Traversal (Yol Geçişi) güvenlik açığı, rastgele dosya üzerine yazma işlemi yapmak için kullanılabilir. Koşullara bağlı olarak etki değişebilir ancak bu güvenlik açığı çoğu durumda kod yürütme gibi büyük güvenlik sorunlarına yol açabilir.

Çözümler

Bu sorunu azaltmak için her girişi ayıklamadan önce hedef yolun, hedef dizinin alt öğesi olduğunu doğrulamanız gerekir. Aşağıdaki kodda, hedef dizinin güvenli olduğu (yalnızca uygulamanız tarafından yazılabilir ve saldırgan kontrolünde olmadığı) varsayılır. Aksi takdirde uygulamanız, sembolik bağlantı saldırıları gibi diğer güvenlik açıklarına karşı savunmasız kalabilir.

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

Mevcut dosyaların yanlışlıkla üzerine yazılmasını önlemek için ayıklama işlemine başlamadan önce hedef dizinin boş olduğundan da emin olmanız gerekir. Aksi takdirde, uygulamanızın kilitlenmesi veya aşırı durumlarda uygulamanın güvenliğinin ihlal edilmesi riskiyle karşılaşırsınız.

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

Kaynaklar