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

OWASP कैटगरी: MASVS-STORAGE: Storage

खास जानकारी

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

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

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

असर

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

जोखिम कम करने के तरीके

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

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

संसाधन