پیمایش مسیر زیپ

رده OWASP: MASVS-STORAGE: ذخیره‌سازی

نمای کلی

آسیب‌پذیری پیمایش مسیر زیپ (Zip Path Traversal) که با نام ZipSlip نیز شناخته می‌شود، مربوط به مدیریت بایگانی‌های فشرده است. در این صفحه، ما این آسیب‌پذیری را با استفاده از فرمت ZIP به عنوان مثال نشان می‌دهیم، اما مشکلات مشابهی می‌تواند در کتابخانه‌هایی که فرمت‌های دیگری مانند TAR، RAR یا 7z را مدیریت می‌کنند، ایجاد شود.

دلیل اصلی این مشکل این است که در داخل آرشیوهای ZIP، هر فایل فشرده با یک نام کاملاً مشخص ذخیره می‌شود که امکان استفاده از کاراکترهای خاص مانند اسلش و نقطه را فراهم می‌کند. کتابخانه پیش‌فرض از بسته java.util.zip نام ورودی‌های آرشیو را برای کاراکترهای پیمایش دایرکتوری ( ../ ) بررسی نمی‌کند، بنابراین هنگام اتصال نام استخراج‌شده از آرشیو به مسیر دایرکتوری مورد نظر باید دقت ویژه‌ای به عمل آید.

اعتبارسنجی هرگونه قطعه کد یا کتابخانه استخراج‌کننده‌ی فایل زیپ از منابع خارجی بسیار مهم است. بسیاری از این کتابخانه‌ها در برابر پیمایش مسیر فایل زیپ آسیب‌پذیر هستند.

تأثیر

آسیب‌پذیری پیمایش مسیر زیپ می‌تواند برای بازنویسی دلخواه فایل مورد استفاده قرار گیرد. بسته به شرایط، تأثیر آن ممکن است متفاوت باشد، اما در بسیاری از موارد این آسیب‌پذیری می‌تواند منجر به مشکلات امنیتی بزرگی مانند اجرای کد شود.

کاهش‌ها

برای کاهش این مشکل، قبل از استخراج هر ورودی، همیشه باید تأیید کنید که مسیر هدف، فرزند دایرکتوری مقصد است. کد زیر فرض می‌کند که دایرکتوری مقصد امن است - فقط توسط برنامه شما قابل نوشتن است و تحت کنترل مهاجم نیست - در غیر این صورت برنامه شما می‌تواند مستعد آسیب‌پذیری‌های دیگری مانند حملات سیملینک باشد.

کاتلین

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

جاوا

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

برای جلوگیری از رونویسی تصادفی فایل‌های موجود، باید قبل از شروع فرآیند استخراج، مطمئن شوید که دایرکتوری مقصد خالی است. در غیر این صورت، خطر خرابی احتمالی برنامه یا در موارد شدید، به خطر افتادن برنامه وجود دارد.

کاتلین

@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)
            // ...
        }
    }
}

جاوا

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

منابع

{% کلمه به کلمه %} {% فعل کمکی %}
  • توجه: متن لینک زمانی نمایش داده می‌شود که جاوا اسکریپت غیرفعال باشد.
  • پیمایش مسیر
{% کلمه به کلمه %}
{% فعل کمکی %}