ज़िप पाथ ट्रेवर्सल

OWASP कैटगरी: MASVS-STORAGE: स्टोरेज

खास जानकारी

ज़िप पाथ ट्रेवर्सल की समस्या, जिसे ZipSlip के नाम से भी जाना जाता है, कंप्रेस किए गए संग्रहों को मैनेज करने से जुड़ी है. इस पेज पर, हम ZIP फ़ॉर्मैट का इस्तेमाल करके इस समस्या के बारे में बताते हैं. हालांकि, TAR, RAR या 7z जैसे अन्य फ़ॉर्मैट को मैनेज करने वाली लाइब्रेरी में भी इसी तरह की समस्याएं आ सकती हैं.

इस समस्या की मुख्य वजह यह है कि ZIP संग्रहों में, पैक की गई हर फ़ाइल को पूरी तरह से क्वालिफ़ाइड नाम के साथ सेव किया जाता है. इससे स्लैश और डॉट जैसे खास वर्णों का इस्तेमाल किया जा सकता है. java.util.zip पैकेज की डिफ़ॉल्ट लाइब्रेरी, संग्रह की एंट्री के नामों में डायरेक्ट्री ट्रेवर्सल वर्णों (../) की जांच नहीं करती. इसलिए, संग्रह से निकाले गए नाम को टारगेट डायरेक्ट्री के पाथ के साथ जोड़ते समय, खास ध्यान रखना ज़रूरी है.

बाहरी सोर्स से ZIP फ़ाइल को एक्सट्रैक्ट करने वाले किसी भी कोड स्निपेट या लाइब्रेरी की पुष्टि करना बहुत ज़रूरी है. ज़्यादातर लाइब्रेरी में, ज़िप पाथ ट्रेवर्सल की समस्या हो सकती है.

असर

ज़िप पाथ ट्रेवर्सल की समस्या का इस्तेमाल करके, किसी भी फ़ाइल को बदला जा सकता है. हालात के हिसाब से, इस समस्या का असर अलग-अलग हो सकता है. हालांकि, कई मामलों में इस समस्या की वजह से, सुरक्षा से जुड़ी बड़ी समस्याएं हो सकती हैं. जैसे, कोड एक्ज़ीक्यूशन.

समस्या को कम करने के तरीके

इस समस्या को कम करने के लिए, हर एंट्री को एक्सट्रैक्ट करने से पहले, हमेशा पुष्टि करें कि टारगेट पाथ, डेस्टिनेशन डायरेक्ट्री का चाइल्ड हो. नीचे दिया गया कोड, यह मानकर चलता है कि डेस्टिनेशन डायरेक्ट्री सुरक्षित है. इसका मतलब है कि इसे सिर्फ़ आपका ऐप्लिकेशन लिख सकता है और इस पर हमलावर का कंट्रोल नहीं है. ऐसा न होने पर, आपके ऐप्लिकेशन में 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;
 }

मौजूदा फ़ाइलों को गलती से बदलने से बचने के लिए, आपको यह भी पक्का करना चाहिए कि एक्सट्रैक्शन की प्रोसेस शुरू करने से पहले, डेस्टिनेशन डायरेक्ट्री खाली हो. ऐसा न करने पर, ऐप्लिकेशन के क्रैश होने या गंभीर मामलों में, ऐप्लिकेशन के कंप्रोमाइज़ होने का खतरा हो सकता है.

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

संसाधन